存储库模式:模型关系的实现和延迟加载
我有一个处理产品和产品类别的应用程序。对于其中的每一个,我都使用 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 类中,只有在需要/访问(延迟加载)时才应加载 ProductCategory 类型的名为 Category 的属性。我希望我的模型保留 POCO 并且仅包含模型的结构。
我采取的方法正确吗?
我是否应该在 Product 类中只拥有类别的 ID,并使用单独的产品类别存储库来加载该类别?
实现延迟加载和关系
目前,我的存储库接口的实现返回一个类型的对象,该对象扩展了 Product 类型,并支持通过存储库实例进行延迟加载。
谁应该负责加载产品类别?
我对产品和类别存储库应如何交互以实现延迟加载感兴趣?它们应该互相引用,还是应该有一个包含两个子存储库的主存储库并将其传递给我的扩展模型类型?
您会采取什么方法?(欢迎任何建议和批评)
我应该注意,我希望应用程序是可扩展的,并且存储库和模型本身的所有接口都将位于单独的程序集中。这意味着扩展程序将无法直接访问模型类定义。
I have an application which deals with products and product categories. For each of these I have models defined using 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 application uses a repository to access these models
// 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);
}
In the Product class, the property named Category of type ProductCategory should be loaded only when it is needed/accessed (lazy-loading). I want my models to remain POCO and contain only the structure of the model.
Am I taking the right approach ?
Should I have only the ID of the category in the Product class and use a separate repository for product categories to load the category ?
Implementing lazy-loading and relationships
For now my implementation of the repository interface returns an object of a type which extends the Product type and has support for lazy-loading through the repository instance.
Who should be responsible for loading the product category ?
I'm interested in how the product and category repositories should interact to achieve the lazy-loading ? Should they reference each other or should I have a main repository with the two sub repositories and pass that to my extended model types ?
What approach would you take ? (any suggestions and criticism is welcomed)
I should note that I want the application to be extensible and all the interfaces for the repositories and the models themselves will be in a separate asembly. This means that the extender will not have direct access to the model class definition.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
一些评论和我的观点:
1)
不。您使用 ORM(至少我假设您这样做)能够通过类实例之间的引用来建模关系,而不是通过您使用的 ID 以关系方式进行查询。将您的想法带到最后的结果意味着您从模型类中删除所有导航属性,并且仅具有标量属性,其中一些属性充当对象之间的键。这只是 ORM 中的“R”。
2)
不确定这到底是什么意思。 (我想看看你是如何做到这一点的代码片段。)但我的猜测是,在你的派生
Product
类中,你以某种方式注入了对存储库的引用,如下所示:嗯,这显然是一个现在加载类别时出现问题,因为
IProductRepository
没有访问它们的方法。3)
您的 ProductRepository 和 CategoryRepository 看起来像通用存储库的实例,仅负责单个实体类型(在 EF 4.1 中,这类似于
DbSet
,其中T
是分别是产品
或类别
)。我会避免在这些存储库之间进行引用,因为每当您添加新实体或导航属性时,这可能会导致大量复杂的存储库引用。
我看到另外两个选项:
(基本上是您已经提到的)拥有一个负责
Product
和Category
的存储库。您仍然可以拥有通用存储库,但我更愿意将它们视为内部辅助存储库,并且仅将它们用作主存储库内的私有成员。这样你就可以拥有一组存储库,每个存储库负责一些密切相关的实体。引入一个
工作单元
,它能够创建所有通用存储库(同样在 EF 4.1 中,这类似于工厂方法DbContext.Set()< /code> 其中
DbContext
是工作单元),然后将此工作单元注入到您的派生实例中:我更喜欢第二个选项,因为在第一个选项中您可能最终会得到在巨大的存储库支持加载所有可能的关系。想想:订单有订单项目,订单项目有产品,产品有类别,订单有客户,客户有地址列表,地址有联系人列表等等......
4) (因为您也在寻求批评)
您是在编写自己的 ORM 还是在编写应用程序?你的设计走向了一个可能变得非常复杂的方向,在我看来,你正在重新发明轮子。如果您计划使用 EF 或 NHibernate(或其他 ORM),那么您正在创建已经可用的开箱即用的函数,您只需将抽象放在其上,这不会增加任何价值。通过动态代理进行延迟加载是透明的,因为您从不明确地使用代码中的这些代理,而始终使用 POCO 实体。它们是不可见的并且仅在运行时存在。为什么要开发自己的延迟加载基础设施?
A few remarks and my opinions:
1)
No. You are using an ORM (at least I assume you do) to be able to model relationships by references between class instances and not by IDs you are using then to query in a relational fashion. Taking your idea to the last consequence would mean that you remove all navigation properties at all from the model classes and have only scalar properties and some of them act as keys between objects. That's only the "R" in ORM.
2)
Not sure what this means exactly. (I would like to see a code-snippet how you do that.) But my guess is that in your derived
Product
class you inject somehow a reference to the repository, like so:Well, it's obviously a problem now to load the categories since
IProductRepository
doesn't have methods to access them.3)
Your ProductRepository and CategoryRepository look like instances of a generic repository which is only responsible for a single entity type (in EF 4.1 this would be similar to
DbSet<T>
whereT
isProduct
orCategory
respectively).I would avoid to have references between those repositories as this may end up in a hell of complex repo-references whenever you add new entities or navigation properties.
I see two other options:
(Basically what you already mentioned) Having a repository which is responsible for
Product
andCategory
together. You could still have your generic repositories but I would consider them more as internal helper repos and would only use them as private members inside of the main repository. This way you can have a group of repositories, each of them is responsible for some closely related entities.Introduce a
Unit of Work
which is able to create all of your generic repositories (again in EF 4.1 this would be something like the factory methodDbContext.Set<T>()
whereDbContext
is the unit of work) and then inject this Unit of Work into your derived instances:I would prefer the second option because in the first option you may end up in huge repositories to support loading of all possible relationships. Think of: Order has OrderItems, OrderItem has Product, Product has Category, Order has Customer, Customer has list of Addresses, Address has list of Contact Persons and so on and so forth...
4) (because you were also asking for criticism)
Are you writing your own ORM or are your writing an application? Your design goes into a direction which may become very complex and you are reinventing the wheel in my opinion. If you planning to use EF or NHibernate (or other ORM's) then you are creating functions which are already available out of the box, you only put abstractions on top of it which add no value. Lazy loading through dynamic proxies happens transparently in the sense that you never work explicitely with those proxies in your code, you always work with your POCO entities. They are invisible and exist only at runtime. Why do you want to develop your own lazy loading infrastructure?
NHibernate 通过代理对象支持延迟加载(默认使用 Castle DynamicProxy),这些代理对象是以下类的子类由存储库操纵。 NHibernate 要求您将成员标记为
virtual
以支持这种情况。我不确定哪个组件在需要时启动加载调用,但怀疑它是代理实例。我个人认为将成员标记为
virtual
对于延迟加载功能来说是一个很小的代价NHibernate supports lazy loading through proxy objects (using Castle DynamicProxy by default) that subclass the classes that are manipulated by the repository. NHibernate requires you to mark members as
virtual
to support this scenario. I'm unsure as to what component initiates the load call when required, but suspect it's the proxy instance.I personally think that marking members as
virtual
is a small price for lazy loading functionality我正在做一些非常相似的事情,就我而言,我有产品和产品类别的存储库。存储库外部没有人知道内容是如何加载的 - 惰性加载或其他任何加载 - 因此您可以稍后根据需要进行更改。就我而言,我急切地加载类别,因为它们的访问量如此之大,并且我按需加载产品并将它们缓存一个小时左右。
I'm doing something very similar and in my case I have repositories for both product and product category. No one outside of the repository knows how things are loaded - lazy or whatever - so you can change it later if you want. In my case, I eagerly-load the categories since they're accessed so much, and I load-on-demand the products and cache them for an hour or so.
我会问,为什么数据的形状对于这些类很重要?
一般来说,这意味着您只需将我们的数据库模式直接暴露给整个应用程序,并在其顶部覆盖一层非常薄的“模型”。
我建议实体包含的数据是私有的并且应该被封装,并且行为应该是实体的焦点。
此外,一次性加载您需要的所有数据更加简单、清晰。如果潜在的数据量太多,那么我建议您分配给一个实体太多的职责,您将需要进一步完善您的模型,或者添加一种机制来根据您所在的上下文检索实体使用它。
我的意思是,如果您要查询以显示数据,则需要公开一些数据集。您可以在另一个上下文中查询相似但不同的数据集。更进一步,如果您需要执行操作或修改,您的实体需要公开特定的行为。但在不同的上下文中 - 当您检索实体以执行不同的操作或修改时,您需要公开不同的特定行为。
尝试将所有这些不同的用途组合成一个契约或接口,或多或少会导致上帝对象实体。
您只需要该类别集合来进行某些显示和某些操作。试图一直暴露它只会导致疼痛。
I would ask, why is the shape of the data the important about these classes?
Generally, this means you are simply exposing our database schema directly to your entire application, with a very thin veneer of a "model" on top of it.
I'd suggest that the data an entity contains is private and should be encapsulated, and that behavior is what should be the focus of the entities.
Furthermore, it is simpler and more clear to load all the data you will need at once. If the potential amount of data is too much, then I would suggest that you have too many responsibilities assigned to one entity and you will need to either further refine your model or add a mechanism to retrieve an entity based on the context in which you are using it.
What I mean is, if you are querying to display data, you need some set of data exposed. You may query a similar but different set of data in another context. Going further, if you need to perform an action or modification, your entity needs to expose a particular behavior. But in a different context - when you are retrieving the entity to perform a different action or modification, then you need a different particular behavior exposed.
Trying to combine all these different uses into one single contract or interface leads to, more or less, god object entities.
You only need that collection of Categories for certain displays and certain actions. It simply causes pain trying to have it exposed all the time.