NHIBERNATE – When the entity is previously retrieved, an entity with a composite ID cannot be combined.

The project I’m working on requires the data in our system to be synchronized with the data in another system (the other system is very popular, which is why synchronization is so important). However, when I encountered a strange problem when I tried to update an existing entity with a composite ID.

The problem is that whenever I retrieve the entity to be updated before calling Merge (using Get), None of it works (changes are not persisted to the DB, but no exception is thrown). When I delete the call to Get, updating the entity works. Need to know if the entity exists, because if the entity is being created, it needs to be generated Partial composite ID.

bool exists = ScanForInstance(instance);
using (var session = SessionFactoryFactory.GetSessionFactory().OpenSession())
{
if (exists)
{
instance = (T)session.Merge(instance);
}
else
{
KeyGenerator.Assign(instance);
newId = session.Save(instance);
}

session.Flush();
}

The Get call is made in the ScanForInstance method:

private bool ScanForInstance(T instance)
where T: class
{
var id = IdResolver.ResolveObject(instance);
using (var session = SessionFactoryFactory.GetSessionFactory().OpenStatelessSession())
{
return session.Get(i d) != null;
}
}

IdResolver is used to determine what should be used for id (the value of a single key in the map, otherwise it is an object of an entity with a composite id Itself).

Like I said, if I delete call Get it works fine. It also applies to all other operations (create, read, and delete). All operations (including updates) apply to Entities with a single key.

The database is Pervasive and has a certain number of restrictions:

>No, I cannot change any schema (I think this is a frequent response to FNB questions) .
>I don’t want to delete and then insert, because there are some columns we didn’t sync back to our system, I don’t want to erase these

Update: I added a simple example where people can copy/paste To test this weird behavior (if it is actually universal). I hope people can at least confirm my problem by doing this.

Type to be mapped, Fluent mapping:

