0

While trying to save an object along with its children, NHibernate throws

System.ObjectDisposedException: 'Cannot access a disposed object.'

when the tx.Commit() is called.

Strangely data is still persisted into the database tables.

My NHibernate session is bound with web request via

    if (!CurrentSessionContext.HasBind(sessionFactory))
        CurrentSessionContext.Bind(sessionFactory.OpenSession());

I'm using ASP.NET MVC 5, NHibernate 4.1, (Not using fluent NHibernate though)

Below is the code that produces the error:

Code:

    ISession session = SessionFactory.GetCurrentSession();            
    session.SetBatchSize(1000);
    using (var tx = session.BeginTransaction())
    {
        try
        {
            IList<Bar> barList = session.QueryOver<Bar>().Where(b => b.IsEnabled == true).List();

            foreach (Bar bar in barList)
            {
                var foo = new Foo { BarObj = bar };

                foo.BarDetailList = new HashSet<BarDetail>();
                foreach (Alpha alpha in bar.AlphaList)
                {
                    foreach (Beta beta in alpha.BetaList)
                    {
                        foo.BarDetailList.Add(
                            new BarDetail { ParentFoo = foo, AlphaObj = alpha, BetaObj = beta }
                        );
                    }
                }

                session.Persist(foo); // Save gives same result
            }
            tx.Commit(); // This line throws the ObjectDisposedException
        }
        catch (Exception ex)
        {
            // details omitted
            tx.Rollback();
        }
    }

Class Definitions:

public class BarDetail : AuditableModelBase
{
    public virtual Foo ParentFoo { get; set; }
    public virtual Alpha alpha { get; set; }
    public virtual Beta beta { get; set; }
    public virtual IList<BarMonthlyDetail> MonthlyDetailList { get; set; }
    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        var barDetail = obj as BarDetail;
        if (barDetail == null)
            return false;

        if (barDetail.Id.HasValue && barDetail.Id == this.Id)
            return true;
        else
        {
            // If id is null, then look for other members used when adding 
            // BarDetail objects as child objects at the time of creation of forecast
            // otherwise they wont get added
            if (this.alpha != null && barDetail.alpha != null && this.alpha.Id == barDetail.alpha.Id
                && this.beta != null && barDetail.beta != null && this.beta.Id == barDetail.beta.Id)
                return true;

        }

        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;

            hash = hash * 23 + Id.GetHashCode();
            return hash;
        }
    }
}

public class BarMonthlyDetail : AuditableModelBase
{
    public virtual BarDetail ParentBarDetail { get; set; }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        var barMonthlyDetail = obj as BarMonthlyDetail;
        if (barMonthlyDetail == null)
            return false;

        if (barMonthlyDetail.Id == this.Id)
            return true;

        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;

            hash = hash * 23 + Id.GetHashCode();
            return hash;
        }
    }
}

public class Bar : AuditableModelBase
{
    public Bar()
    {
    }

    public virtual ISet<UserAccount> Users { get; set; }
    public virtual ISet<Alpha> alphas { get; set; }
    public virtual ISet<Beta> betas { get; set; }

    public override string ToString()
    {
        return Name ?? "";
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        var bar = obj as Bar;
        if (bar == null)
            return false;

        if (bar.Id == this.Id)
            return true;

        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;

            hash = hash * 23 + Id.GetHashCode();
            //hash = hash * 23 + (Name ?? "").GetHashCode();
            //hash = hash * 23 + (Code ?? "").GetHashCode();
            return hash;
        }
    }
}

public class Foo : AuditableModelBase
{        
    public virtual Bar bar { get; set; }
    public virtual IList<BarDetail> BarDetailList { get; set; }

    public override string ToString()
    {
        return Name ?? "";
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        var foo = obj as Foo;
        if (foo == null)
            return false;

        if (foo.Id == this.Id)
            return true;

        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;

            hash = hash * 23 + Id.GetHashCode();
            return hash;
        }
    }
}

public class Alpha : AuditableModelBase
{
    public virtual ISet<Bar> bars { get; set; }
    public virtual ISet<Beta> betas { get; set; }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        var alpha = obj as Alpha;
        if (alpha == null)
            return false;

        if (alpha.Id == this.Id)
            return true;

        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;

            hash = hash * 23 + Id.GetHashCode();
            return hash;
        }
    }
}

public class Beta : AuditableModelBase
{

    public virtual Bar bar { get; set; }
    public virtual Alpha alpha { get; set; }

    public override string ToString()
    {
        return Name ?? "";
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        var beta = obj as Beta;
        if (beta == null)
            return false;

        if (beta.Id == this.Id)
            return true;

        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;

            hash = hash * 23 + Id.GetHashCode();
            return hash;
        }
    }
}

Mappings:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Core" namespace="DataAccess.Models">
  <class name="BarDetail" table="BarDetail">
    <id name="Id">
      <generator class="identity"/>
    </id>
    <property name="IsEnabled" not-null="true"/>
    <many-to-one name="ParentFoo" class="Foo" column="FooId" not-null="true"/>
    <many-to-one name="alpha" class="Alpha" column="AlphaId" not-null="true"/>
    <many-to-one name="beta" class="Beta" column="BetaId" not-null="true"/>    
    <bag name="MonthlyDetailList" table="BarMonthlyDetail" lazy="true" cascade="save-update" order-by="Month asc" >
      <key>
        <column name="BarDetailId" not-null="true"/>
      </key>
      <one-to-many class="BarMonthlyDetail"/>
    </bag>
  </class>
