我应该在ASP.Net MVC 3应用程序中的哪个位置创建工作单元实例?

tco*_*ode 12 architecture unit-of-work repository-pattern asp.net-mvc-3

我已经在Stackoverflow上阅读了很多关于在包含业务层的ASP.Net MVC 3应用程序中使用工作单元模式的帖子.但是,关于这个话题,我仍然有几个问题,非常感谢人们给我的任何反馈.

我正在开发一个使用EF 4.1的ASP.Net MVC 3 Web应用程序.我将使用这个项目的存储库和工作单元模式,类似于在这个伟大的教程中使用它们的方式

我的项目的不同之处在于我还需要包含一个业务层(我的解决方案中的单独项目),以便为应用程序执行各种业务规则.上面提到的教程没有Business层,因此从控制器创建了一个Unit of Work类的实例

public class CourseController : Controller
{
    private UnitOfWork unitOfWork = new UnitOfWork();
Run Code Online (Sandbox Code Playgroud)

但是,我的问题是,如果我有业务层,我应该在哪里创建工作单元类的实例?

我个人认为它应该在我的控制器中创建,然后注入到业务层中,如下所示:

public class PeopleController : Controller
{
    private readonly IUnitOfWork _UoW;
    private IPersonService _personService;

    public PeopleController()
    {
        _UoW = new UnitOfWork();
        _personService = new PersonService(_UoW);
    }

    public PeopleController(IUnitOfWork UoW, IPersonService personService)
    {
        _UoW = UoW;
        _personService = personService;

    }

    public ActionResult Edit(int id)
    {
        Person person = _personService.Edit(id);
        return View(person);
    }

public class UnitOfWork : IUnitOfWork, IDisposable
{
    private BlogEntities _context = new BlogEntities();
    private PersonRepository personRepository = null;

    public IPersonRepository PersonRepository
    {
        get
        {

            if (this.personRepository == null)
            {
                this.personRepository = new PersonRepository(_context);
            }
            return personRepository;
        }
    }

    public void Save()
    {
        _context.SaveChanges();
    }


public class PersonService : IPersonService
{
    private readonly IUnitOfWork _UoW;

    public PersonService(IUnitOfWork UoW)
    {
        _UoW = UoW;
    }

    public Person Edit(int id)
    {
         Person person = _UoW.PersonRepository.GetPersonByID(id);
         return person;
    }

public class PersonRepository : IPersonRepository
{
    private readonly BlogEntities _context;

    public PersonRepository(BlogEntities context)
    {
        _context = context;
    }

    public Person GetPersonByID(int ID)
    {
        return _context.People.Where(p => p.ID == ID).Single();
    }
Run Code Online (Sandbox Code Playgroud)

我读过其他人说过,工作单元实例化不应该在Controller中,而应该在服务层中创建.我不太确定这种方法的原因是因为我的Controller可能必须在一个业务事务中使用几个不同的服务层,并且如果在每个服务中创建了工作单元实例,那么它将导致多个工作单元实例被创造,这违背了目的,即每个商业交易的一个工作单元.

也许我上面解释的是错误的,但如果是这样的话,如果有人能说得对,我会非常感激.

再次感谢你的帮助.

Ste*_*kes 18

我认为你有几个改变:

  1. 允许您的DI容器将UnitOfWork实例注入其构造函数中的Service类,并将其完全保留在Controller之外.

  2. 如果您的DI容器支持它(例如Ninject),UnitOfWork请根据请求配置您的管理; 通过这种方式,您的服务将UnitOfWork针对每个请求进行分配,并且您已完成所有操作.要么...

