1

Today I was testing a couple of things on my domain model and I have realized the behavior wasn't what I expected.

I have tried to isolate the problem creating a simple Customers-Orders model.

This is my mapping.

Customer:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class xmlns="urn:nhibernate-mapping-2.2" name="NHVariousTests.Domain.Customer, NHVariousTests" table="Customers">
    <id name="Code" type="System.Guid">
        <column name="CustomerCode" />
        <generator class="guid.comb" />
    </id>
    <version name="Version" type="System.Int32" unsaved-value="0" access="backfield"">
        <column name="Version" not-null="true" />
    </version>
    <property name="Description" type="AnsiString">
          <column name="Description" not-null="false" />
    </property>   
    <set name="Orders" access="field.pascalcase-underscore" cascade="all-delete-orphan" inverse="true" lazy="true">
        <key column="CustomerCode" />
        <one-to-many class="NHVariousTests.Domain.Order, NHVariousTests" />
    </set>
  </class>
</hibernate-mapping>

Order:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class xmlns="urn:nhibernate-mapping-2.2" name="NHVariousTests.Domain.Order, NHVariousTests" table="Orders">
    <id name="Code" type="System.Guid">
        <column name="OrderCode" />
        <generator class="guid.comb" />
    </id>
    <version name="Version" type="System.Int32" unsaved-value="0" access="backfield">
        <column name="Version" not-null="true" />
    </version>
    <property name="Description" type="AnsiString">
        <column name="Description" not-null="false" />
    </property>   
    <many-to-one class="NHVariousTests.Domain.Customer, NHVariousTests" name="Customer">
        <column name="CustomerCode" not-null="true" />
    </many-to-one>
  </class>
</hibernate-mapping>

My Order class is very simple:

public class Order : EntityGuid
{
    public Order()
    {
    }

    public virtual string Description { get; set; }

    public virtual Customer Customer { get; set; }
}

and this is my Customer class:

public class Customer : EntityGuid
{
    public Customer()
    {
        this._Orders = new HashSet<Order>();
    }

    public virtual string Description { get; set; }

    #region Orders

    private readonly ICollection<Order> _Orders = null;

    public virtual ReadOnlyCollection<Order> Orders
    {
        get { return (new List<Order>(_Orders).AsReadOnly()); }
    }

    public virtual bool AddOrder(Order order)
    {
        if ((order != null) && (!this._Orders.Contains(order)))
        {
            order.Customer = this;
            this._Orders.Add(order);
            return (true);
        }
        return (false);
    }

    public virtual bool RemoveOrder(Order order)
    {
        if ((order != null) && (this._Orders.Contains(order)))
        {
            this._Orders.Remove(order);
            order.Customer = null;
            return (true);
        }
        return (false);
    }

    #endregion
}

in this first example I am using a private ICollection<Order> which is created as a HashSet<Order>.

If I run this code:

using (var session = sessionFactory.OpenSession())
{
    using (var tx = session.BeginTransaction())
    {
        var customer = new Domain.Customer() { Description = "ACME Ltd" };

        var order = new Domain.Order() { Description = "Coffee" };
        customer.AddOrder(order);

        order = new Domain.Order() { Description = "Milk" };
        customer.AddOrder(order);

        session.Save(customer);

        tx.Commit();
    }
}

I can see the INSERT of the customer and the two INSERT for the orders but I've got an UPDATE for the customer, as well.

enter image description here

If I change my Customer class switching to Iesi collections:

public class Customer : EntityGuid
{
    public Customer()
    {
        this._Orders = new Iesi.Collections.Generic.HashedSet<Order>();
    }

    public virtual string Description { get; set; }

    #region Orders

    private readonly ICollection<Order> _Orders = null;

    public virtual ReadOnlyCollection<Order> Orders
    {
        get { return (new List<Order>(_Orders).AsReadOnly()); }
    }

    public virtual bool AddOrder(Order order)
    {
        if ((order != null) && (!this._Orders.Contains(order)))
        {
            order.Customer = this;
            this._Orders.Add(order);
            return (true);
        }
        return (false);
    }

    public virtual bool RemoveOrder(Order order)
    {
        if ((order != null) && (this._Orders.Contains(order)))
        {
            this._Orders.Remove(order);
            order.Customer = null;
            return (true);
        }
        return (false);
    }

    #endregion
}

I have got the expected behavior:

enter image description here

This is my EntityGuid class:

public abstract class EntityGuid : EntityWithTypedId<Guid>, IAuditedEntity
{
    public EntityGuid()
    {
        this.CreatedDate = DateTime.Now;
        this.UpdatedDate = DateTime.Now;
        this.CreatedBy = "";
        this.UpdatedBy = "";
    }

    public virtual DateTime CreatedDate { get; set; }
    public virtual string CreatedBy { get; set; }

    public virtual DateTime UpdatedDate { get; set; }
    public virtual string UpdatedBy { get; set; }

    public virtual int Version { get; private set; }

    public virtual bool IsTransient()
    {
        return (EntityWithTypedId<Guid>.Equals(this.Code, default(Guid)));
    }

    public virtual bool IsTransient(EntityWithTypedId<Guid> obj)
    {
        return obj != null && Equals(obj.Code, default(Guid));
    }

}

Is there anyone who can help me to try and understand what's happening?

Regards.

If someone is really interested to have a look at the code. I've cleaned everything and make it as simple as possible. Can be downloaded here (NH_CollectionProblems).

I am using NHibernate 3.3.2.4000

LeftyX
  • 35,328
  • 21
  • 132
  • 193
  • Set dynamic-update="true" in the Customer mapping so that you can see what is being updated. I would also try changing the Orders return type to `IEnumerable`. – Jamie Ide Jan 08 '13 at 12:33
  • Thanks Jamie. It seems that the only thing that is being updated is the Version. If I use IEnumerable same thing happens. – LeftyX Jan 08 '13 at 16:58
  • Try removing the unnecessary collection initializer. that is do not set _Orders = null. How is the Version assigned? – Jamie Ide Jan 08 '13 at 17:12
  • Jamie: I've added my abstract class EntityGuid and have removed the initializer so that my member looks like this: `private ICollection _Orders = new HashSet();` but still the same problem. – LeftyX Jan 08 '13 at 17:28
  • Jamie: Surely has got something to do with the Version cause if I remove it everything works fine. – LeftyX Jan 08 '13 at 17:35
  • If you look at the mapping and the `abstract class EntityGuid` you will see. It's really weird. I've updated my question. Now the 2 classes are similar. If I only change the inizialization of the collection from `HashSet` to `Iesi.Collections.Generic.HashedSet` and everything works fine. – LeftyX Jan 09 '13 at 12:53
  • Jamie: I've attached a simple project to my question, just in case you want to have a look at my implementation. Thanks anyway. – LeftyX Jan 09 '13 at 16:06

2 Answers2

1

Either...

Roger
  • 1,944
  • 1
  • 11
  • 17
1

The behavior you're expecting is wrong, you should expect that modifying a collection on a versioned entity will update the version. This behavior is described in the documentation for mapping a collection which also describes how to disable it:

optimistic-lock (optional - defaults to true): Species that changes to the state of the collection results in increment of the owning entity's version. (For one to many associations, it is often reasonable to disable this setting.)

I did pull down the project and verified that changing this setting toggled the update being issued: false = no update, true (default) = update. However, I could not get the expected behavior using the Iesi HashedSet. I think it's a bug but I don't have more time to work on it now.

Jamie Ide
  • 48,427
  • 16
  • 81
  • 117