你可以在NHibernate的一个会话中执行多个事务吗?这样做是否明智?

6

我考虑为NHibernate持久化层编写自己的IUnitOfWork实现。

正确的做法似乎是在构造函数中实例化ISession和ITransaction,然后在析构函数或Dispose()方法中进行处理。

当然,如果有人调用Save()方法,则ISession将被刷新,ITransaction将被完成,因此在调用Save()之后,将没有一个有效的打开事务来再次保存,除非我提交第一个事务并立即打开另一个新事务。但这是个好主意吗?

从设计上讲,执行一次提交操作是有意义的,但我可能无法控制代码,其他开发人员可能不太遵循UnitOfWork模式。

如果尝试使UnitOfWork能够容忍每个会话中的多个事务,是否会失去/获得任何东西?还是应该检查是否存在打开的事务,并在已提交时抛出异常,而不是创建新事务?


它正在为一个网络应用程序进行设计。 - Jeremy Holovacs
3个回答

11

回答第一个问题:是的,可以在一个会话中有多个事务。

是否明智?这取决于情况。

问题在于,在第一个事务中更改数据将被提交,但不确定整个工作单元(会话)是否最终提交。当您在以后的事务中获得StaleObjectException时,您已经提交了一些数据。请注意,这种异常使您的会话无法使用,您必须销毁它。然后很难重新开始并重试。

我想说,在以下情况下运行良好:

  • 这是一个UI应用程序
  • 更改仅在最后一个事务中刷新。

UI应用程序

用户交互地处理错误。这意味着用户可以看到在出现错误时实际存储的内容,并重复他所做的更改。

更改仅在最后一个事务中刷新

NH所实现的会话仅在结束或“必要时”刷新更改。因此,可以将更改保留在内存中,直到会话提交为止。问题在于NH需要在每次查询之前刷新会话,这很难控制。它可以关闭,这会导致副作用。在编写简单事务时,您可能可以控制它。在复杂系统中,几乎不可能确保没有出错。

The Simple Way(tm)

我编写了一个相当大的客户端 - 服务器系统的持久层。在这样的系统中,您没有用户直接处理错误。您需要在系统中处理错误,并以一致的状态将控制权返回给客户端。

我将整个事务处理简化到绝对最低限度,以使其稳定并“傻瓜式”。我始终创建会话和事务,并将其提交或未提交。


我的理解是,调用 ITransaction.Commit() 将会刷新会话。在使用同一会话实例化一个新的 ITransaction,并紧接着执行 Commit() 后,什么情况会导致 StaleObjectException 异常的出现? - Jeremy Holovacs
提交事务会释放锁定。这使得出现StaleObjectExceptions的可能性更大。但是,您仍然可能会遇到它们,因为在您在内存中更改数据时,数据库中的数据也可能发生变化。NH的乐观锁定至少可以检测到这些冲突。 - Stefan Steinegger

2

有多种选项可用于使用工作单元实现nhibernate嵌套事务。

在这里,我正在使用命令模式来进行工作单元。

public interface IAction
{    
    void Execute();
}

public abstract class Action<T> : IAction, IDisposable where T : Action<T>
{
    public void Execute()
    {
        try
        {
            //Start unit of work  by your unit of work pattern or
            transaction.Begin();
            OnExecute();
            //End Unit of work
            transaction.Commit();
        }
        catch (Exception e)
        {
            transaction.Rollback();
            throw e;
        }
    }

    protected abstract void OnExecute();

    public void Dispose()
    {

    }
}

public class MyBusinessLogic : Action<MyBusinessLogic>
{
    protected override void OnExecute()
    {
       //Implementation
    }
}

public class MyAnotherBusinessLogic : Action<MyAnotherBusinessLogic>
{
    protected override void OnExecute()
    {
        //Nested transaction
        MyBusinessLogic logic = new MyBusinessLogic();
        logic.Execute();
    }
}

只是指出几点:1)已经有一个System.Action<T>,这样会混淆名称。2)您的Action<T>类没有定义事务来自哪里。3)这仍然会导致与NHibernate中嵌套事务相同的错误(提交内部事务将使外部事务抛出ObjectDisposedException)。 - rossisdead

0

我认为每个工作单元只有一个事务的解决方案过于限制性。在某些环境中,一个会话可能需要执行多个事务的能力。我自己明确地管理事务,这似乎是一种灵活的解决方案。

public interface IUnitOfWork: IDisposable
{
    IGenericTransaction BeginTransaction();
}

public interface IGenericTransaction: IDisposable
{
    void Commit();

    void Rollback();
}

public class NhUnitOfWork: IUnitOfWork
{
    private readonly ISession _session;

    public ISession Session
    {
        get { return _session; }
    }

    public NhUnitOfWork(ISession session)
    {
        _session = session;
    }

    public IGenericTransaction BeginTransaction()
    {
        return new NhTransaction(_session.BeginTransaction());
    }

    public void Dispose()
    {
        _session.Dispose();
    }
}

public class NhTransaction: IGenericTransaction
{
    private readonly ITransaction _transaction;

    public NhTransaction(ITransaction transaction)
    {
        _transaction = transaction;
    }

    public void Commit()
    {
        _transaction.Commit();
    }

    public void Rollback()
    {
        _transaction.Rollback();
    }

    public void Dispose()
    {
        _transaction.Dispose();
    }
}

使用方法如下。它可以轻松地融入任何模式中。

public void Do(IUnitOfWork uow)
{
  using (var tx = uow.BeginTransaction()) {
    // DAL calls
    tx.Commit();
  }
}

这正是我们所做的,但在这种情况下它不起作用: 我猜在评论中不能进行代码编辑...但如果你在里面有另一个事务,它就不起作用了。但它应该可以。嵌套事务在数据库中实现是有很好的原因的。 - user2415376

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接