  3. 如果您的DI容器不支持每个请求的生命周期,请将其配置为管理UnitOfWork单个,这样每个Service类都会获得相同的实例.然后更新UnitOfWork其存储Entities对象,其中存储关于每个请求的基础的对象的数据存储中,例如在HttpContext.Current.Items如所描述的在这里.

编辑1

关于UnitOfWork应该注射的地方; 我说服务层是正确的地方.如果您将系统想象为一系列层,其中外层处理用户交互,而较低层处理数据存储,则每层应该更少关注用户并更关注数据存储.UnitOfWork是来自"低层"层之一的概念,而控制器来自更高层层; 你的Service图层适合它们.因此把它UnitOfWork放入Service课堂而不是课堂是有道理的Controller.

编辑2

详细说明UnitOfWork创作及其与以下内容的关系HttpContext.Current.Items:

UnitOfWork将不再拥有Entities对象的引用,这将通过HttpContext对象完成,注入到这样UnitOfWork的接口后面:

public interface IPerRequestDataStore : IDisposable
{
    bool Contains(string key);

    void Store<T>(string key, T value);

    T Get<T>(string key);
}
Run Code Online (Sandbox Code Playgroud)

HttpContext然后该对象将实现IPerRequestDataStore如下:

public class StaticHttpContextPerRequestDataStore : IPerRequestDataStore
{
    public bool Contains(string key)
    {
        return HttpContext.Current.Items.Contains(key);
    }

    public void Store<T>(string key, T value)
    {
        HttpContext.Current.Items[key] = value;
    }

    public T Get<T>(string key)
    {
        if (!this.Contains(key))
        {
            return default(T);
        }

        return (T)HttpContext.Current.Items[key];
    }

    public void Dispose()
    {
        var disposables = HttpContext.Current.Items.Values.OfType<IDisposable>();

        foreach (var disposable in disposables)
        {
            disposable.Dispose();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,我称之为StaticHttpContextPerRequestDataStore使用静态HttpContext.Current属性; 这不是单元测试的理想选择(完全是另一个主题),但至少名称表明了它的依赖性.

UnitOfWork然后你将IPerRequestDataStore它传给它的每一个Repository对象,这样它们就可以访问它了Entities.这意味着无论UnitOfWork您创建多少个实例,您都将在Entities整个请求中使用相同的对象,因为它是在...中存储和检索的IPerRequestDataStore.

你有一个抽象的基础Repository,它将使用它IPerRequestDataStore来延迟加载它的Entities对象,如下所示:

public abstract class RepositoryBase : IDisposable
{
    private readonly IPerRequestDataStore _dataStore;

    private PersonRepository personRepository;

    protected RepositoryBase(IPerRequestDataStore dataStore)
    {
        this._dataStore = dataStore;
    }

    protected BlogEntities Context
    {
        get
        {
            const string contextKey = "context";

            if (!this._dataStore.Contains(contextKey))
            {
                this._dataStore.Store(contextKey, new BlogEntities());
            }

            return this._dataStore.Get<BlogEntities>(contextKey);
        }
    }

    public void Dispose()
    {
        this._dataStore.Dispose();
    }
}
Run Code Online (Sandbox Code Playgroud)

你的PeopleRepository(例如)看起来像这样:

public class PeopleRepository : RepositoryBase, IPersonRepository
{
    public PeopleRepository(IPerRequestDataStore dataStore) 
        : base(dataStore)
    {
    }

    public Person FindById(int personId)
    {
        return this.Context.Persons.FirstOrDefault(p => p.PersonId == personId);
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,这是你的创作PeopleController:

IPerRequestDataStore dataStore = new StaticHttpContextDataStore();
UnitOfWork unitOfWork = new UnitOfWork(dataStore);
PeopleService service = new PeopleService(unitOfWork);
PeopleController controller = new PeopleController(service);
Run Code Online (Sandbox Code Playgroud)

这里的一个核心概念是对象通过其构造函数将依赖项注入其中; 这通常被认为是一种很好的做法,更容易让你从其他对象中组合对象.

  • 另外,在服务中创建 UoW 实例肯定会导致问题,例如,假设我必须使用两个单独的服务作为同一事务的一部分来更新两个不同的实体类型。如果在每个服务中创建一个 UoW 实例,那么我将为每个更新创建一个单独的数据库上下文实例,但是,一个可能会成功,另一个可能会失败,这违背了 UoW 的目的,即原子事务 (2认同)