ninject inject iunitofwork to repository scoped attribute

风流意气都作罢 提交于 2020-01-04 11:08:14

问题


Let me start with my current setup, and then explain what I am trying to achieve. We are using NHibernate and trying to implement the IRepository/IUnitOfWork pattern with Ninject. It should ideally work generically for whatever application is using the code, whether is ASP.Net, WCF or something else.

IUnitOfWork

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

UnitOfWork

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();
    }
}

IRepository

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

GenericRepository

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

I am using Ninject as my IOC container. The goal is to reuse the same IUnitOfWork for the life cycle of the creating of the UnitOfWork. I want the life cycle that is implemented to work no matter what the calling application is, or else I would have just used InRequestScope like most suggestions online. I was able to do something like this:

//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
}

And my bindings are set up like:

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

And this binding configuration actually works for the above MyService, it will create the UnitOfWork once in the constructor and it will use that same UnitOfWork for the IRepo impls as well, no matter how many layers down they may actually be.

But what I would like to be able to do is hide the IUnitOfWork away from the applications altogether. I would rather provide some TransactionAttribute that can be placed on top of a method and it will create the IUnitOfWork on entry and that same instance will be injected to all any future requests for an IUnitOfWork within the scope of the TransactionAttribute. And it would take care of committing and rollingback accordingly. So the previous code would become something like this:

//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);
}

Is there any kind of binding setup I can do that will enable me to mark a method with a [Transaction] like this? I am open to some minor restructuring of the IUnitOfWork and IRepository stuff, and the Service layer code is just scrap code so I can be very flexible there.


回答1:


First of, injecting won't work with

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

after all, the container can't know what kind of method you're going to call and when you're going to call it.

Taking things further, since you want it to work with WebApps, WCF,.. maybe some quartz job or what not: You'll have to decide whether you want to tie the lifetime of an IUnitOfWork / Repository / NHibernate Session to the lifetime of the objects which use it (like MyService) or if you want a service to live longer (for example a singleton) and create a new NHibernate Session for for a method call like:

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

This could actually probably be achieved by AOP, either using Fody or PostSharp. Alternatively you could also look into using the decorator pattern to achieve this (see for example here). However if i remember correctly ninject currently lacks some of the niceties to support easz decorator handling.

Alternatively to the [Transaction] attribute you could start with controlling everything explicitly:

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
}

Since you will need to access the nhibernate Session in other objects, like _userRepo and _catRepo you'll also need a way to access the Session there independent from the container, since it got nothing to do with how/when you instantiate the objects. Of course, you could pass it along like:

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

But that's not really cool. So instead you will need to use something like a ThreadLocal (if you're using async/await this could be problematic) or otherwise a SynchronizationContext-local storage.

Now if you've managed to get this far you can look into doing AOP. AOP would have two handle two things:

  • getting access to the IUnitOfWork.
    • could be done by weaving it as additional ctor argument
    • or it could check if there is already one, if there is, it could use this one, if not, it could either throw or weave the argument in...
  • wrapping the decorated method like with the same code as above:
public void DoSomeWork()
{
    using(IUnitOfWorkSession session = _unitOfWork.Begin()) // weave this
    {    
        _userRepo.Add(session, someUser);
        _catRepo.Add(session, someCat);
        session.Commit(); // weave this
    }
}


来源:https://stackoverflow.com/questions/26665351/ninject-inject-iunitofwork-to-repository-scoped-attribute

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!