工作单元+存储库+具有依赖注入的服务层

时间:2014-09-20 22:26:12

标签: asp.net-mvc design-patterns dependency-injection repository-pattern unit-of-work

我正在设计一个Web应用程序和一个Windows服务,并且希望将工作单元+存储库层与服务层结合使用,并且我在将它们放在一起时遇到一些麻烦,以便客户端应用程序控制与工作单位的数据。

工作单元具有在事务中注册的所有存储库的集合以及提交和回滚操作

public interface IUnitOfWork : IDisposable
{
    IRepository<T> Repository<T>() where T : class;
    void Commit();
    void Rollback();
}

通用存储库具有将在特定模型(表)

的数据层上执行的操作
public interface IRepository<T> where T : class 
{
    IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, IList<Expression<Func<T, object>>> includedProperties = null, IList<ISortCriteria<T>> sortCriterias = null);
    PaginatedList<T> GetPaged(Expression<Func<T, bool>> filter = null, IList<Expression<Func<T, object>>> includedProperties = null, PagingOptions<T> pagingOptions = null);
    T Find(Expression<Func<T, bool>> filter, IList<Expression<Func<T, object>>> includedProperties = null);
    void Add(T t);
    void Remove(T t);
    void Remove(Expression<Func<T, bool>> filter);
}

工作单元的具体实现使用实体框架(DbContext)将更改保存到数据库,并且每个工作单元创建一个新的DbContext类实例

public class UnitOfWork : IUnitOfWork
{
    private IDictionary<Type, object> _repositories;
    private DataContext _dbContext;
    private bool _disposed;

    public UnitOfWork()
    {
        _repositories = new Dictionary<Type, object>();
        _dbContext = new DataContext();
        _disposed = false;
    }

如果工作单元中的存储库不存在于当前工作单元实例中,则会在访问时创建存储库。存储库将DbContext作为构造函数参数,因此它可以有效地在当前工作单元中工作

public class Repository<T> : IRepository<T> where T : class
{
    private readonly DataContext _dbContext;
    private readonly DbSet<T> _dbSet;

    #region Ctor
    public Repository(DataContext dbContext)
    {
        _dbContext = dbContext;
        _dbSet = _dbContext.Set<T>();
    }
    #endregion

我还有一个服务类,它封装了业务工作流逻辑并在构造函数中获取它们的依赖关系。

public class PortfolioRequestService : IPortfolioRequestService
{
    private IUnitOfWork _unitOfWork;
    private IPortfolioRequestFileParser _fileParser;
    private IConfigurationService _configurationService;
    private IDocumentStorageService _documentStorageService;

    #region Private Constants
    private const string PORTFOLIO_REQUEST_VALID_FILE_TYPES = "PortfolioRequestValidFileTypes";
    #endregion

    #region Ctors
    public PortfolioRequestService(IUnitOfWork unitOfWork, IPortfolioRequestFileParser fileParser, IConfigurationService configurationService, IDocumentStorageService documentStorageService)
    {
        if (unitOfWork == null)
        {
            throw new ArgumentNullException("unitOfWork");
        }

        if (fileParser == null)
        {
            throw new ArgumentNullException("fileParser");
        }

        if (configurationService == null)
        {
            throw new ArgumentNullException("configurationService");
        }

        if (documentStorageService == null)
        {
            throw new ArgumentNullException("configurationService");
        }

        _unitOfWork = unitOfWork;
        _fileParser = fileParser;
        _configurationService = configurationService;
        _documentStorageService = documentStorageService;
    }
    #endregion

Web应用程序是一个ASP.NET MVC应用程序,控制器会注入其依赖项 在构造函数中也是如此。在这种情况下,注入工作单元和服务类。该操作执行服务公开的操作,例如在存储库中创建记录并使用DocumentStorageService将文件保存到文件服务器,然后在控制器操作中提交工作单元。

public class PortfolioRequestCollectionController : BaseController
{
    IUnitOfWork _unitOfWork;
    IPortfolioRequestService _portfolioRequestService;
    IUserService _userService;

