存储库模式:模型关系的实现和延迟加载

时间:2011-05-10 16:43:26

标签: c# design-patterns repository lazy-loading entity-relationship

我有一个处理产品和产品类别的应用程序。对于其中的每一个,我都使用POCO定义了模型。

// Represents a product.
class Product {
  public virtual int ID { get; set; }
  public virtual string Name { get; set; }
  public virtual ProductCategory Category { get; set; }
}

// Represents a product category.
class ProductCategory {
  public virtual int ID { get; set; }
  public virtual string Name { get; set; }
  public virtual IEnumerable<Product> Products { get; set; }
}

该应用程序使用存储库来访问这些模型

// The interface implemented by the application's repository
interface IProductRepository {
  IEnumerable<Product> GetAllProducts();

  void Add(Product product);
  void Remove(Product product);
  void Save(Product product);
}

在Product类中,只有在需要/访问(延迟加载)时才应加载名为Category类型的属性ProductCategory。我希望我的模型保持POCO并且只包含模型的结构。

我采取了正确的方法吗?
我是否应该只拥有Product类中类别的ID,并使用单独的产品类别存储库来加载类别?

实施延迟加载和关系
目前,我对repository接口的实现返回了一个扩展Product类型的类型的对象,并且支持通过存储库实例进行延迟加载。

谁应该负责加载产品类别?

我对产品和类别存储库应该如何交互以实现延迟加载感兴趣?它们应该相互引用还是应该有一个包含两个子存储库的主存储库并将其传递给我的扩展模型类型?

你会采取什么方法?(欢迎任何建议和批评)


我应该注意,我希望应用程序是可扩展的,并且存储库和模型本身的所有接口都将位于单独的组件中。这意味着扩展程序不能直接访问模型类定义。

4 个答案:

答案 0 :(得分:5)

一些评论和我的意见:

<强> 1)

  

我是否应该只拥有该ID   Product类中的类别和用法   一个单独的产品存储库   加载类别的类别?

没有。您正在使用ORM(至少我假设您这样做)能够通过类实例之间的引用来建模关系,而不是通过您正在使用的ID以关系方式进行查询。将您的想法转化为最后一个结果意味着您从模型类中删除所有导航属性,并且只有标量属性,其中一些属性作为对象之间的键。这只是ORM中的“R”。

<强> 2)

  

现在我执行了   repository接口返回一个对象   扩展产品的类型   键入并支持延迟加载   通过存储库实例。

不确定这究竟意味着什么。 (我希望看到一个代码片段,你是如何做到这一点的。)但我的猜测是,在你的派生Product类中,你会以某种方式注入对存储库的引用,如下所示:

public class ProductProxy : Product
{
    private IProductRepository _productRepo;

    public ProductProxy(IProductRepository productRepo)
    {
        _productRepo = productRepo;
    }

    // now you use _productRepo to lazily load something on request, do you?
}

嗯,加载类别显然是一个问题,因为IProductRepository没有方法可以访问它们。

第3)

  

我对产品和产品感兴趣   类别存储库应该相互作用   实现延迟加载?应该   他们互相引用或应该引用我   有两个主存储库   子存储库并将其传递给我   扩展模型类型?

您的ProductRepository和CategoryRepository看起来像通用存储库的实例,它只负责单个实体类型(在EF 4.1中,这类似于DbSet<T> TProduct或分别为Category

我会避免在这些存储库之间有引用,因为每当你添加新的实体或导航属性时,这可能会导致复杂的repo-references。

我看到另外两个选择:

  • (基本上你已经提到的)拥有一个负责ProductCategory的资源库。您仍然可以拥有通用存储库,但我会将它们视为内部帮助程序存储库,并且只将它们用作主存储库中的私有成员。通过这种方式,您可以拥有一组存储库,每个存储库都负责一些密切相关的实体。

  • 介绍一个能够创建所有通用存储库的Unit of Work(同样在EF 4.1中,这类似于工厂方法DbContext.Set<T>(),其中DbContext是单位工作)然后将此工作单元注入您的派生实例:

    public class ProductProxy : Product
    {
        private IUnitOfWork _unitOfWork;
    
        public ProductProxy(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }  
    
        public Category Category
        {
            get
            {
                // ...
                var productRepo = _unitOfWork.CreateGenericRepo<Product>();
                var categoryRepo = _unitOfWork.CreateGenericRepo<Category>();
                // you can pull out the repos you need and work with them
            }
            set { ... }
        }
    }
    

我更喜欢第二个选项,因为在第一个选项中,您最终可能会进入大型存储库以支持加载所有可能的关系。想一想:Order有OrderItems,OrderItem有Product,Product有Category,Order有Customer,Customer有地址列表,Address有联系人列表等等......

4)(因为你也要求批评)

您是在编写自己的ORM还是正在编写应用程序?你的设计走向一个可能变得非常复杂的方向,你在我看来正在重新发明轮子。如果您计划使用EF或NHibernate(或其他ORM),那么您正在创建已经开箱即用的功能,您只需在其上面添加抽象而不添加任何值。通过动态代理进行延迟加载是透明的,因为您永远不会明确地使用代码中的代理,您始终可以使用POCO实体。它们是不可见的,仅在运行时存在。为什么要开发自己的延迟加载基础架构?

答案 1 :(得分:2)

NHibernate支持通过代理对象(默认情况下使用Castle DynamicProxy)进行延迟加载,这些对象是由存储库操作的类的子类。 NHibernate要求您将成员标记为virtual以支持此方案。我不确定哪个组件在需要时启动加载调用,但怀疑它是代理实例。

我个人认为将成员标记为virtual是延迟加载功能的一个小代价

答案 2 :(得分:0)

我正在做一些非常相似的事情,就我而言,我有产品和产品类别的存储库。存储库外的任何人都不知道如何加载 - 懒惰或其他 - 所以如果你愿意,你可以稍后更改它。在我的情况下,我急切地加载类别,因为它们被访问了很多,我按需加载产品并将它们缓存一小时左右。

答案 3 :(得分:0)

  

我希望我的模特能够保持POCO和   只包含结构   模型。

我想问一下,为什么数据的形状对这些类很重要?

通常,这意味着您只需将我们的数据库架构直接暴露给您的整个应用程序,并在其上面放置一个非常薄的“模型”贴面。

我建议实体包含的数据是私有的,应该封装,行为应该是实体的焦点。

此外,加载一次所需的所有数据更简单,更清晰。如果潜在的数据量过多,那么我建议您分配给一个实体的职责太多,您需要进一步优化模型或添加机制以根据您所在的上下文检索实体使用它。

我的意思是,如果您要查询显示数据,则需要暴露一些数据集。您可以在另一个上下文中查询类似但不同的数据集。更进一步,如果您需要执行操作或修改,您的实体需要公开特定的行为。但是在不同的上下文中 - 当您检索实体以执行不同的操作或修改时,您需要暴露不同的特定行为。

尝试将所有这些不同的用途组合成一个单一的合同或界面,或多或少地导致上帝对象实体。

您只需要为某些显示和某些操作添加类别集合。它只会导致疼痛,试图让它一直暴露在外。