0

In my application i need to work with detached objects and have already implemented the reporitory pattern. the problems show up in the following code sample:

var shops = ShopRepository.GetAll();           //Session.Clear() called inside, CacheMode = CacheMode.Ignore
var employees = EmployeeRepository.GetAll();   //same here

// Case 1: Session.Save() works, Session.Merge() irgnores the changes

employees.First().WorksAt.Name = "le new Shop in Town";
EmployeeRepository.Save(employees.First());

// Case 2: Session.Merge() works, Session.Save() throws NonUniqueObjectException

employees.First().WorksAt = employees.Last().WorksAt;
EmployeeRepository.Save(employees.First());

The shops need to be loaded first, so the user may set the "WorksAt" - Property for employees via a ComboBox (or something comparable).

My expectation is, that nhibernate "forgets" all instances (and the underlying graph also) after calling Session.Clear(). That is not the case. I still keep getting the NonUniqueObjectException on Update() - operations. So, i'm not able to use SaveOrUpdate although it handles all kind of changes made to the object graph quite well.

Session.Merge() will only get half the job done:

no exceptions were thrown, new references were saved, but changes made to shop-properties don't get saved (because merge simply loads the "old" employee / shop objects from the cache and copies the employee-properties to the old instance)

What i've already tried:

  • Set cascade to "merge"
  • Disable 2nd level cache in configuration
  • Session.Lock(myObj, LockMode.None)
  • Selfmade semi-merge: reset all the references via reflection on Repository.Save() for the object returned by Session.Merge()
  • Override Equals() and GetHashCode() methods

The model contains ~ 50 entities, so (re)loading every referenced bo-instance within a session on saving / updating is not an option. Currently i am using nHibernate 3.3.0.4 and FluentNHibernate 1.3.

Is there a compact / elegant solution that covers up both cases? Maybe disabling NHibernate's internal id-tracking will solve the issues?

The mapping may look like this:

public class Shop
{
public virtual Int Id  { get; set; };
public virtual string Name  { get; set; };

public virtual ISet<Employee> AllEmployees{ get; set; }

  public Shop()
  {
    AllEmployees = new HashedSet<Employee>();
  }
}

public class ShopMap: ClassMap<Shop>
{
  public ShopMap()
  {
    Id(x => x.Id);
    Map(x => x.Name);
  }

  HasMany(c => c.AllEmployees).Cascade.None().NotFound.Ignore();
}

public class Employee
{
public virtual Int Id  { get; set; };
public virtual string FirstName  { get; set; };
public virtual string SureName  { get; set; };

public virtual Shop WorksAt{ get; set; }

  public Employee()
  {
  }
}

public class EmployeeMap: ClassMap<Employee>
{
  public EmployeeMap()
  {
    Id(x => x.Id);
    Map(x => x.FirstName);
    Map(x => x.SureName);

    References(c => c.WorksAt).Cascade.SaveUpdate().Fetch.Join().NotFound.Ignore();
  } 
}

Update

Here's the code of my Save() method inside the repository base-class:

public static void Save(T pSaveObj)
{
  using (ISession session = GetSession())
  {
    using (ITransaction trans = session.BeginTransaction())
    {          
      // session.Save(pSaveObj);
      session.Save(session.Merge(pSaveObj));                                         
      session.Flush();
      session.Clear();
    }
  }
}
Alex
  • 84
  • 6

1 Answers1

0

Well.. you are totally misusing NHibernate. Clear and Flush are almost never called on the session. Creating session just to execute save gives you 0 benefits from 1st level cache. Transaction is implicitly rolled back on dispose (if there was no commit), so you really do nothing.

If I read your intentions correctly, you would rather like to have code like this:

public static void Save(T pSaveObj)
{
  using (ISession session = GetSession())
  {
    using (ITransaction trans = session.BeginTransaction())
    {          
      session.SaveOrUpdate(pSaveObj);                                         
      trans.Commit()
    }
  }
}
  • I assume that pSaveObj is not attached to the session (if it is attached, then use session.Update)
  • It is still bad way to use NH
    • If you don't have very strong reason to use repository pattern, forget it and work with raw session object, it will save you thousand of problems

P.S. Look what NH is doing with a sql profiler. I find it a good way to learn.

pkmiec
  • 2,594
  • 18
  • 16
  • After some time invested working with this approach in real life applicaitons: pkmiec is totally right, really don't use Repository-Pattern (or detached business objects at all) when NH is involved. Use unit-of-work instead and add matching poco's to your design – Alex Apr 19 '18 at 11:29