Reputation:
I have an NHibernate Repository. I want to call Update
method on both loaded and unloaded objects in the NHibernate ISession
.
But I get this exception which means I should call Merge
method instead of Update
.
A different object with the same identifier value was already associated with the session: 0adc76b1-7c61-4179-bb39-a05c0152f1a1, of entity: Eshop.Entities.Currency
How can I generalize my repository to avoid this exception?
Here is my generic repository:
public class NHibernateProvider : IDataProvider
{
#region Variables
private readonly object locker = new object();
private ISessionFactory sessionFactory;
private Configuration configuration;
private ITransaction transaction;
#endregion
#region Properties
private ISessionFactory SessionFactory
{
get
{
lock (locker)
{
if (Null.IsObjectNull(HttpContext.Current.Items["DataProvider"]))
{
configuration = new Configuration();
configuration.Configure();
HttpContext.Current.Items["DataProvider"] = sessionFactory = configuration.BuildSessionFactory();
HttpContext.Current.Items["DataProviderSession"] = sessionFactory.OpenSession();
}
return (HttpContext.Current.Items["DataProvider"] as ISessionFactory);
}
}
}
private ISession session;
private ISession Session
{
get
{
if (Null.IsObjectNull(HttpContext.Current.Items["DataProviderSession"]))
{
session = SessionFactory.OpenSession();
session.FlushMode = FlushMode.Auto;
HttpContext.Current.Items["DataProviderSession"] = session;
}
else
{
session = HttpContext.Current.Items["DataProviderSession"] as ISession;
}
return session;
}
}
#endregion
#region Methods
public T Get<T>(Guid ID)
{
return Session.Get<T>(ID);
}
public T Get<T>(Expression<Func<T, bool>> predicate)
{
return Session.Query<T>().Where(predicate).FirstOrDefault();
}
public IQueryable<T> GetAll<T>()
{
return Session.Query<T>();
}
public IQueryable<T> GetAll<T>(Expression<Func<T, bool>> predicate)
{
return Session.Query<T>().Where(predicate);
}
public IQueryable<T> GetAll<T>(Expression<Func<T, bool>> predicate, int currentPage, int pageSize
)
{
if (Session.Query<T>().Any(predicate))
{
return Session.Query<T>().Where(predicate).Skip<T>(currentPage*pageSize).Take(pageSize);
}
return new List<T>().AsQueryable();
}
public IQueryable<T> GetAll<T, TKey>(Expression<Func<T, bool>> predicate, int currentPage, int pageSize,
Expression<Func<T, TKey>> sortExpression)
{
if (Session.Query<T>().Any(predicate))
{
return
Session.Query<T>().Where(predicate).Skip<T>(currentPage*pageSize).Take(pageSize).OrderBy<T, TKey>(
sortExpression);
}
return new List<T>().AsQueryable();
}
public bool Exists<T>(Guid ID)
{
if (Null.IsNotObjectNull(Session.Get<T>(ID)))
{
return true;
}
return false;
}
public bool Exists<T>(Expression<Func<T, bool>> predicate)
{
return Session.Query<T>().Where(predicate).Any();
}
public void Update<T>(T targetObject, bool commit = true) where T:class
{
try
{
BeginTransaction();
Session.Update(targetObject);
CommitTransaction(commit);
}
catch (Exception)
{
RollBackTransaction();
throw;
}
}
public void Update<T>(IEnumerable<T> targetObjects, bool commit = true) where T : class
{
try
{
BeginTransaction();
foreach (var target in targetObjects)
{
Session.Update(target);
}
CommitTransaction(commit);
}
catch (Exception)
{
RollBackTransaction();
throw;
}
}
public void Insert<T>(T targetObject, bool commit = true)
{
try
{
BeginTransaction();
Session.Save(targetObject);
CommitTransaction(commit);
}
catch (Exception)
{
RollBackTransaction();
throw;
}
}
public void Insert<T>(IEnumerable<T> targetObject, bool commit = true)
{
foreach (T target in targetObject)
{
Insert<T>(target, false);
}
CommitTransaction(commit);
}
public void Delete<T>(T targetObject, bool commit = true)
{
try
{
BeginTransaction();
Session.Delete(targetObject);
CommitTransaction(commit);
}
catch (Exception)
{
RollBackTransaction();
throw;
}
}
public void Delete<T>(Guid targetID, bool commit = true)
{
try
{
BeginTransaction();
Session.Delete(Get<T>(targetID));
CommitTransaction(commit);
}
catch (Exception)
{
RollBackTransaction();
throw;
}
}
public void Delete<T>(Expression<Func<T, bool>> predicate, bool commit = true)
{
try
{
BeginTransaction();
if (Session.Query<T>().Any(predicate))
{
foreach (T element in Session.Query<T>().Where(predicate))
{
Session.Delete(element);
}
}
CommitTransaction(commit);
}
catch (Exception)
{
RollBackTransaction();
throw;
}
}
private void RollBackTransaction()
{
transaction.Rollback();
}
private void CommitTransaction(bool commit)
{
if (commit && transaction.IsActive )
{
transaction.Commit();
}
}
private void BeginTransaction()
{
if (Session.Transaction.IsActive == false)
{
transaction =Session.BeginTransaction();
}
}
#endregion
}
Upvotes: 4
Views: 6184
Reputation: 16389
The Merge
, Update
and SaveOrUpdate
are different.
Usually update() or saveOrUpdate() are used in the following scenario:
- the application loads an object in the first session
- the object is passed up to the UI tier
- some modifications are made to the object
- the object is passed back down to the business logic tier
- the application persists these modifications by calling update() in a second session
saveOrUpdate() does the following:
- if the object is already persistent in this session, do nothing
- if another object associated with the session has the same identifier, throw an exception
- if the object has no identifier property, save() it
- if the object's identifier has the value assigned to a newly instantiated object, save() it
- if the object is versioned by a or , and the version property value is the same value assigned to a newly instantiated object, save() it
- otherwise update() the object
and merge() is very different:
- if there is a persistent instance with the same identifier currently associated with the session, copy the state of the given object onto the persistent instance
- if there is no persistent instance currently associated with the session, try to load it from the database, or create a new persistent instance
- the persistent instance is returned
- the given instance does not become associated with the session, it remains detached
You should call Merge
if you want to attach a detached instance of entity to current session and persistent instance of the same (with same identifier) may already exists in current session. If you directly call Update
or SaveOrUpdate
on this entity, you may get NonUniqueObjectException
exception.
Looking at the exception you are getting, it is obvious that the persistent instance with same identifier already exists in session; you have to call Merge
for this specific case if you are willing to loose the entity already in session.
In the quote above, note that returned instance (by Merge
method) is the persistent instance; not the one which was passed as parameter.
How can I generalize my repository to avoid this exception?
Too broad to answer and also opinion based. I will avoid to generalize repository this way. In fact, I will avoid generic repository with NHibernate if possible. I will instead expose both Merge
and Update
methods and will leave it to the user to use the correct one; but as you can see, this minimizes the use of generic repository then. That is why, I will prefer to avoid it.
Another alternative is to handle the exception like below (Caution: Not safe; I do not recommend this):
try
{
nhSession.Update(instance);
}
catch(NonUniqueObjectException)
{
instance = nhSession.Merge(instance);
}
I do not recommend this because this actually may hide the actual issue in your code. In some cases, this may produce unexpected behavior. Your changes may be lost unexpectedly as your original entity in session will be overwritten.
As you said in your answer:
Found that, I should use Merge
Again, as I said above, I will not recommend using Merge
instead of Update
everywhere.
because merge will decide to merge or update the entity considering its state ( detached, persistent) in NHibernate session.
This may be benefit in some cases; but in other cases, this may create issues. I have explained this above.
Upvotes: 5
Reputation:
Found that, I should use Merge , because merge will decide to merge or update the entity considering its state ( detached, persistent) in NHibernate session.
Upvotes: 3