首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >当使用实体框架时,存储库应该是工作单元上的属性吗?

当使用实体框架时,存储库应该是工作单元上的属性吗?
EN

Stack Overflow用户
提问于 2014-06-02 04:44:14
回答 2查看 3K关注 0票数 9

我已经浏览了网页,寻找一个良好的实现仓库/单位的工作模式使用实体框架。我遇到的每件事都是在抽象的某个时刻紧密耦合的,或者假设工作单元和存储库使用的DbContext是共享的,并且应该存在于整个HTTP请求中(例如,通过依赖注入每个请求)。

例如,假设您正在使用来自服务层的存储库,服务构造函数可能如下所示:

代码语言:javascript
复制
public DirectoryService(IUnitOfWork unitOfWork, ICountryRepository countryRepo, IProvinceRepository provinceRepo)
{
    /* set member variables... */
}

工作单元构造函数可能如下所示:

代码语言:javascript
复制
public UnitOfWork(IDbContext context)
{
    _context = context;
}

存储库构造函数可能如下所示:

代码语言:javascript
复制
CountryRepository(IDbContext context)
{
    _context = context;
}

该解决方案使人盲目地假设依赖注入正在设置工作单元和存储库,以便使用每个请求的实例共享相同的IDbContext。这真的是一个安全的假设吗?

如果您使用的依赖注入与每个请求的实例,相同的IDbContext将注入到多个工作单位。工作单位不再是原子的,是吗?我可能在一个服务中有挂起的更改,然后提交到另一个服务中,因为上下文是跨多个工作单元共享的。

在我看来,设置一个IDbContextFactory并通过每个工作单元获得一个新的数据库上下文似乎更有意义。

代码语言:javascript
复制
public interface IDbContextFactory
{
    IDbContext OpenContext();
}

public class UnitOfWork 
{
    private IDbContextFactory _factory;
    private IDbContext _context;

    UnitOfWork(IDbContextFactory factory)
    {
        _factory = factory;
    }

    internal IDbContext Context
    {
        get { return _context ?? (_context = _factory.OpenContext()); }
    }
}

然后,问题就变成了,如何将我的工作实现单元提供给注入的存储库?我不想假设每个请求都有实例,因为那样我就回到了我开始时的同一条船上。

我唯一能想到的就是遵循实体框架的领导,将存储库(IDbSet<T>)作为工作单元(DbContext)的一部分。

所以我可能有这样的工作单位:

代码语言:javascript
复制
public class DirectoryUnitOfWork : IDirectoryUnitOfWork
{
    private IDbContextFactory _factory;
    private IDbContext _context;

    public DirectoryUnitOfWork(IDbContextFactory factory)
    {
        _factory = factory;
    }

    protected IDbContext Context 
    {
        get { return _context ?? (_context = _factory.OpenContext()); }
    }

    public ICountryRepository CountryRepository
    {
        get { return _countryRepo ?? (_countryRepo = new CountryRepository(Context)); }
    }

    public IProvinceRepository ProvinceRepository
    {
        get { return _provinceRepo ?? (_provinceRepo = new ProvinceRepository(Context)); }
    }

    void Commit()
    {
        Context.SaveChanges();
    }
}

然后我的目录服务开始像这样

代码语言:javascript
复制
public class DirectoryService : IDirectoryService
{
    private IDirectoryUnitOfWork _unitOfWork;

