asp.net-mvc - 具有依赖注入(inject)的工作单元 + 存储库 + 服务层

标签 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);
}

工作单元的具体实现使用底层 Entity Framework (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 应用程序, Controller 将其依赖项注入(inject)
在构造函数中也是如此。在这种情况下,工作单元和服务类被注入(inject)。该操作执行服务公开的操作,例如在存储库中创建记录并使用 DocumentStorageService 将文件保存到文件服务器,然后在 Controller 操作中提交工作单元。
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 请求的工作单元的相同实例注入(inject)所有调用者,因此 Controller 类获取一个新实例,然后使用该工作单元的服务类获取相同的实例作为 Controller 。通过这种方式,服务将一些记录添加到在工作单元中注册的存储库,并且可以由 Controller 中的客户端代码提交。

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

在 Windows 服务应用程序上,我希望能够通过单个工作单元获取一组记录,例如所有处于挂起状态的记录。然后我想遍历所有这些记录并查询数据库以单独获取每个记录,然后在每个循环期间检查每个记录的状态,因为从我查询所有记录到我想要操作的时间,状态可能已经改变一个。我现在遇到的问题是,我当前的架构不允许我为同一个服务实例拥有多个工作单元。
public class ProcessPortfolioRequestsJob : JobBase
{
    IPortfolioRequestService _portfolioRequestService;
    public ProcessPortfolioRequestsJob(IPortfolioRequestService portfolioRequestService)
    {
        _portfolioRequestService = portfolioRequestService;
    }

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

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

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

更新 1
添加继承自 EF 的 DbContext 的 DataContext 类,我在其中声明了我的 EF DbSet 和配置。
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);
    }
}

最佳答案

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

我建议你完全摆脱工作单元。 Entity Framework 已经是 UoW。甚至微软也改变了主意,不再推荐 UoW 和 EF。

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

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

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

关于asp.net-mvc - 具有依赖注入(inject)的工作单元 + 存储库 + 服务层,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25953881/

相关文章:

c# - 是否可以在asp.net mvc中按成员角色进行缓存?

c# - 针对 web api 中间层的 mvc 身份验证

c# - 登录 ASP.Net MVC 应用程序需要什么请求信息

java - 面试题: Design pattern to control behaviour of Fridge

java - 支持无状态和有状态使用的 JPA 代码

java - 如何使用 Mockito 关闭 stub 方法的参数

asp.net - 授权属性内的 UrlHelper 和 ViewContext

java - 软件分析....从 UML 或设计模式开始?

模块化方式的 Java Swing + MVC 模式

c# - 如何在依赖注入(inject)之外构造对象?