CodeGo.net>使用DbContext的SOLID方式
作者:互联网
通过直接依赖于命令和查询处理程序中的DbContext,我了解到我违反了StackOverflow user的注释中的SOLID-principles:
The
DbContext
is a bag with request-specific runtime data and
injecting runtime data into constructors causes trouble. Letting your
code having a direct dependency of onDbContext
causes your code to
violate DIP and ISP and this makes hard to maintain.
这完全有道理,但是我不确定如何使用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实体),更新,删除,回滚甚至重新加载之类的方法.然后依靠命令中的此接口,但是我在这里遗漏了一点吗?我们抽象得太深了吗?
第二个问题是:使用DbContext时,这是尊重SOLID原理的唯一方法吗?还是“可以”违反原则的地方?
如果需要,我将简单注入器用作IoC容器.
解决方法:
使用EntityFrameworkUnitOfWork,您仍然违反了以下部分:
The DbContext is a bag with request-specific runtime data and injecting runtime data into constructors causes trouble
您的对象图应该是无状态的,并且状态应该在运行时通过对象图传递.您的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上运行,反之亦然.但至少在这种情况下,我们的SOLID稍微多了一点,因为在需要应用权限过滤或其他类型的过滤的情况下,它使我们不必通过查询处理程序进行大范围的更改.
试图从查询处理程序中完全抽象出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);
}
在我工作的系统中,我们几乎从未删除任何内容.因此,这阻止了我们对该IRepository< TEntity>使用Delete方法.当同时在该接口上同时具有GetById和Create时,更改已经很高,您将违反接口隔离原则,并且非常小心不要添加更多方法.您甚至可能想要拆分它们.如果您发现命令处理程序变得很大,并且具有许多依赖关系,则可能希望将它们拆分为Aggregate Services,或者如果结果更糟,则可以考虑从IUnitOfWork返回存储库,但是您必须注意不要丢失添加跨领域关注点的可能性.
Is this the only way respect the SOLID-principles when working with the DbContext
这绝对不是唯一的方法.我会说,最令人愉快的方法是应用域驱动设计并使用聚合根.在后台,您可能拥有一个O / RM,该O / RM可以为您持久保存完整的聚合,而对命令处理程序和实体本身则完全隐藏了.如果您可以将该对象图完全序列化为JSON并将其作为blob存储在数据库中,那就更令人高兴了.首先,这完全不需要使用O / RM工具,但这实际上意味着您拥有文档数据库.您最好使用真实的文档数据库,因为否则将几乎无法查询该数据.
or is this a place where its “okay” to violate the principles?
无论您做什么,都将不得不违反SOLID原则.最好由您来违反它们,以及在哪里坚持下去.
标签:dependency-injection,dbcontext,solid-principles,c,inversion-of-control 来源: https://codeday.me/bug/20191119/2039259.html