0

I'm using Fluent Nhibernate and my relevant entities are:

public class Product
{
    public virtual int ID {get; set;}
    public virtual string Name {get; set;}
    public virtual Supplier Supplier {get; set;}
}

public class Supplier
{
    public virtual int ID {get; set;}
    public virtual string Name {get; set;}
    public virtual List<Product> Products {get; set;}
}

In the mapping the supplier has HasMany on the Products + Inverse + Cascade.All in order to save all the prodcuts at once. My Product Primary Key and thus the equality members is the Id which is generated with NH sequence.

I need to create a list of products to a new supplier. Well if I add the products to the list before they get the primary key from the DB, only the first product being added to the list because the ID is 0 for all the products so the equals method return true, and the list "thinks" it already has that product.

I can save the products one by one before adding to the supplier list so the will get the Id value for the data base. But that doesn't use the cascade ability.

Any creative suggestion will be welcome.

gdoron
  • 147,333
  • 58
  • 291
  • 367
  • why do you rely on id only when it comes to equality? – wiero Nov 20 '11 at 08:02
  • @wiero, Because it's my primary key in the DB. why? what would you suggest in those cases? – gdoron Nov 20 '11 at 08:06
  • 1
    hi i did some research and i found this question http://stackoverflow.com/questions/2719877/object-equality-in-context-of-hibernate-webapp and i think this is good approach, for me it was not so obvious than two object can be considered equal depending only on db id, for example product with same name and supplier with diferent id will be different. this is an interesting problem :) – wiero Nov 20 '11 at 09:26

2 Answers2

2

It's clear that you are intentionally breaking equality by doing a blind compare by Id.

I usually do not override Equals in my entities because it forces loading in some cases where it's not needed.

If you really want to do override Equals, you can do something like:

public override bool Equals(object obj)
{
    if (Id == 0)
        return ReferenceEquals(this, obj);
    return obj != null && Id == ((Entity)obj).Id;
}
Diego Mijelshon
  • 52,548
  • 16
  • 116
  • 154
  • +1 Thanks, what do you mean by: "it forces loading in some cases where it's not needed." – gdoron Nov 20 '11 at 12:58
  • If you compare two unloaded proxies, by default NHibernate will do so based on the Id, without going to the DB. If you override equality, it will load them. – Diego Mijelshon Nov 20 '11 at 13:37
  • Why does NH behave that way? Any way you take a risk if don't override the equality members. – gdoron Nov 20 '11 at 14:24
  • It's not something easy to explain in the comments, but it makes total sense once you understand how NH proxies work. And no, there's no risk in not overriding equality if you don't share entities between sessions. – Diego Mijelshon Nov 20 '11 at 14:54
  • Could you please be kind and give me a reference to a post that describes this behavior outside of comments scope? =) – gdoron Nov 20 '11 at 15:45
  • I would, but I have not written it yet :-) – Diego Mijelshon Nov 20 '11 at 19:09
  • I know it took me a time but I just remembered why I implemented the equality members. **Encapsulation** and **Separate of concerns**. Only the Entity is responsible of designing it PK and uniqueness. If you write a comparison: 'if (Entity.Code == ViewModel.Code && Entity.ID == ViewModel.Id)' you broke the encapsulation. so define the PKs as Not LazyLoad and save the proxies. sorry for the verbose. =) – gdoron Nov 24 '11 at 11:25
  • That's nice. But the PK is a Repository/DB/ORM concern, not a domain one. Equality between **entities** (not value objects) should be done via identity (i.e. they are the same object in memory). In other words, there's no reason why you would ever had two instances in the same context representing the same DB row; that's what the identity map is for. – Diego Mijelshon Nov 24 '11 at 14:58
  • That isn't always true; If I have an Entity X with Many Products and for complex documentation issue the X doesn't have "HasMany" to the Products, you want to query all the Products of X like this query: **session.Query().Where(p => x.Equals(p.X)** and here you go, same Entity twice. – gdoron Nov 24 '11 at 15:22
  • Nope, that's not true. I believe you haven't tried. From the docs: *For a particular persistence context, NHibernate guarantees that persistent identity is equivalent to CLR identity (in-memory location of the object).*. – Diego Mijelshon Nov 24 '11 at 16:24
  • I will check it next week at work but it sound that comparing Entity X with the entity proxy will never be the same reference. so comparing X == p.X will be always false, or when you compare with == it's no longer a proxy? can you please share a link to where you found it in the docs? – gdoron Nov 24 '11 at 19:08
  • Just search in the docs for that exact phrase. And your assumption is incorrect, you can NEVER, EVER get two different proxies/references for the same entity in the same session. – Diego Mijelshon Nov 24 '11 at 22:51
0

I dont know if I understood the question 100%, but anyhow:

We use a similiar approach where an Event has a list of Registrations. We do however, as you mentioned, save the registrations first before saving the actual event. This causes the N+1 problem when adding many registrations at once, but thats rarely the case for our scenario. Maybe that problem would go away if we used another id-generator such as HiLo? I dont know because we havent had the time to look into it.

When we delete a registration the cascade operation works successfully, and the registration collection of the event is properly updated.

Mattias
  • 684
  • 3
  • 7
  • 16
  • I don't know why, but Hilo supposed to get an id from the session before going to the real DB, but it's doesn't work with collections. – gdoron Nov 20 '11 at 13:11