【问题标题】:Using IQueryable with repository pattern in DDD在 DDD 中将 IQueryable 与存储库模式一起使用
【发布时间】:2014-02-03 16:36:05
【问题描述】:

我需要关于 DDD(域驱动设计)以及存储库模式和封装的实施的建议。 据我了解,存储库模式使我能够将所有数据库访问逻辑放在一个地方,并将该逻辑从应用程序的其他部分抽象出来。 另一方面是 orm (Nhibernate, EntityFramework...),它支持 Linq 和 IQueryable。 我的强项是: 1. 如果我使用存储库,那么我不应该使用 IQueryable 作为我的返回类型 bust,而是使用 IEnumerable。因为如果我使用 IQueryable 那么这将允许将数据库代码泄漏到其他应用程序层(IE 将允许其他开发人员在他们不属于的 mvc 控制器中进行查询)。 但是每个控件都使用 IQueryable 来访问数据并且这样做是因为更容易。 如果我使用 IQueryable 作为我的存储库方法的返回类型,那么: - 我允许其他开发人员在应用程序的其他层进行数据库查询(我认为这应该是不可能的) - 它会将我的域实体(域模型)泄漏到应用程序的其他层(即用户界面),但不应该,而是应该使用 DTO。

我的问题是 IQueryable 是 DDD 中的一个好习惯吗?

【问题讨论】:

    标签: domain-driven-design repository-pattern ddd-repositories


    【解决方案1】:

    我会说这不是一个好习惯。 理想情况下,您应该在您的域之上拥有某种应用层。如果您直接或通过 Query 对象公开您的域对象,那么实际上有人可能会在您的控制之外对其进行修改。

    我个人喜欢将 IQueryable 视为一个实现细节,理想情况下我的域不会依赖它(如果我想切换我的存储技术,这可能是个问题)。 大多数情况下,我最终会在我的存储库实现内部使用 IQueryable。我通常最终得到的是实现具有 FindBySpecification 方法的通用存储库,然后为从它继承的每个聚合根拥有专门的存储库。例如:

    public interface IRepository<TEntity>        
    {
        TEntity Get(Guid ID);
        void Add(TEntity entity);
        void Remove(TEntity entity);
        void Detach(TEntity entity);
        IEnumerable<TEntity> WithSpecification(ISpecification<TEntity> specification);
    }
    
    public interface IOrdersRepository : IRepository<Order>
    {
        IEnumerable<Order> GetCompletedOrdersForAllPreferedCustomers(DateTime orderCompletedAfter);
        Order GetOrderBySomeOtherComplicatedMeans();
    }
    

    另一种方法是设计您的应用程序以遵循 CQRS 原则。 然后你可以让你的 DomainModel 在命令端发挥它的魔力,并在查询端为你的客户端创建一个只读的数据模型。 根据您的要求,此设置可以变得非常复杂,但它可以像映射到同一个数据库的两个 ORM 模型一样简单(命令端的一个映射您的域实体,查询端的一个映射到简单的 DTO) .

    【讨论】:

    • 我遇到的问题是我们在 MVC 应用程序上使用 DevExpress 控件。当您使用 IQueriable 时,GridView 分页非常简单。但是如果你使用另一种数据机制,那么在所有网格上实现自定义数据绑定就会变得非常繁琐。
    • 是的,我正在使用某种 CQRS(没有事件源),但只有一个 orm 模型。在写入端我使用可以改变行为的命令,在读取端我使用相同的 orm 模型,但暴露为 DTO 对象
    • 不,没有就地编辑。但我仍然需要某种分页和排序方式。过滤是另一个问题,因为读取端不直接访问实体,而只访问 DTO 对象。我现在这样做的方式是将页面索引和计数传递给我进行分页的存储库中的方法。我知道在应用程序层中使用 IQueriable 会更容易使用 Skip().Take()。
    • 嗯,我想最好的办法是让你的 orm 将你的表映射到简单的 POCO 类,然后使用工厂在写入端创建你的域实体,这样你就可以使用相同的 ORM对于阅读方面,这将涉及更多代码,但我想会是更通用的解决方案。这完全取决于您的项目,对于具有实际稳定数据库模式的较小项目,我将为读取端创建单独的 ORM 映射。无论哪种方式,这都是一种权衡。
    【解决方案2】:

    我个人认为通过 IQueryable 跨所有层公开 EF 实体不一定是坏事。但这只是我个人的看法。其他人可能不同意,尤其是从封装的角度来看。但通常,松散耦合的概念是复杂性和实际收益之间的权衡。通过封装 IQueryable 知道您将失去很多实际收益,例如延迟加载的能力。

    如果您的应用程序层直接位于存储库层之上,我投票选择使用 IEnnumerable 而不是 IQueryable。但是,如果您在中间有一个服务层(我个人更喜欢包含所有业务逻辑,因此存储库层可以专门处理数据访问操作),那么我将让存储库返回 IQueryable 并让服务层在执行后返回 IEnnumerable它的业务逻辑来自存储库返回的 IQueryable 对象。

    这些只是我个人的规则:

    1. 如果您需要封装 EF,请仅将其封装在远离应用层的地方。否则,请在所有图层上根据需要过度使用它。 EF很强大,把它封装在repository层会让你失去很多它的威力
    2. 应用层应该尽可能的薄,它不应该对它从它下面的层接收到的数据执行任何进一步的处理。它接收列表并呈现它,就是这样。

    【讨论】:

    • 是的,我在 Dal 层之上有应用层。我的中心层是 DomainModel。我的应用程序层是域层的一个薄外观,它只是作为协调其他层的管道层。领域业务逻辑保留在领域层中。
    • 延迟加载的需要意味着您的聚合设计不正确。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-04
    • 2017-03-27
    • 1970-01-01
    • 2011-01-17
    • 2011-04-12
    • 2012-02-15
    相关资源
    最近更新 更多