    public DirectoryService(IDirectoryUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public GetCountry(int id)
    {
        return _unitOfWork.CountryRepository.GetById(id);
    }

    public GetProvince(int id)
    {
        return _unitOfWork.ProvinceRepository.GetById(id);
    }

    public void AddProvince(Province province)
    {
        _unitOfWork.ProvinceRepository.Create(province);
        Country country = GetCountry(province.CountryId);
        country.NumberOfProvinces++; // Update aggregate count
        _unitOfWork.Commit();
    }

    /* ... and so on ... */
}

这似乎是一项很大的工作,但是使用这种方法可以使所有的东西都松耦合,并且可以测试单元。我是不是错过了一种更简单的方法,还是说这是一种很好的方法-- if --我要抽象化实体框架?

EN

回答 2

Stack Overflow用户

发布于 2014-06-03 13:56:22

您应该永远不要抽象ORM (本身就是一个抽象),但是您应该抽象持久性。我使用的唯一UoW是db事务,这是持久性细节。您不需要同时使用UoW和Repository。你应该想想如果你真的需要这些东西。

就个人而言,我默认使用存储库,因为持久性是我在应用程序中做的最后一件事。我不关心模式本身,我关心的是将我的BL或UI与DAL分离。您的上层( DAL除外,从依赖关系的角度来看,DAL是最低层)应该始终了解抽象,以便您可以在DAL实现中随心所欲。

许多开发人员不知道的一个诀窍是,设计模式(特别是架构模式)首先应该被视为高层次原则,其次是技术诀窍。简单地说,最重要的是模式试图实现的利益(原则,更大的图景),而不是它的实际实现(“低层次”细节)。

问问自己,为什么BL一开始就应该知道一个UoW。BL只知道处理业务对象的抽象。你从来没有和整个BL一起工作过一次,你总是处在一个特定的BL环境中。您的DirectoryService似乎在为自己的利益做很多事情。更新统计数据看起来与添加一个新省不属于同一个上下文。此外,为什么需要UoW进行查询?

我经常看到的一个错误是,当开发人员没有完成最重要的部分:设计本身时,他们就急于编写任何代码(附带一个设计模式)。当您有不正确的设计,问题会出现,您开始寻找解决办法。其中之一就是这个具有Repository属性的UoW,它需要像BL这样的高层来了解更多的业务关注点。现在BL需要知道您使用的是UoW,这是一个较低级别的模式,它对DAL很好,而对BL则不是很好。

顺便说一句,UoW在处理‘读’的情况时,对查询没有任何意义,而UoW只是为了‘写’。我没有提到EF或任何ORM,因为它们并不重要,您的BL服务(目录服务)已经被错误设计所要求的基础设施细节所破坏。请注意,有时您确实需要妥协才能实现解决方案,但事实并非如此。

正确的设计意味着您知道您的有限上下文(是的,DDD概念,您可以应用它,不管您想做多少DDD ),而不是把所有可能使用相同数据的东西放在一个地方。您有特定的用例上下文,计算省份(演示/报告详细信息)与添加省实际上是不同的用例。

添加省,然后发布一个向处理程序发出信号以更新统计数据的事件,是一种更优雅、更易于维护的解决方案。也不需要UoW。

您的代码将如下所示

代码语言:javascript
复制
public class DirectoryService : IDirectoryService
{
     public DirectoryService(IProvinceRepository repo, IDispatchMessage bus) 
     { 
       //assign fields
     }

      /* other stuff */

      //maybe province is an input model which is used by the service to create a business Province?
      public void AddProvince(Province province)
     {

        _repo.Save(province);
        _bus.Publish( new ProvinceCreated(province));
     }
}

  public class StatsUpdater:ISubscribeTo<ProvinceCreated> /* and other stat trigger events */
   {
       public void Handle(ProvinceCreated evnt)
       {
              //update stats here
       }  
   }

在某种程度上,它简化了事情,在其他方面,你可能会把事情复杂化。实际上,这是一种可维护的方法,因为stats更新程序可以订阅许多事件,但是逻辑只停留在一个类中。DirectoryService只做假设的事情(名称的哪个部分AddProvince提示您该方法也更新stats?)。

总之,您需要更好地设计BL,然后再将UoW、DbContext、存储库等作为属性来使您的生活复杂化。

票数 6
EN

Stack Overflow用户

发布于 2014-06-02 06:55:06

或者假设工作单元和存储库使用的DbContext是共享的,并且应该在整个HTTP中运行

这显然是错误的。假设上下文在UoW和存储库之间共享,并不意味着上下文生存期应该依赖于HTTP。相反,您可以创建新的上下文实例和随时使用它的UoW。这只是一个为HTTP请求而存在的默认 UoW的方便,但是创建新的本地工作单元可能很方便。

另一方面,如果存储库是从UoW公开的:

代码语言:javascript
复制
public class UnitOfWork
{
    ...

    public IUserRepository UserRepo
    {
        get { ... } 
    }

    public IAccountRepository AccountRepo
    {
        get { ... } 
    }

那么,在repos之间共享相同上下文的(而不是)可能会产生意想不到的结果:

代码语言:javascript
复制
 UoW uow = ....

 var u1 = uow.User.FirstOrDefault( u => u.ID == 5 );
 var u2 = uow.Account.FirstOrDefault( a => a.ID_USER == 5 ).User;

您肯定希望这两个查询返回id 5用户的同一个实例,而且,共享相同的上下文意味着第二个查询可以从第一级缓存中检索用户。另一方面,两个repos的两个不同上下文意味着您得到了两个不同的实例。

这也意味着这是不可能的。

代码语言:javascript
复制
 var u1 = uow.User.FirstOrDefault( u => u.ID == 5 );
 var a1 = uow.Account.FirstOrDefault( a => a.ID == 177 );

 a1.User = u1; // oops!

因为混合来自不同环境的粒子只会引起一个例外。但是,上面的场景是常见的!

这些观察得出的结论是,应该在repos之间共享上下文。但是,如果需要一个新实例,只需创建上下文的本地新实例,将其注入UoW,然后将其注入repos,并随意释放它。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/23987471

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档