    #region Ctors
    public PortfolioRequestCollectionController(IUnitOfWork unitOfWork, IPortfolioRequestService portfolioRequestService, IUserService userService)
    {
        _unitOfWork = unitOfWork;
        _portfolioRequestService = portfolioRequestService;
        _userService = userService;
    }
    #endregion
[HttpPost]
    [ValidateAntiForgeryToken]
    [HasPermissionAttribute(PermissionId.ManagePortfolioRequest)]
    public ActionResult Create(CreateViewModel viewModel)
    {
        if (ModelState.IsValid)
        {
            // validate file exists
            if (viewModel.File != null && viewModel.File.ContentLength > 0)
            {
                // TODO: ggomez - also add to CreatePortfolioRequestCollection method
                // see if file upload input control can be restricted to excel and csv
                // add additional info below control
                if (_portfolioRequestService.ValidatePortfolioRequestFileType(viewModel.File.FileName))
                {
                    try
                    {
                        // create new PortfolioRequestCollection instance
                        _portfolioRequestService.CreatePortfolioRequestCollection(viewModel.File.FileName, viewModel.File.InputStream, viewModel.ReasonId, PortfolioRequestCollectionSourceId.InternalWebsiteUpload, viewModel.ReviewAllRequestsBeforeRelease, _userService.GetUserName());
                        _unitOfWork.Commit();                            
                    }
                    catch (Exception ex)
                    {
                        ModelState.AddModelError(string.Empty, ex.Message);
                        return View(viewModel);
                    }

                    return RedirectToAction("Index", null, null, "The portfolio construction request was successfully submitted!", null);
                }
                else
                {
                    ModelState.AddModelError("File", "Only Excel and CSV formats are allowed");
                }
            }
            else
            {
                ModelState.AddModelError("File", "A file with portfolio construction requests is required");
            }
        }


        IEnumerable<PortfolioRequestCollectionReason> portfolioRequestCollectionReasons = _unitOfWork.Repository<PortfolioRequestCollectionReason>().Get();
        viewModel.Init(portfolioRequestCollectionReasons);
        return View(viewModel);
    }

在Web应用程序上,我使用Unity DI容器将每个http请求的相同工作单元实例注入所有调用者,因此控制器类获取一个新实例,然后使用该工作单元的服务类获取与控制器相同的实例。这样,服务就会将一些记录添加到存储库中,该存储库在一个工作单元中注册,并且可以由控制器中的客户端代码提交。

关于上述代码和架构的一个问题。如何摆脱服务类中的工作依赖单元?理想情况下,我不希望服务类具有工作单元的实例,因为我不希望服务提交事务,我只是希望服务引用它所需的存储库使用,并让控制器(客户端代码)在看到适合时提交操作。

在Windows服务应用程序中,我希望能够获得一组具有单个工作单元的记录,并说明处于待定状态的所有记录。然后我想循环遍历所有这些记录并查询数据库以单独获取每个记录,然后在每个循环期间检查每个记录的状态,因为状态可能已经从我查询的所有时间改变到我想要操作的时间一个人。我现在面临的问题是,我目前的架构并不允许我为同一个服务实例提供多个工作单元。

public class ProcessPortfolioRequestsJob : JobBase
{
    IPortfolioRequestService _portfolioRequestService;
    public ProcessPortfolioRequestsJob(IPortfolioRequestService portfolioRequestService)
    {
        _portfolioRequestService = portfolioRequestService;
    }

上面的Job类将构造函数中的服务作为依赖项,并由Unity再次解析。解析和注入的服务实例取决于工作单元。我想对服务类执行两次get操作,但因为我在相同的工作单元实例下运行,所以我无法实现。

对于你们所有的大师们,你们对我如何重新构建我的应用程序,工作单元+存储库+服务类以实现上述目标有什么建议吗?

我打算使用工作单元+存储库模式来实现我的服务类的可测试性,但我对其他设计模式持开放态度,这些模式将使我的代码同时可维护和可测试,同时保持关注点的分离。

更新1 添加继承自EF的DbContext的DataContext类,我在其中声明了我的EF DbSets和配置。

public class DataContext : DbContext
{
    public DataContext()
        : base("name=ArchSample")
    {
        Database.SetInitializer<DataContext>(new MigrateDatabaseToLatestVersion<DataContext, Configuration>());
        base.Configuration.ProxyCreationEnabled = false;
    }

