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

Cri*_*oma 10 c# entity-relationship design-patterns lazy-loading repository

我有一个处理产品和产品类别的应用程序.对于这些中的每一个,我都使用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; }
}
Run Code Online (Sandbox Code Playgroud)

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

// 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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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


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

Sla*_*uma 5

一些评论和我的意见:

1)

我是否应该只拥有Product类中的类别ID,并使用单独的产品类别存储库来加载类别?

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

2)

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

不确定这究竟意味着什么.(我希望看到一个代码片段,你是如何做到这一点的.)但我的猜测是,在你的派生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?
}
Run Code Online (Sandbox Code Playgroud)

好吧,现在加载类别显然是个问题,因为IProductRepository没有方法可以访问它们.

3)

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

你ProductRepository和CategoryRepository看起来像一个普通的宝库,它是只为一个单一的实体类型(EF 4.1,这将是类似于负责的情况下,DbSet<T>这里TProductCategory分别).

我会避免在这些存储库之间有引用,因为每当你添加新的实体或导航属性时,这可能会导致复杂的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 { ... }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

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