Методы обновления и слияния Nhibernate
У меня есть хранилище NHibernate. Я хочу вызвать метод Update для загруженных и выгруженных объектов в сеансе nhibernate.
но у меня есть это исключение, которое означает, что я должен вызывать метод Merge вместо update. Как я могу обобщить свой репозиторий, чтобы избежать этого исключения?
другой объект с таким же значением идентификатора уже был связан с сеансом: 0adc76b1-7c61-4179-bb39-a05c0152f1a1, объекта: Eshop.Entities.Currency
Вот мой общий репозиторий:
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
}
1 ответ
Обнаружил, что я должен использовать Merge, потому что Merge решит объединить или обновить сущность, учитывая ее состояние (отсоединено, постоянно) в сеансе NHibernate.
В Merge
, Update
а также SaveOrUpdate
разные.
Обычно update() или saveOrUpdate() используются в следующем сценарии:
- приложение загружает объект в первом сеансе
- объект передается на уровень пользовательского интерфейса
- в объект внесены некоторые изменения
- объект передается обратно на уровень бизнес-логики
- приложение сохраняет эти изменения, вызывая update() во втором сеансе
saveOrUpdate() выполняет следующие действия:
- если объект уже является постоянным в этом сеансе, ничего не делать
- если другой объект, связанный с сеансом, имеет тот же идентификатор, генерировать исключение
- если у объекта нет свойства идентификатора, save() его
- если идентификатор объекта имеет значение, присвоенное вновь созданному объекту, save() его
- если для объекта используется версия с помощью или, и значение свойства версии совпадает с значением, присвоенным вновь созданному объекту, save() его
- в противном случае update() объект
и merge() очень разные:
- если существует постоянный экземпляр с тем же идентификатором, который в настоящее время связан с сеансом, скопируйте состояние данного объекта на постоянный экземпляр
- если в данный момент нет постоянного экземпляра, связанного с сеансом, попробуйте загрузить его из базы данных или создать новый постоянный экземпляр
- постоянный экземпляр возвращается
- данный экземпляр не становится связанным с сеансом, он остается отключенным
Вам следует позвонить Merge
если вы хотите прикрепить отдельный экземпляр объекта к текущему сеансу, и постоянный экземпляр того же самого (с тем же идентификатором) может уже существовать в текущем сеансе. Если вы позвоните напрямуюUpdate
или SaveOrUpdate
на этом объекте вы можете получить NonUniqueObjectException
исключение.
Глядя на полученное вами исключение, очевидно, что постоянный экземпляр с таким же идентификатором уже существует в сеансе; ты должен позвонитьMerge
для этого конкретного случая, если вы хотите потерять объект, уже находящийся в сеансе.
В приведенной выше цитате обратите внимание, что возвращенный экземпляр (автор Merge
метод) - это постоянный экземпляр; не тот, который был передан как параметр.
Как я могу обобщить свой репозиторий, чтобы избежать этого исключения?
Слишком широко, чтобы ответить, а также основано на мнении. Я не буду обобщать репозиторий таким образом. Фактически, я буду избегать универсального репозитория с NHibernate, если это возможно. Вместо этого я выставлю обаMerge
а также Update
методы и предоставит пользователю возможность использовать правильный; но, как видите, это сводит к минимуму использование универсального репозитория. Вот почему я предпочитаю избегать этого.
Другой альтернативой является обработка исключения, как показано ниже (Внимание: небезопасно, я не рекомендую это делать):
try
{
nhSession.Update(instance);
}
catch(NonUniqueObjectException)
{
instance = nhSession.Merge(instance);
}
Я не рекомендую этого делать, потому что это может скрыть реальную проблему в вашем коде. В некоторых случаях это может привести к неожиданному поведению. Ваши изменения могут быть неожиданно потеряны, поскольку исходный объект в сеансе будет перезаписан.
Как вы сказали в своем ответе:
Обнаружил, что я должен использовать Merge
Опять же, как я уже сказал выше, я не рекомендую использовать Merge
вместо того Update
везде.
потому что при слиянии будет решено объединить или обновить объект с учетом его состояния (отсоединенный, постоянный) в сеансе NHibernate.
В некоторых случаях это может быть полезно; но в других случаях это может создать проблемы. Я объяснил это выше.