The project I am working on requires data in our system to be synchronized with that of another (the other system is quite popular which is why synchronization is so important). However, I am having a weird issue when I attempt to update an existing entity that has a composite id.
The problem is that whenever the entity to be updated is retrieved (using Get) prior to calling Merge, it does not work (the changes are not persisted to the DB but no exception is thrown). When I remove the call to Get, updating the entity works. Knowledge on whether the entity exists is needed because if it is being created, part of the composite id needs to be generated.
bool exists = ScanForInstance(instance);
using (var session = SessionFactoryFactory.GetSessionFactory<T>().OpenSession())
{
if (exists)
{
instance = (T)session.Merge(instance);
}
else
{
KeyGenerator.Assign<T>(instance);
newId = session.Save(instance);
}
session.Flush();
}
The Get call is made in the ScanForInstance method:
private bool ScanForInstance<T>(T instance)
where T : class
{
var id = IdResolver.ResolveObject<T>(instance);
using (var session = SessionFactoryFactory.GetSessionFactory<T>().OpenStatelessSession())
{
return session.Get<T>(id) != null;
}
}
The IdResolver is used for determining what should be used for the id (the value of a single key in a mapping, otherwise the object itself for entities with composite ids).
Like I said, if I remove the call to Get it works fine. It works fine for all other operations as well (create, read, and deletes). All operations, including updating, works fine for entities with single keys.
The DB is Pervasive and there are a certain number of restrictions:
- No, I cannot change any of the schema (I see that as a frequent response to problems with FNB).
- I do not want to just delete then insert as there are some columns we do not sync back to our system and I do not want to wipe these out
UPDATED: I've added a simple example that people can copy/paste to test this weird behavior (if it is in fact universal). I'm hoping people will do this to at least confirm my problem.
Type to be mapped, Fluent mapping:
public class ParentType
{
public virtual long AssignedId { get; set; }
public virtual long? GeneratedId { get; set; }
public virtual string SomeField { get; set; }
public override bool Equals(object obj)
{
return Equals(obj as ParentType);
}
private bool Equals(ParentType other)
{
if (ReferenceEquals(this, other)) return true;
if (ReferenceEquals(null, other)) return false;
return AssignedId == other.AssignedId &&
GeneratedId == other.GeneratedId;
}
public override int GetHashCode()
{
unchecked
{
int hash = GetType().GetHashCode();
hash = (hash * 31) ^ AssignedId.GetHashCode();
hash = (hash * 31) ^ GeneratedId.GetHashCode();
return hash;
}
}
}
public class ParentMap : ClassMap<ParentType>
{
public ParentMap()
{
Table("STANDARDTASKITEM");
CompositeId()
.KeyProperty(x => x.AssignedId, "STANDARDTASK")
.KeyProperty(x => x.GeneratedId, "STANDARDTASKITEM");
Map(x => x.SomeField, "DESCRIPTION");
Not.LazyLoad();
}
}
Don't mind the fact it is called 'ParentType.' I don't actually have any other mappings with this and don't actually use the type as a parent type in this example. It is called this because I'm about to open another question that does involve problems with composite ids and inheritance (DON'T USE COMPOSITE ID'S! :-D).
For the actual testing, I just created a console project in VS with this as the Program.cs:
static void Main(string[] args)
{
var smFactory = Fluently.Configure()
.Database(() => new OdbcPersistenceConfigurer()
.Driver<OdbcDriver>()
.Dialect<GenericDialect>()
.Provider<DriverConnectionProvider>()
.ConnectionString(BuildSMConnectionString())
.ProxyFactoryFactory(typeof(NHibernate.ByteCode.Castle.ProxyFactoryFactory))
.UseReflectionOptimizer()
.UseOuterJoin())
.Mappings
(m =>
m.FluentMappings.Add<ParentMap>()
);
var sessionFactory = smFactory.BuildSessionFactory();
var updatedInstance = new ParentType
{
AssignedId = 1,
GeneratedId = 13,
SomeField = "UPDATED"
};
bool exists;
using (var session = sessionFactory.OpenStatelessSession())
{
exists = session.Get<ParentType>(updatedInstance) != null;
}
using (var session = sessionFactory.OpenSession())
{
if (exists)
{
session.Merge(updatedInstance);
session.Flush();
}
}
}
private static string BuildSMConnectionString()
{
// Return your connection string here
}
class OdbcPersistenceConfigurer : PersistenceConfiguration<OdbcPersistenceConfigurer, OdbcConnectionStringBuilder>
{
}
I know that adding this sample is only slightly more helpful since anyone wanting to test this would either need to change the ParentType field to conform to a table that they already have in their own DB, or add a table to match what is mapped in ParentType. I'm hoping someone will do this at least out of curiosity now that I've given a good head-start on testing.