NHibernate: How is identity Id updated when saving a transient instance?
Solution 1
As far as your question is concerned, whenever you flush your session is when your entity is persisted to the database. When saving your (new) entity, NHibernate generates the ID for you using the generator you provided.
Keep in mind that an Identity generator is not recommended (see this post by Ayende). When you use an Identity generator, your new entity is persisted to the database when you save, even if you don't flush to the database. The reason this happens is because NHibernate needs to provide you with an ID for the entity, which it can't do without doing a roundtrip to the database.
A better solution would be to use something like a Guid generator, or HiLo if you want 'normal' values. This way you can save your entity without actually having to do a database roundtrip, which allows you to do a lot more performance wise (batching comes to mind).
Solution 2
I'm not sure I understand your question. The actual saving to the database occurs when the session is flushed (e.g. by committing the transaction). Calling SaveOrUpdate() doesn't itself save the entity, it just informs the session that the entity is due to be saved when the session is flushed.
Assuming that the ID of the entity maps to an identity field in the database and that your mapping tells NHibernate that identity is set by the database, then the ID set by the database will be set as the entity's ID when it is saved.
Solution 3
Nhibernate will set the ID property of your entity just after SaveOrUpdate call.
bretddog
Hardware/Software: www.superuser.com Server, Network, PCs (Professional Admins) : www.serverfault.com Colors: RGB/Hex Chart, Earth Tones, Earth 2 Electronics: http://electronics.stackexchange.com/
Updated on June 04, 2022Comments
-
bretddog almost 2 years
If I use session-per-transaction and call:
session.SaveOrUpdate(entity)corrected:
session.SaveOrUpdateCopy(entity)..and entity is a transient instance with identity-Id=0. Shall the above line automatically update the Id of the entity, and make the instance persistent? Or should it do so on transaction.Commit? Or do I have to somehow code that explicitly?
Obviously the Id of the database row (new, since transient) is autogenerated and saved as some number, but I'm talking about the actual parameter instance here. Which is the business logic instance.
EDIT - Follow-up, of related problem.
Mappings:
public class StoreMap : ClassMap<Store> { public StoreMap() { Id(x => x.Id).GeneratedBy.Identity(); Map(x => x.Name); HasMany(x => x.Staff) // 1:m .Cascade.All(); HasManyToMany(x => x.Products) // m:m .Cascade.All() .Table("StoreProduct"); } } public class EmployeeMap : ClassMap<Employee> { public EmployeeMap() { Id(x => x.Id).GeneratedBy.Identity(); Map(x => x.FirstName); Map(x => x.LastName); References(x => x.Store); // m:1 } } public class ProductMap : ClassMap<Product> { public ProductMap() { Id(x => x.Id).GeneratedBy.Identity(); Map(x => x.Name).Length(20); Map(x => x.Price).CustomSqlType("decimal").Precision(9).Scale(2); HasManyToMany(x => x.StoresStockedIn) .Cascade.All() .Inverse() .Table("StoreProduct"); } }
EDIT2
Class definitions:
public class Store { public int Id { get; private set; } public string Name { get; set; } public IList<Product> Products { get; set; } public IList<Employee> Staff { get; set; } public Store() { Products = new List<Product>(); Staff = new List<Employee>(); } // AddProduct & AddEmployee is required. "NH needs you to set both sides before // it will save correctly" public void AddProduct(Product product) { product.StoresStockedIn.Add(this); Products.Add(product); } public void AddEmployee(Employee employee) { employee.Store = this; Staff.Add(employee); } } public class Employee { public int Id { get; private set; } public string FirstName { get; set; } public string LastName { get; set; } public Store Store { get; set; } } public class Product { public int Id { get; private set; } public string Name { get; set; } public decimal Price { get; set; } public IList<Store> StoresStockedIn { get; private set; } }
-
bretddog about 13 yearsYes, so you mean; after the code line transaction.Commit(), the Id property of the entity shall reflect that set to the database row? The reason I ask is that I trace the code (In my repository.SaveOrUpdate method), and the id of entity is 0 all the time, even after .Commit. So I wanted to confirm exactly WHEN it should be set.
-
Diego Mijelshon about 13 yearsIf your id is 0 after commit, you didn't map the generator correctly.
-
bretddog about 13 yearsThanks! that was good to get firmly confirmed. Then it must be related to my other problem stackoverflow.com/questions/4890123/…. I thought this Id-problem could be the cause of that one. But then I assume this is an effect, not a cause. Btw; By "generator", do you mean the standard entity-class mappings? or the ISessionFactory configuration?
-
Jamie Ide about 13 yearsChange Id(x => x.Id); to Id(x => x.Id).GeneratedBy.Identity();
-
bretddog about 13 yearsI did that (thought it was autogenerated by default, but not sure). But it still doesn't update the entity-Id.
-
bretddog about 13 yearsThat makes sense I guess, since the session scope could be long. But still my Id's are 0..
-
David about 13 yearsFluentNHibernate judging by the OP's tags.
-
David about 13 yearsCan we see your mapping class? I thought that the default for Fluent NH was to use the database's identity generation anyway.
-
Sly about 13 yearsUsually Id not gets set when Name attribute for the id property in mappings is not set. But Fluent does this by default I think
-
bretddog about 13 yearsYes I map with fluent: Id(x => x.Id).GeneratedBy.Identity(); Tried now also Id(x => x.Id).Column("Id").GeneratedBy.Identity(); but did not help.
-
bretddog about 13 years@David: Please see added mappings above. I also thought that was default, but tried both with same result.
-
James Gregory about 13 yearsIt is, if your Id property is an integer/long. We need to see the class definition really.
-
bretddog about 13 years@James: Yes, Id's are int. I added class definitions above.
-
bretddog about 13 yearsOk, I found the reason, though I don't really understand. Please see added answer.
-
bretddog about 13 yearsInteresting.. so if I save a collection by looping through a foreach calling Save on each item, every save will hit the database? Will the complete row be saved on session.Save?
-
Erik van Brakel about 13 yearsI'll have to double check that with the profiler, but it might very well be.
-
James Gregory about 13 yearsYou shouldn't be using
SaveOrUpdateCopy
unless you understand what it does, and that it's applicable to your situation; always useSaveOrUpdate
by default.SaveOrUpdate
will save or update your entity instance, and update the id property if it was transient.SaveOrUpdateCopy
will return a new object with the id changed; whatever object you called the method with will be unchanged.