</hibernate-mapping>

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Core" namespace="DataAccess.Models">
  <class name="BarMonthlyDetail" table="BarMonthlyDetail">
    <id name="Id">
      <generator class="identity"/>
    </id>
    <property name="Month" />
    <many-to-one name="ParentBarDetail" class="BarDetail" column="BarDetailId" not-null="true"/>
  </class>
</hibernate-mapping>

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Core" namespace="DataAccess.Models">
  <class name="Foo" table="Foo">

    <id name="Id">
      <generator class="identity"/>
    </id>

    <many-to-one name="bar" class="Bar" column="BarId" not-null="true"/>

    <bag name="BarDetailList" table="BarDetail" lazy="true" cascade="save-update">
      <key>
        <column name="FooId" not-null="true"/>
      </key>
      <one-to-many class="BarDetail"/>
    </bag>

  </class>
</hibernate-mapping>

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Core" namespace="DataAccess.Models">
  <class name="Beta" table="Beta">
    <id name="Id">
      <generator class="identity" />
    </id>
    <property name="IsEnabled" not-null="true"/>
    <many-to-one name="bar" class="Bar" column="BarId" not-null="true" />
    <many-to-one name="alpha" class="Alpha" column="AlphaId" not-null="true" />
  </class>
</hibernate-mapping>

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Core" namespace="DataAccess.Models">
  <class name="Alpha" table="Alpha">
    <id name="Id">
      <generator class="identity" />
    </id>
    <set name="alphas" table="AlphaBar" lazy="true">
      <key column="AlphaId" not-null="true"/>
      <many-to-many class="Bar" column="BarId"/>
    </set>
    <set name="betas" table="Beta" lazy="true" inverse="true">
      <key column="BarId" not-null="true"/>
      <one-to-many class="Beta" />
    </set>
  </class>
</hibernate-mapping>

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Core" namespace="DataAccess.Models">
  <class name="Bar" table="Bar">
    <id name="Id">
      <generator class="identity" />
    </id>
    <set name="Users" table="UserBar" inverse="true" lazy="true">
      <key column="BarId"/>
      <many-to-many class="UserAccount" column="UserId"/>
    </set>
    <set name="alphas" table="AlphaBar" inverse="true" lazy="true">
      <key column="BarId"/>
      <many-to-many class="Alpha" column="AlphaId"/>
    </set>
    <set name="betas" table="Beta" lazy="true" inverse="true">
      <key column="BarId" not-null="true"/>
      <one-to-many class="Bar" />
    </set>
  </class>
</hibernate-mapping>

I tried to look for similar SO posts (e.g. This)and or web but this problem seems to be different

Update: I have also tried by setting flush mode explicitly session.FlushMode = FlushMode.Commit; previously, it was set to Auto but error persists

Muhammad Ali
  • 330
  • 3
  • 13
  • Your `try catch` is unneeded and even harmful (swallow exception). The `using` is enough. If not committed, the transaction will be rollbacked. You are maybe hiding the actual error with it. Try again without it. – Frédéric Jul 10 '17 at 20:05
  • Unnecessary code is trimmed in the `catch` block. I am doing logging and in some cases converting the exception into another in it. I have updated the catch block. – Muhammad Ali Jul 11 '17 at 13:20
  • Are you sure your `ObjectDisposedException` is not coming from your catch block? You have not included the stack trace, you have not told which line throws it. – Frédéric Jul 11 '17 at 15:01
  • Edited and mentioned it. Basically the line `tx.Commit()` in try block triggers this exception. Also added class definitions and mappings. Btw Thank you! for your continued responses. – Muhammad Ali Jul 12 '17 at 07:25
  • Is it the transaction itself which is already disposed? That would means there are really more ongoing than what your code show. But without the stack trace and a [mcve], we cannot knows. You really need to provide a [mcve], something as tiny as possible, but still enough we can easily put it together to get run-able code demonstrating your issue, without having missing parts. – Frédéric Jul 12 '17 at 11:09

1 Answers1

0

I got rid of the error. I was using a Set when I changed it to Bag and based my GetHashCode() method only on Id, it started working fine.

Muhammad Ali
  • 330
  • 3
  • 13
  • In most cases `set` better models your data than `bag`, better keep it. So better find the actual cause of your trouble. Why was `GetHashcode` based on something else than `Id`? It is suspicious. It is an error if those others things are mutable. A hashcode must not change once computed. – Frédéric Jul 10 '17 at 20:10
  • Agreed. But how about adding multiple objects at the same time? `foo.BarDetailList` cannot add multiple new objects `GetHashCode` is just based on object's Id. Do you have a better solution in this situation? – Muhammad Ali Jul 11 '17 at 13:22
  • There is no reason for a set to refuse receiving many different objects. There must be another trouble in your code.What you show in your question is not a [mcve], I do not think anyone can reproduce your trouble and sort it out currently. Try providing a [mcve]. – Frédéric Jul 11 '17 at 15:04
  • But how would a `Set` can add multiple new element when their `Id`s are `null` and `Hashcode` and `Equals` are only based on `Id`? I have added class definitions and mappings for all the classes involved. Kept suspicious ones on the top though. BTW Thank you! for your continued responses. – Muhammad Ali Jul 12 '17 at 07:24
  • You need a special hashcode and equals handling when the instance is transient. And either store the computed hashcode for yielding the same back after having flushed, or discard the session and its entities for starting afresh with were-not-transient entities. The later is safer if the code causes many sessions to interact with the same entity instances. Otherwise the "was-not-transient" entity may be in presence of another instance of itself loaded from another session, having a different hashcode while being considered equal. See [here](/q/5686604/1178314) or [here](/q/11262747/1178314). – Frédéric Jul 12 '17 at 11:04