2

让我从我当前的设置开始,然后解释我想要实现的目标。我们正在使用 NHibernate 并尝试使用 Ninject 实现 IRepository/IUnitOfWork 模式。理想情况下,它应该适用于使用代码的任何应用程序,无论是 ASP.Net、WCF 还是其他东西。

工作单位

public interface IUnitOfWork
{
    object Add(object obj);//all other supported CRUD operations we want to expose
    void Commit();
    void Rollback();
}

工作单位

public class UnitOfWork : IUnitOfWork
{
    private readonly ISessionFactory _sessionFactory;
    private readonly ISession _session;
    private readonly ITransaction _transaction;

    public UnitOfWork(ISessionFactory sessionFactory)
    {
        _sessionFactory = sessionFactory;
        _session = _sessionFactory.OpenSession();
        _transaction = _session.BeginTransaction();
    }

    public object Add(object obj)
    {
        return _session.Save(obj);
    }

    public void Commit()
    {
        if(!_transaction.IsActive)
        {throw new Exception("some error");}
        _transaction.Commit();
    }

    public void Rollback()
    {
        if (!_transaction.IsActive)
        {
            throw new Exception("some other error");
        }
        _transaction.Rollback();
    }
}

存储库

public interface IRepository<TEntity, TId> where TEntity : class
{
    TId Add(TEntity item);//add other missing CRUD operations
}

通用存储库

public class GenericRepository<TEntity, TId> : IRepository<TEntity, TId>
    where TEntity : class
{
    public TId Add(TEntity item)
    {
        throw new NotImplementedException();
    }
}

我使用 Ninject 作为我的 IOC 容器。目标是在创建 UnitOfWork 的生命周期中重用相同的 IUnitOfWork。无论调用应用程序是什么,我都希望实现的生命周期能够正常工作,否则我会像大多数在线建议一样使用 InRequestScope。我能够做这样的事情:

//constructor
public MyService(IUnitOfWork uow, IRepository<User, int> userRepo, IRepository<Cat, int> catRepo)
{
    _uow = uow; _userRepo = userRepo; _catRepo = catRepo;
}

//method in same class
public void DoSomeWork()
{
    _userRepo.Add(someUser);
    _catRepo.Add(someCat);
    _uow.Commit();

    //rollback on error
}

我的绑定设置如下:

Bind<IUnitOfWork>.To<UnitOfWork>().InCallScope();
Bind(typeof(IRepository<,>)).To(typeof(GenericRepository<,>));

而且这个绑定配置实际上适用于上述MyService情况,它将在构造函数中创建一次 UnitOfWork,并且它也会为 IRepo impls 使用相同的 UnitOfWork,无论它们实际上可能是多少层。

但我希望能够将 IUnitOfWork 完全隐藏在应用程序之外。我宁愿提供一些可以放置在方法之上的 TransactionAttribute,它将在入口处创建 IUnitOfWork,并且同一实例将被注入到 TransactionAttribute 范围内对 IUnitOfWork 的所有未来请求。它会相应地处理提交和回滚。所以之前的代码会变成这样:

//constructor
public MyService(IRepository<User, int> userRepo, IRepository<Cat, int> catRepo)
{
    _uow = uow; _userRepo = userRepo; _catRepo = catRepo;
}

//method in same class
[Transaction]
public void DoSomeWork()
{
    _userRepo.Add(someUser);
    _catRepo.Add(someCat);
}

有什么我可以做的绑定设置可以让我用这样的 [Transaction] 标记方法吗?我愿意对 IUnitOfWork 和 IRepository 的东西进行一些小的重组,服务层代码只是废代码,所以我可以在那里非常灵活。

4

1 回答 1

0

首先,注入不起作用

[Transaction]
public void DoSomeWork()
{
      _userRepo.Add(someUser);
      _catRepo.Add(someCat);
}

毕竟容器不知道你要调用什么样的方法,什么时候调用。

更进一步,因为您希望它与 WebApps、WCF、.. 可能是一些石英工作或其他工作:您必须决定是否要将IUnitOfWork/ Repository/ NHibernateSession的生命周期与对象的生命周期联系起来使用它(如MyService)或者如果您希望服务寿命更长(例如单例)并Session为方法调用创建一个新的 NHibernate,例如:

[Transaction]
public void DoSomeWork()
{
    _userRepo.Add(someUser);
    _catRepo.Add(someCat);
}

这实际上可以通过AOP实现,使用FodyPostSharp。或者,您也可以考虑使用装饰器模式来实现这一点(例如参见此处)。但是,如果我没记错的话,ninject 目前缺乏一些支持 easz 装饰器处理的细节。

或者,[Transaction]您可以从显式控制所有内容开始:

public interface IUnitOfWork
{
    IUnitOfWorkSession Begin(); // starts a session and transaction
}

public interface IUnitOfWorkSession : IDisposable
{
   void Commit();
   void Dispose(); // performs rollback in case no commit was performed
}

由于您将需要访问Session其他对象中的休眠状态,例如_userRepo并且_catRepo您还需要一种方法来Session独立于容器访问那里,因为它与实例化对象的方式/时间无关。当然,您可以像这样传递它:

public void DoSomeWork()
{
    using(IUnitOfWorkSession session = _unitOfWork.Begin())
    {    
        _userRepo.Add(session, someUser);
        _catRepo.Add(session, someCat);
        session.Commit();
    }
}

但这并不是很酷。因此,您将需要使用类似 a 的东西ThreadLocal(如果您使用的是 async/await,这可能会出现问题)或SynchronizationContext-local 存储。

现在,如果您已经做到了这一点,您可以考虑做 AOP。AOP 将有两个处理两件事:

  • 访问 IUnitOfWork。
    • 可以通过将其编织为额外的 ctor 参数来完成
    • 或者它可以检查是否已经有一个,如果有,它可以使用这个,如果没有,它可以抛出或编织参数......
  • 使用与上面相同的代码包装装饰方法:
public void DoSomeWork()
{
    using(IUnitOfWorkSession session = _unitOfWork.Begin()) // weave this
    {    
        _userRepo.Add(session, someUser);
        _catRepo.Add(session, someCat);
        session.Commit(); // weave this
    }
}
于 2014-10-31T12:23:19.693 回答