我们先从技术角度考虑仓储的问题。实体框架(EntityFramework)中,操作数据库是非常简单的:在ObjectContext中使用LINQ to Entities即可完成操作。开发人员也不需要为事务管理而操心,一切都由EF包办。与原本的ADO.NET以及LINQ to SQL相比,EF更为简单,LINQ to Entities的引入使得软件开发变得更为“领域化”。
下面的代码测试了持久化一个 Customer实体,并从持久化机制中查询这个Customer实体的正确性。从代码中可以看到,我们用了一种很自然的表达方式,表述了“我希望查询一个名字为Sunny的客户”这样一种业务逻辑。
[TestMethod]
public void FindCustomerTest()
{
Customer customer = Customer.CreateCustomer("daxnet", "12345",
new Name { FirstName = "Sunny", LastName = "Chen" },
new Address(), new Address(), DateTime.Now.AddYears(-29));
using (EntitiesContainer ec = new EntitiesContainer())
{
ec.Customers.AddObject(customer);
ec.SaveChanges();
}
using (EntitiesContainer ec = new EntitiesContainer())
{
var query = from cust in ec.Customers
where cust.Name.FirstName.Equals("Sunny")
select cust;
Assert.AreNotEqual(0, query.Count());
}
}
如果你需要实现的系统并不复杂,那么按上面的方式添加、查询实体也不会有太大问题,你可以在 ObjectContext中随心所欲地使用LINQ to Entities来方便地得到你需要的东西,更让人兴奋的是,.NET 4.0允许支持并行计算的PLINQ,如果你的计算机具有多核处理器,你将非常方便地获得效率上的提升。然而,当你的架构需要考虑下面几个方面时,单纯的 LINQ to Entities方式就无法满足需求了:
于是,也就回到了上篇博客中我描述的问题:仓储不是Data Object,也不仅仅是进行数据库CRUD操作的Data Manager,它承担了解耦领域模型和技术架构的重要职责。为了完美地解决上面提到的问题,我们仍然采用领域驱动设计中仓储的设计模式,而将实体框架作为仓储的具体实现部分。在详细介绍仓储的设计与实现之前,让我们回顾一下上文最后部分我提到的那个仓储的接口:
public interface IRepository<TEntity>
where TEntity : EntityObject, IAggregateRoot
{
void Add(TEntity entity);
TEntity GetByKey(int id);
IEnumerable<TEntity> FindBySpecification(Func<TEntity, bool> spec);
void Remove(TEntity entity);
void Update(TEntity entity);
}
在本文的案例中,仓储是这样实现的:
从上面的代码可以看到,EdmRepository将实体框架抽象到ObjectContext这一层,这也使我们没法通过LINQ to Entities来查询模型中的对象。幸运的是,ObjectContext为我们提供了一系列函数,用以实现实体的CRUD。为了使用这些函数,我们需要知道与实体相关的EntitySetName,为此,我定义了一个AggregateRootAttribute,并将其应用在聚合根上,以便在对实体进行操作的时候,能够正确地获得EntitySetName。类似的代码如下:
回头来看EdmRepository的构造函数,在构造函数中,我们使用.NET的反射机制获得了定义在聚合根类型上的EntitySetName
通过这个配置片段我们还可以看到,在框架创建针对“客户”实体的仓储实例时,我们案例中的领域模型容器(EntitiesContainer)也以构造器注入的方式,被注射到了EdmRepository的构造函数中。接下来我们做一个单体测试:
总结
总之,仓储的实现可以用下图表述:
回头来看本文刚开始的三个问题:依赖注入可以解决问题1和3,而仓储接口的引入,也使得规约模式的应用成为可能。.NET中有一个泛型委托,称为Func<T, bool>,它可以作为LINQ的where子句参数,实现类似规约的功能。有关规约模式,我将在其它的文章中讨论。
从本文还可以了解到,依赖注入是维持领域模型纯净度的一大利器;另一大利器是领域事件,我将在后续的文章中详述。对于本文开始的第三个问题,也就是仓储实现的可扩展性,将在下篇文章中进行讨论,包括的内容有:事务处理和可扩展的仓储框架的实现。
-----【以下为原文网友评论及回复信息】----- |
[ 2010-2-24 11:22:00 | By: 文野(游客) ] 你好,我同意衡量系统架构优劣度的重要指标,就是领域模型的纯净度这个观点。但我一直有一些疑惑,很多ORM框架,或象上面例子中,给领域对象添加一些Attribute,作为实体在持久化或其它方面的指导,这些Attribute,是不是使得领域模型变得不够纯净了?象上面的[AggregateRoot(“Customers”)],至少从感觉上觉得,它跟领域模型或者说业务领域一点关系都没有。如果要得到更纯净的领域模型,是不是可以加一层领域模型与数据持久模型的映射,这样会不会好点?
以下为blog主人的回复: |
[ 2010-2-26 16:03:00 | By: haojie77 ] "EdmRepository将实体框架抽象到ObjectContext这一层,这也使我们没法通过LINQ to Entities来查询模型中的对象." 从现在的实现方式看, 拥有ObjectContext的CustomerRepository是否被一直缓存起来了? 这是否意味着数据库的链接始终保持着? 目前的EdmRepository只能进行聚合根的CRUD操作, 如果要获得聚合中的其他实体(势必要使用linq to entity的操作, 若不对请加以指正), 要在哪里实现呢? 是否在EdmRepository中追加定义一些specification的查询? (在<<NET.Domain.Driven.Design.with.C#>>中聚合中的其他实体也是从该聚合根的 Repository来获得.) 非常期待您之后的讲座!
以下为blog主人的回复: |
[ 2010-2-28 0:14:00 | By: ruson(游客) ]
按您文中所述,领域模型中用到了仓储,这里项目如果分开的话就会有相互引用,因为仓库实现中会用到领域Domain。虽然用依赖注入可以解决。但觉得这本身已经不太合理了。
以下为blog主人的回复: |
[ 2010-2-28 0:21:00 | By: ruson(游客) ] 没有最好的架构,只有最合理的架构。当系统不复杂且大部分都是对单一表的CURD操作时,领域驱动就变得像贫血模型,感觉是个鸡肋。
以下为blog主人的回复: |
[ 2010-3-1 16:10:00 | By: haojie77 ]
您说的这个Address只是一个Customer的属性. 我想知道的是如何得到某个特殊的在Customer聚合中的实体.
以下为 blog主人的回复:
class Customer |
EntityFramework之领域驱动设计实践【仓储基本实现】
原文:http://blog.csdn.net/zhixiang2010/article/details/18941985