使用DbContext SOLID方式

时间:2015-08-21 10:15:22

标签: c# dependency-injection inversion-of-control dbcontext solid-principles

直接依赖命令和查询处理程序中的DbContext,我了解到我违反SOLID-principlesStackOverflow user来自ready()

  

DbContext是一个包含特定于请求的运行时数据的包   将运行时数据注入构造函数会导致麻烦。让你的   与DbContext直接相关的代码会导致您的代码   违反DIP和ISP,这很难维护。

这很有道理,但我不确定如何解决它可能使用IoC和DI?

首先是使用单个方法创建IUnitOfWork,可用于查询上下文:

public interface IUnitOfWork
{
    IQueryable<T> Set<T>() where T : Entity;
}

internal sealed class EntityFrameworkUnitOfWork : IUnitOfWork
{
    private readonly DbContext _context;

    public EntityFrameworkUnitOfWork(DbContext context)
    {
        _context = context;
    }

    public IQueryable<T> Set<T>() where T : Entity
    {
        return _context.Set<T>();
    }
} 

既然我可以依赖IUnitOfWork查询处理程序(接收数据),我已经解决了这一部分。

接下来我需要查看我的命令(修改数据)我可以使用装饰器为我的命令解决保存对上下文的更改:

internal sealed class TransactionCommandHandler<TCommand> : IHandleCommand<TCommand> where TCommand : ICommand
{
    private readonly DbContext _context;
    private readonly Func<IHandleCommand<TCommand>> _handlerFactory;

    public TransactionCommandHandler(DbContext context, Func<IHandleCommand<TCommand>> handlerFactory)
    {
        _context = context;
        _handlerFactory = handlerFactory;
    }

    public void Handle(TCommand command)
    {
        _handlerFactory().Handle(command);
        _context.SaveChanges();
    }
}

这也很好。

第一个问题是:如何从命令处理程序修改上下文中的对象,因为我不能再直接依赖于DbContext了?

赞:context.Set<TEntity>().Add(entity);

据我了解,我必须为此创建另一个接口以使用SOLID原则。例如ICommandEntities,其中包含void Create<TEntity>(TEntity entity),更新,删除,回滚甚至重新加载等方法。然后在我的命令中依赖于这个接口,但我在这里错过了一点,我们是否抽象得太深了?

第二个问题是:在使用DbContext时这是否是尊重SOLID原则的唯一方式,或者这是一个&#34;好的&#34;违反原则?

如果需要,我使用Simple Injector作为我的IoC容器。

1 个答案:

答案 0 :(得分:3)

使用EntityFrameworkUnitOfWork,您仍然违反以下部分:

  

DbContext是一个包含特定于请求的运行时数据的包,并将运行时数据注入构造函数会导致麻烦

您的对象图应该是无状态的,并且状态应该在运行时通过对象图传递。您的EntityFrameworkUnitOfWork应如下所示:

internal sealed class EntityFrameworkUnitOfWork : IUnitOfWork
{
    private readonly Func<DbContext> contextProvider;

    public EntityFrameworkUnitOfWork(Func<DbContext> contextProvider)
    {
        this.contextProvider = contextProvider;
    }

    // etc
}

使用单个IQueryable<T> Set<T>()方法进行抽象非常适合查询。它可以让孩子玩耍,以便稍后向IQueryable<T>添加基于权限的基于权限的过滤,而无需更改查询处理程序中的任何代码行。

请注意,这是一个暴露IQueryable<T>(如此IUnitOfWork)抽象的抽象,但仍然违反了SOLID原则。这是因为IQueryable<T>是一个漏洞抽象,这基本上意味着依赖倒置原则违规。 IQueryable是一个漏洞抽象,因为在EF上运行的LINQ查询不会自动在NHibernate上运行,反之亦然。但至少我们在这种情况下更加坚固,因为它阻止我们通过查询处理程序进行彻底的更改,以防我们需要应用权限过滤或其他类型的过滤。

尝试从查询处理程序中完全抽象出O / RM是没用的,只会导致您将LINQ查询移动到另一个层,或者会使您恢复为SQL查询或存储过程。但同样,抽象O / RM不是问题所在,能够在应用程序中的正确位置应用横切关注点是问题。

最后,如果您迁移到NHibernate,您很可能必须重写一些查询处理程序。在这种情况下,您的集成测试将直接告诉您需要更改哪些处理程序。

但是有足够的关于查询处理程序的说法;让我们谈谈命令处理程序。他们需要使用DbContext做更多的工作。最后你可能会考虑让命令处理程序直接依赖DbContext。但我仍然希望他们不要这样做,让我的命令处理程序只依赖于SOLID抽象。这看起来如何可能因应用程序而异,但由于命令处理程序通常非常专注,并且只更改了几个实体,因此我更喜欢以下内容:

interface IRepository<TEntity> {
    TEntity GetById(Guid id);
    // Creates an entity that gets saved when the transaction is committed,
    // optionally using an id supplied by the client.
    TEntity Create(Guid? id = null);
}

在我工作的系统中,我们几乎没有删除任何东西。因此,这阻止我们在Delete上使用IRepository<TEntity>方法。如果在该接口上同时具有GetByIdCreate,则更改已经很高,您将违反接口隔离原则,并且要非常小心,不要添加更多方法。你甚至可能想要拆分它们。如果您发现您的命令处理程序变大,有很多依赖项,您可能希望在Aggregate Services中拆分它们,或者如果结果更糟,您可以考虑从IUnitOfWork返回存储库,但是您必须小心,不要失去增加跨领域关注的可能性。

  

这是使用DbContext

时尊重SOLID原则的唯一方法

这绝对不是唯一的方法。我想说最愉快的方法是应用领域驱动设计并使用聚合根。在后台,您可能有一个O / RM为您保留完整的聚合,完全隐藏在命令处理程序和实体本身之外。如果您可以将此对象图完全序列化为JSON并将其作为blob存储在数据库中,那就更令人愉快了。这完全消除了首先使用O / RM工具的需要,但这实际上意味着您拥有一个文档数据库。您最好使用真实的文档数据库,否则几乎不可能查询该数据。

  

或者这是#34;好的&#34;违反原则?

无论你做什么,都必须在某处违反SOLID原则。这取决于您违反它们的好处以及坚持使用它们的好处。