我正在尝试找到在使用NHibernate的Web应用程序中处理事务的最佳解决方案。
我们使用IHttpModule,在HttpApplication.BeginRequest中我们打开一个新会话,然后用ManagedWebSessionContext.Bind(context,session)将它绑定到HttpContext;我们在HttpApplication.EndRequest上关闭和取消绑定会话。
在我们的Repository基类中,根据best practice,我们总是围绕我们的SaveOrUpdate,Delete,Get方法包装一个事务:
public virtual void Save(T entity)
{
var session = DependencyManager.Resolve<ISession>();
using (var transaction = session.BeginTransaction())
{
session.SaveOrUpdate(entity);
transaction.Commit();
}
}
但是如果你需要在某个地方进行交易,那么这不起作用。一个应用程序服务,包括对Save,Delete等的多个存储库调用。
所以我们尝试的是使用TransactionScope(我不想编写自己的事务管理器)。为了测试这是否有效,我使用外部TransactionScope,它不会调用.Complete()来强制回滚:
Repository Save():
public virtual void Save(T entity)
{
using (TransactionScope scope = new TransactionScope())
{
var session = DependencyManager.Resolve<ISession>();
session.SaveOrUpdate(entity);
scope.Complete();
}
}
使用存储库的块:
TestEntity testEntity = new TestEntity { Text = "Test1" };
ITestRepository testRepository = DependencyManager.Resolve<ITestRepository>();
testRepository.Save(testEntity);
using (var scope = new TransactionScope())
{
TestEntity entityToChange = testRepository.GetById(testEntity.Id);
entityToChange.Text = "TestChanged";
testRepository.Save(entityToChange);
}
TestEntity entityChanged = testRepository.GetById(testEntity.Id);
Assert.That(entityChanged.Text, Is.EqualTo("Test1"));
这不起作用。但对我来说,如果NHibernate支持TransactionScope,它会!发生的事情是数据库中根本没有ROLLBACK但是当testRepository.GetById(testEntity.Id);语句执行UPDATE与SET Text =“TestCahgned”被触发(它应该在BEGIN TRAN和ROLLBACK TRAN之间被触发)。 NHibernate从level1缓存读取值并向数据库发出UPDATE。不期望的行为!?根据我的理解,只要在NHibernate的范围内完成回滚,您还需要关闭和取消绑定当前会话。
我的问题是:有没有人知道使用TransactionScope和ManagedWebSessionContext做到这一点的好方法?
答案 0 :(得分:2)
我采取了非常类似的方法。在HttpModule中,我向sessionfactory请求新会话+在新请求进入时绑定它。但我也在这里开始事务。然后,当请求结束时,我只是解除绑定并尝试提交事务。
此外,我的基本存储库不会以任何方式进行会话 - 而是会要求当前会话,然后对会话执行一些工作。此外,我不会使用事务将任何内容包装在此基类中。相反,整个http请求只是一个工作单元。
这可能不适合您正在处理的项目,但我更喜欢这种方法,因为每个请求都会失败或成功作为单个原子单元。如果您对实际实施感兴趣,我有完整的博客文章here及源代码。
以下是此基本存储库的示例:
public abstract class NHibernateRepository<T> where T : class
{
protected readonly ISessionBuilder mSessionBuilder;
public NHibernateRepository()
{
mSessionBuilder = SessionBuilderFactory.CurrentBuilder;
}
public T Retrieve(int id)
{
ISession session = GetSession();
return session.Get<T>(id);
}
public void Save(T entity)
{
ISession session = GetSession();
session.SaveOrUpdate(entity);
}
public void Delete(T entity)
{
ISession session = GetSession();
session.Delete(entity);
}
public IQueryable<T> RetrieveAll()
{
ISession session = GetSession();
var query = from Item in session.Linq<T>() select Item;
return query;
}
protected virtual ISession GetSession()
{
return mSessionBuilder.CurrentSession;
}
}
答案 1 :(得分:1)
感谢您的回答!
是的,这是解决问题的一种简单而直接的方法。但我的问题是我想确保存在一个围绕存储库操作的事务,即使Web请求(其他类型的客户端)没有调用应用程序服务,存储库等,因此我希望周围有一个事务最低级别(例如session.Save)然后使用TransactionScope在需要时创建更长的事务。但是你的解决方案很简单,我喜欢它,mabye我将使用它,然后确保其他客户端也使用事务。
答案 2 :(得分:1)
交易生命周期应为:
using (TransactionScope tx = new TransactionScope())
{
using (ISession session1 = ...)
using (ITransaction tx1 = session.BeginTransaction())
{
...do work with session
tx1.Commit();
}
using (ISession session2 = ...)
using (ITransaction tx2 = session.BeginTransaction())
{
...do work with session
tx2.Commit();
}
tx.Complete();
}
答案 3 :(得分:1)
您实际上可以使用Session.Transaction.IsActive
检查交易是否处于活动状态。如果一个未激活,您可以创建一个。您还可以创建一个Transact
方法,自动为您完成大部分操作。这是一段摘录,主要来自NHibernate 3.0 Cookbook:
// based on NHibernate 3.0 Cookbook, Data Access Layer, pg. 192
public class GenericDataAccessObject<TId> : IGenericDataAccessObject<TId>
{
// if you don't want to new up your DAO per Unit-of-work you can
// resolve the session at the time it's accessed.
private readonly ISession session;
protected GenericDataAccessObject(ISession session)
{
this.session = session;
}
protected ISession Session { get { return session; } }
public virtual T Get<T>(TId id)
{
return Transact(() => Session.Get<T>(id));
}
protected virtual void Save<T>(T entity)
{
Transact(() => Session.Save(entity));
}
/// <summary>
/// Perform func within a transaction block, creating a new active transaction
/// when necessary. No error handling is performed as this function doesn't have
/// sufficient information to provide a useful error message.
/// </summary>
/// <typeparam name="TResult">The return type</typeparam>
/// <param name="func">The function wrapping the db operations</param>
/// <returns>The results returned by <c>func</c></returns>
protected TResult Transact<TResult>(Func<TResult> func)
{
// the null Transaction shouldn't happen in a well-behaving Session
// implementation
if (Session.Transaction == null || !Session.Transaction.IsActive)
{
TResult result;
// transaction rollback happens during dispose when necessary
using (var tx = Session.BeginTransaction())
{
result = func.Invoke();
tx.Commit();
}
return result;
// We purposefully don't catch any exceptions as if we were to catch
// the error at this point we wouldn't have enough information to describe
// to the user why it happened -- we could only describe what happened.
}
return func.Invoke();
}
protected void Transact(Action action)
{
Transact<bool>(() =>
{
action.Invoke();
return false;
}
);
}
}