NHibernate: How is identity Id updated when saving a transient instance?

10,790

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.

Share:
10,790
bretddog
Author by

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, 2022

Comments

  • bretddog
    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
    bretddog about 13 years
    Yes, 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
    Diego Mijelshon about 13 years
    If your id is 0 after commit, you didn't map the generator correctly.
  • bretddog
    bretddog about 13 years
    Thanks! 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
    Jamie Ide about 13 years
    Change Id(x => x.Id); to Id(x => x.Id).GeneratedBy.Identity();
  • bretddog
    bretddog about 13 years
    I did that (thought it was autogenerated by default, but not sure). But it still doesn't update the entity-Id.
  • bretddog
    bretddog about 13 years
    That makes sense I guess, since the session scope could be long. But still my Id's are 0..
  • David
    David about 13 years
    FluentNHibernate judging by the OP's tags.
  • David
    David about 13 years
    Can we see your mapping class? I thought that the default for Fluent NH was to use the database's identity generation anyway.
  • Sly
    Sly about 13 years
    Usually 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
    bretddog about 13 years
    Yes 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
    bretddog about 13 years
    @David: Please see added mappings above. I also thought that was default, but tried both with same result.
  • James Gregory
    James Gregory about 13 years
    It is, if your Id property is an integer/long. We need to see the class definition really.
  • bretddog
    bretddog about 13 years
    @James: Yes, Id's are int. I added class definitions above.
  • bretddog
    bretddog about 13 years
    Ok, I found the reason, though I don't really understand. Please see added answer.
  • bretddog
    bretddog about 13 years
    Interesting.. 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
    Erik van Brakel about 13 years
    I'll have to double check that with the profiler, but it might very well be.
  • James Gregory
    James Gregory about 13 years
    You shouldn't be using SaveOrUpdateCopy unless you understand what it does, and that it's applicable to your situation; always use SaveOrUpdate 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.