【问题标题】:How to decorate a read-only repository with audit repository while using UnitOfWork?使用 UnitOfWork 时如何用审计存储库装饰只读存储库?
【发布时间】:2013-12-14 01:21:30
【问题描述】:

我有一个使用 EF6 获取一些数据的简单存储库。我也在使用 DI 框架来注入依赖项。

namespace Domain
{
    public interface IMyRespository
    {
        List<MyObject> FetchObjects();
    }
}

namespace Data
{
    public class MyRepository : IMyRepository
    {
         private readonly MyDbContext _context;

         public MyRepository(MyDbContext context)
         {
             _context = context;
         }

         public List<MyObjects> FetchObjects()
         {
             return _context.MyObjects.ToList();
         }
     }
} 

一个新要求指出我需要记录每个 FetchObjects() 调用及其输出。我认为这将是应用装饰器模式的完美示例。

namespace Domain
{
    public class MyRepositoryDecorator : IMyRepository
    {
        private readonly IMyRepository _inner;
        private readonly ILogRepository _logRepository;

        public MyRepositoryDecorator(IMyRepository inner, ILogRepository logRepository)
        {
            _inner = inner;
            _logRepository = logRepository;
        }

        public List<MyObjects> FetchObjects()
        {
            var objects = _inner.FetchObjects();
            var logObject = new LogObject(objects);
            _logRepository.Insert(logObject);
            _logRepository.Save();
            return objects;
        }
    }
}

现在我正在寻找使用 UnitOfWork 模式,但我不确定在这种情况下如何实现。

据我了解,需要一些组件来管理 UnitOfWork。因此,在这种情况下,服务类将进行一些调用,并在最后调用 UnitOfWork 类上的 Save/Commit。

但是,如果存储库接口指示只读操作,则服务类没有理由将调用包装在 UnitOfWork 中并在最后调用 Save/Commit。它看起来也很奇怪。然而,装饰器需要这样做才能完成工作。

我可能在这里遗漏了一些基本结构。关于如何正确处理这种情况的任何想法?

【问题讨论】:

    标签: c# dependency-injection repository repository-pattern unit-of-work


    【解决方案1】:

    使用 Decorator(或类似的)将 UoW 与存储库混合是一个坏主意,因为 UoW 跨越多个存储库并不罕见。

    此外,是否应提交 UoW 也不取决于存储库。存储库应该尽可能少地了解 UoW,理想情况下(大多数情况下都是如此)什么都不知道。

    在您的场景中,UnitOfWork 类几乎只处理事务,因此它可以实现为 TransactionScope 的简单包装器,类似于:

    public sealed class UnitOfWork : IDisposable {
       private readonly TransactionScope _transaction;
       public UnitOfWork() { _transaction = new TransactionScope(); }
    
       public void Commit { _transaction.Commit(); }
       public void Dispose { _transaction.Dispose(); }
    }
    

    现在由服务来实例化/提交 UoW,而不是由 Repository:

    //assuming in a service
    public void DoSomething() {
        using(var uow = new UnitOfWork()) {
            _repositoryA.UpdateSomething();
            _repositoryB.DeleteSomething();
            _uow.Commit();
        }
    }
    

    如果您的服务只想读取数据,那么就不要在该操作中使用 UnitOfWork(或者在不调用 Commit 的情况下使用它,这样它就会被释放)。

    如果您的存储库需要了解 UoW,通常会在其行为方法中将其作为另一个参数传递。 请注意,它没有完成,因为存储库想要调用提交,但有时(很少)存储库需要“登记”到 UoW。这些情况比较复杂。

    【讨论】:

    • 感谢您的回复。我认为装饰器存储库不一定需要了解 UoW。我只是不确定装饰者是否可以假设它总是包裹在 UoW 中。问题似乎是,如果调用服务没有设置 UoW(因为从它的角度来看,它只是在读取数据),则永远不会调用 Commit 并且装饰器所做的更改会丢失。
    • Repository 不知道任何 UoW,并且在这种情况下,它总是在每个方法中执行其内部提交。但是如果服务没有提交 UoW,那么所有涉及的存储库中的事务都将被回滚。如果服务不使用 UoW 并在一个或多个 repos 上调用多个行为,则有可能会提交一些操作,而如果在两者之间失败,则可能不会提交一些操作。但这正是我们需要 UoW 作为模式的原因。
    • 此外,遵循 CQS 原则(约 20 年前由 B. Meyer 制定)总是好的,它说如果你的方法返回任何状态,那么它就是一个查询,并且这个方法不能进行任何更改。如果您的方法进行了更改,那么它就是一个命令并且它不能返回任何状态(实际上应该返回 void)。如果你遵循这个简单的原则,那么就不会有任何“从它的角度来看它只是在阅读”,从一开始就很清楚:如果你发出命令则使用UoW,如果你只做查询则不要。跨度>
    • 还要考虑是否应该在存储库中完成日志记录。如果它是业务逻辑的一部分,那么它显然不应该存在。但如果它只是一个“技术性的东西”,那么好吧,只是在内部抑制事务以进行日志记录操作。遵循 SRP 并将日志记录专用于另一个“日志记录服务”而不是“就地”执行总是好的。并且该服务将知道如何在编写日志时处理内容(禁止事务等)。
    • 啊。我了解基本原理,很高兴看到它们结合在一起。我希望能够只装饰存储库,但 CQS 将其扔出窗外。真的有道理。在数以百万计的日志装饰器示例中,没有人展示如何记录“查询”,这解释了它。这不仅仅是“技术”,我实际上需要它来实现一段业务逻辑。所以在这种情况下,我应该用 ILogService 替换 ILogRepository 并保留装饰器? (使用 UoW 实现 ILogService)
    猜你喜欢
    • 2019-03-29
    • 1970-01-01
    • 1970-01-01
    • 2010-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多