    public DbSet<PortfolioRequestCollection> PortfolioRequestCollections { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        modelBuilder.Configurations.Add(new PortfolioRequestCollectionConfiguration());

        base.OnModelCreating(modelBuilder);
    }
}

2 个答案:

答案 0 :(得分:17)

如果您使用工作单元(UoW)的目的是为了测试性,那么您走错了路。工作单元对可测性无效。它的主要目的是为不同的数据源提供原子事务,为尚未提供它的数据层提供UoW功能,或者以更容易替换的方式包装现有的UoW ......通过使用通用存储库使其无效(无论如何,这将它与实体框架紧密结合)。

我建议你彻底摆脱工作单位。实体框架已经是一个UoW。甚至微软也改变了主意,不再推荐使用EF的UoW。

因此,如果您摆脱了UoW,那么您可以使用存储库来包装您的EF查询。我不建议使用通用存储库,因为这会在您的代码(您的UoW已经在做的事情)中泄漏您的数据层实现,而是创建具体的repoTsitories(如果您愿意,可以在内部使用通用存储库,但是通用存储库不应泄漏到您的存储库之外)。

这意味着您的服务层将获取所需的特定具体存储库。例如,IPortfolioRepository。然后你有一个继承自IPortfolioRepository的PortfolioRepository类,它将你的EF DbContext作为一个参数,由你的Depndency Injection(DI)框架注入。如果将DI容器配置为以“PerRequest”为基础实例化EF上下文,则可以将同一实例传递给所有存储库。您可以在存储库中使用Commit方法调用SavesChanges,但它会保存对所有更改的更改,而不仅仅是对该存储库的更改。

就可测试性而言,您有两种选择。您可以模拟具体的存储库,也可以使用EF6的内置模拟功能。

答案 1 :(得分:4)

我自己经历过那个地狱洞,这就是I have done

  • 彻底抛弃UoW。 EF的DBContext 基本上是一个UoW。没有必要重新发明轮子。

    MSDN

      

    DbContext类

         

    表示工作单元和存储库模式的组合   并使您能够查询数据库并将更改组合在一起   然后将作为一个单元写回商店。

  • 服务层+回购层似乎是一个不错的选择。但是,当DBContext的DbSet等同于存储库时,repos总是漏洞抽象。

  • 然后当出现对Windows服务的需求时,现在又变得混乱了另一层。在混合中抛出异步或后台处理,事情很快就会开始泄漏。

如果你问我的2美分,我会说服务层+ EF,一个包装业务逻辑,另一个包装UOW /存储库模式。

或者,特别是对于Windows服务,我发现转向command-query based方法效果更好。 它不仅有助于测试性,它还有助于异步任务,即使在请求结束后我也不必担心DBContext保持活动状态(DBContext现在与命令处理程序绑定并且只要async命令保持不变就保持活动状态活的)。

现在,如果您最近消化了所有关于UOW / Repository模式的事实,那么当然,即使阅读Command-Query模式也会让您的思绪受到伤害。我一直沿着这条道路走下去,但相信我,至少值得一试并尝试一下,值得花时间。

这些帖子可能有所帮助:

如果你足够勇敢(在通过CQRS消化之后),那么看一下实现Mediator模式的MediatR(基本上用通知包装命令查询)并允许通过pub-sub工作。 pub-sub模型非常适合Windows服务和服务层。