The project I'm working on requires data synchronization in our system with another (another system is quite popular, so synchronization is so important). However, I have a strange problem when I try to update an existing object with a composite identifier.
The problem is that whenever the restored object is retrieved (using Get ) before the Merge call, it does not work (the changes are not saved to the DB, but the exception is not thrown). When I delete the Get call, updating the object works. Knowing whether an entity exists is necessary because if it is created, it is necessary to generate a part of a composite identifier.
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 executed in ScanForInstance :
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; } }
IdResolver is used to determine what should be used for the identifier (the value of one key in the mapping, otherwise the object itself for objects with composite identifiers).
As I said, if I delete the Get call, it works fine. It works great for all other operations (create, read and delete). All operations, including updating, work fine for objects with single keys.
Pervasive database and there are a certain number of restrictions:
- No, I cannot change any scheme (I see this as a frequent answer to problems with FNB).
- I do not want to simply delete and then insert, because there are some columns that we do not synchronize with our system, and I do not want to destroy them.
UPDATED: I added a simple example that people can copy / paste to test this strange behavior (if it is truly universal). I hope people do this to at least confirm my problem.
Type to display, Free 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(); } }
Do not mind that it is called ParentType. Actually, I have no other mappings with this, and actually don't use this type as the parent type in this example. This is called that because I'm going to open up another question that is related to issues with compound identifiers and inheritance ( DO NOT USE COMPOSITE IDs !: -D ).
For actual testing, I just created a console project in VS with this as 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() {
I know that adding this example is a little more useful, since anyone who wants to test this will either have to change the ParentType field to fit the table that they already have in their own database, or add a table to match what is displayed in ParentType. I hope that someone will do this at least out of curiosity, now that I have a good start in testing.