public class ParentType
{
public virtual long AssignedId {get; set; }

public virtual long? GeneratedId {get; set; }< br />
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 &&
Gener atedId == 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
{
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 it being called'ParentType'. I don't actually have any other mappings, and I don't actually use that type as a parent type in this example. The reason for this The title is because I am about to open another question involving composite ID and inheritance (don't use composite ID! :-D).

For the actual test, I just created a console project in VS as Program.cs:

static void Main (string[] args)
{
var smFactory = Fluently.Configure()
.Database(() => new OdbcPersistenceConfigurer()
.Driver()< br /> .Dialect()
.Provider()
.ConnectionString(BuildSMConnectionString())
.ProxyFactoryFactory(typeof(NHibernate.ByteCode.Castle.ProxyFactoryFactory))
.UseReflectionOptimizer()
.UseOuterJoin())
.Mappings
(m =>
m.FluentMappings.Add()
) ;

var sessionFactory = smFactory.BuildSessionFactory();

var updatedInstance = new ParentType
{
AssignedId = 1,
GeneratedId = 13,
SomeField = "UPDATED"
};

bool exists;

usi ng (var session = sessionFactory.OpenStatelessSession())
{
exists = session.Get(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
{

}

I know that adding this example is only slightly useful because Anyone who wants to test it needs to change the ParentType field to match the table they already have in their own DB, or add a table to match what is mapped in the ParentType. I hope someone will do this, at least out of curiosity, because I have given a good start to the test.

Well, I at least found a solution to the problem Way, but not why. My solution is to create a new type that contains the attributes I use as composite id:

public class CompositeIdType
{
public virtual long A ssignedId {get; set; }

public virtual long GeneratedId {get; set; }

public override bool Equals(object obj)
{
return Equals(obj as CompositeIdType);
}

private bool Equals(CompositeIdType other)
{
if (ReferenceEquals(this, other)) return true;< br /> 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;
}
}
}

Then, replace the attributes in ParentType with a reference to this new type:

public class ParentType
{
public virtual Comp ositeIdType Key {get; set; }

public virtual string SomeField {get; set; }
}

With these changes, the new mapping will be:

public class ParentMap: ClassMap
{
public ParentMap()
{
Table("STANDARDTASKITEM");

CompositeId(x => x.Key)
.KeyProperty(x => x.AssignedId, "STANDARDTASK")
.KeyProperty(x => x.GeneratedId, "STANDARDTASKITEM");

Map(x => x.SomeField, "DESCRIPTION");

Not.LazyLoad();
}
}

After completing all these changes, Merge works fine even if Get is called before Merge is called. My best choice is that the non-generic form of CompositeId does not perform certain operations correctly, or when When you call Merge on the entity that uses it, the mapping it is making is not compatible with NH (I want to enter the source code. If this is the case, FNH wants to solve it, but I have spent too much time figuring out how to circumvent it. (Over this question).

It’s all good but this requires me to create a new type for each entity I map, or at least create a new type for ids with a different number of keys (i.e. a A type with 2 keys, a type with 3 types) keys, etc.).

To avoid this, I can hack it so that you add a reference of the same type as the mapping, and Set a reference to this in the constructor:

public class ParentType
{
public ParentType()
{
K ey = this;
}

public virtual ParentType Key {get; set; }

public virtual long AssignedId {get; set; }
< br /> 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;
}
}
}

Then the mapping will be:

public class ParentMap: ClassMap< ParentType>
{
public ParentMap()
{
Table("STANDARDTASKITEM");

CompositeId(x => x.Key )
.KeyProperty(x => x.AssignedId, "STANDARDTASK")
.KeyProperty(x => x.GeneratedId, "STANDARDTASKITEM");

Map(x = > x.SomeField, "DESCRIPTION");

Not.LazyLoad();
}
}

I have tested this for updating and Insert using Merge with get is called before the merge and is surprisingly IT work. I'm still in the scope of using fixes (new types that include compound id or self-referencing) because self-referencing seems a bit clumsy to my taste .

If anyone finds out why this doesn’t work, I still want to know...

The project I’m working on requires the data in our system to be Data synchronization in another system (another system is popular, which is why synchronization is so important). However, when I try to update an existing entity with a composite ID, I ran into a strange problem.

The problem is that whenever the entity to be updated is retrieved (using Get) before calling Merge, it does not work (the changes will not be persisted to the DB, but no exception will be thrown). When I delete the call to Get, updating the entity works. I need to know whether the entity exists, because if the entity is being created, part of the composite ID needs to be generated.

bool exists = ScanForInstance( instance);
using (var session = SessionFactoryFact ory.GetSessionFactory().OpenSession())
{
if (exists)
{
instance = (T)session.Merge(instance);
}
else
{
KeyGenerator.Assign(instance);
newId = session.Save(instance);
}

session.Flush();
}

The Get call is made in the ScanForInstance method:

private bool ScanForInstance (T instance)
where T: class
{
var id = IdResolver.ResolveObject(instance);
using (var session = SessionFactoryFactory.GetSessionFactory( ).OpenStatelessSession())
{
return session.Get(id) != null;
}
}

IdResolver is used to determine Should be used for the content of the id (the value of a single key in the map, otherwise the object itself of an entity with a composite id).

Like I said, if I delete the call to Get it works fine. It also Applies to all other operations (create, read, and delete). All operations (including updates) apply to entities with a single key.

The database is Pervasive and has a certain number of restrictions:

>No, I can't change any architecture (I think this is a frequent response to FNB questions).
>I don't want to delete and then insert because there are some columns that we haven't synced back to our system, and I don't want to erase These

updates: I added a simple example where people can copy/paste to test this strange behavior (if it is actually universal). I hope people can at least confirm mine by doing this Question.

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
{
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 it’s called'ParentType'. I don’t actually have any other mappings, and I don’t actually use that type as the parent type in this example. The reason I’m calling it is because I’m about to open another A problem involving composite ID and inheritance issues (don't use composite ID! :-D).

For the actual test, I just created a console project in VS as Program.cs:

static void Main (string[] args)
{
var smFactory = Fluently.Configure()
.Database(() => new OdbcPersistenceConfigurer()
.Driver()< br /> .Dialect()
.Provider()
.ConnectionString(BuildSMConnectionString())
.ProxyFactoryFactory(typeof(NHibernate.ByteCode.Castle.ProxyFactoryFactory))
.UseReflectionOptimizer()
.UseOuterJoin())
.Mappings
(m =>
m.FluentMappings.Add()
) ;

var sessionFactory = smFactory.BuildSessionFactory();

var updatedInstance = new ParentType
{
AssignedId = 1,
GeneratedId = 13,
SomeField = "UPDATED"
};

bool exists;

using (var session = sessionFactory.OpenStatelessSession())
{
exists = session.Get(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
{

}

I know that adding this example is only slightly useful, because any Anyone who wants to test it needs to change the ParentType field to match the table they already have in their DB, or add a table to match the content mapped in the ParentType. I hope someone will do this, at least out of curiosity, because I Already gave a good start to the test.

Well, I at least found a solution to the problem, but not why. My solution is to create A new type that contains the attributes that I use as composite id:

public class CompositeIdType
{
public virtual long AssignedId {get; set; }

public virtual lon g GeneratedId {get; set; }

public override bool Equals(object obj)
{
return Equals(obj as CompositeIdType);
}

private bool Equals(CompositeIdType other)
{
if (ReferenceEquals(this, other)) return true;
if (ReferenceEquals(null, other)) return false;

return AssignedId == other.AssignedId &&
GeneratedId == other.GeneratedId;
}

public override int GetHashCode()
{< br /> unchecked
{
int hash = GetType().GetHashCode();

hash = (hash * 31) ^ AssignedId.GetHashCode();
hash = (hash * 31) ^ GeneratedId.GetHashCode();

return hash;
}
}
}

Then, change The properties in ParentType are replaced with references to this new type:

public class ParentType
{
public virtual CompositeIdType Key {get; set; }< br />
public virtual string SomeField {get; set; }
}

With these changes, the new mapping will be:

public class ParentMap: ClassMap
{
public ParentMap()
{
Table("STANDARDTASKITEM");

CompositeId(x => x.Key)
.KeyProperty(x => x.AssignedId, "STANDARDTASK")
.KeyProperty(x => x.GeneratedId, "STANDARDTASKITEM");

Map(x => x.SomeField, "DESCRIPTION");

Not.LazyLoad();
}
}

After completing all these changes, even after calling Merge Before calling Get, Merge also works normally. My best choice is that the non-generic form of CompositeId does not perform certain operations correctly, or when you call Merge on the entity that uses it, the mapping it is making is consistent with NH Incompatibility (I want to enter the source code if this is the case, FNH wants to solve it, but I have spent too much time figuring out how to bypass this problem).

It's all good but This requires me to create a new type for each entity I map, or at least a new type for ids with a different number of keys (i.e. a type with 2 keys and a type with 3 types) keys Etc.).

To avoid this, I can hack it so that you add a reference of the same type as the mapping and set the reference to this in the constructor:

< /p>

public class ParentType
{
public ParentType()
{
Key = this;
}

public virtual ParentType Key { get; set; }

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;< br />
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;
}
}
}

Then map Will be:

public class ParentMap: ClassMap
{
public ParentMap()
{
Table("STANDARDTASKITEM");

CompositeId(x => x.Key)
.KeyProperty(x => x.AssignedId, "STANDARDTASK")
.KeyProperty(x => x.GeneratedId, "STANDARDTASKITEM");

Map(x => x.SomeField, "DESCRIPTION");

Not.LazyLoad();
}
}

I have tested this for updates and inserts using Merge with get to be called before merging and it is surprisingly IT work. I am still in the scope of using repair (including composite id or self-reference New type), because self-referencing seems a bit clumsy to my taste.

If anyone finds out why this doesn’t work, I still want to know...

Leave a Comment

Your email address will not be published.