直接依赖命令和查询处理程序中的DbContext
,我了解到我违反SOLID-principles的StackOverflow 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容器。
答案 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>
方法。如果在该接口上同时具有GetById
和Create
,则更改已经很高,您将违反接口隔离原则,并且要非常小心,不要添加更多方法。你甚至可能想要拆分它们。如果您发现您的命令处理程序变大,有很多依赖项,您可能希望在Aggregate Services中拆分它们,或者如果结果更糟,您可以考虑从IUnitOfWork
返回存储库,但是您必须小心,不要失去增加跨领域关注的可能性。
这是使用DbContext
时尊重SOLID原则的唯一方法
这绝对不是唯一的方法。我想说最愉快的方法是应用领域驱动设计并使用聚合根。在后台,您可能有一个O / RM为您保留完整的聚合,完全隐藏在命令处理程序和实体本身之外。如果您可以将此对象图完全序列化为JSON并将其作为blob存储在数据库中,那就更令人愉快了。这完全消除了首先使用O / RM工具的需要,但这实际上意味着您拥有一个文档数据库。您最好使用真实的文档数据库,否则几乎不可能查询该数据。
或者这是#34;好的&#34;违反原则?
无论你做什么,都必须在某处违反SOLID原则。这取决于您违反它们的好处以及坚持使用它们的好处。