【译者按】 Entity Framework 1.0 发布也有一段时间了,但感觉用的人很少。其中一个很大的原因,也许就是不支持POCO。要知道,Entity Framework 1.0的做法是让你的实体从EF的基类继承而来,这对很多人,特别是崇尚DDD的人来说,那是一副难以下咽的药啊。曾有微软开发人员提供了一个 POCO Adapter,但那究竟不是正规的做法。Visual Studio 2010 和 .NET 4.0 提供了许许多多的新特性,真是让人激动,向往,大有一种回到.NET 1.0 刚出来时的感觉。春天啊(应该是夏天啊),你终于回来了(虽然早晨/晚上还是unseasonably冷)。其中的Entity Framework 4.0版本将提供POCO支持,对很多人来说,这是开始使用Entity Framework的时候了。ADO.NET 团队博客上贴出了一些关于EF和POCO的贴子,非常值得一读。
【原文地址】POCO in the Entity Framework: Part 1 - The Experience
【原文发表日期】 21 May 09 05:46 PM
上个星期,我在《POCO初览》中提到了POCO实体支持是我们加到Entity Framework 4.0中的新功能之一。这个星期,我要对Entity Framework 4.0中POCO支持的细节进行深入讨论。
要讨论的东西很多,包括
- Entity Framework 4.0中总的POCO体验
- POCO的变化跟踪
- 关系修整
- 复杂类型
- 延迟(懒式)装载和显式装载
- 最佳实践
在这个贴子中,我将主要着重于总的体验,这样,你可以马上启用Entity Framework 4.0中的POCO支持。我将使用一个简单的例子做示范,这样,你可以看到在Entity Framework 4.0中使用POCO的感觉。我将使用Northwind数据库,在随后的贴子中,将继续建立在这个例子的基础之上。
第一步: 创建模型,关闭默认的代码生成
虽然POCO允许你以透明持久化的方式编写自己的实体类,但还是有必要“接入”持久性和EF元数据,这样你的POCO实体可以从数据库中复原,以及持久化到数据库中。为实现这个,你还是需要使用实体框架设计器创建一个实体数据模型(Entity Data Model),或者提供跟你在Entity Framework 3.5中生成的完全一样的CSDL, SSDL 和 MSL 元数据文件。所以,首先,我将使用ADO.NET实体数据模型向导(Entity Data Model Wizard)来生成一个EDMX。
- 创建一个类库项目来定义你的POCO类型,我将其命名为NorthwindModel。这个项目与持久性毫不相关,对实体框架没有依赖性。
- 创建一个类库项目,它将包含与持久性相关的代码,我将其命名为NorthwindData。这个项目除了对NorthwindModel项目有依赖外,还对实体框架(System.Data.Entity)有依赖。
- 在NorthwindData 项目中添加新项,加一个名为“Northwind.edmx ”的ADO.NET实体数据模型(这么做将自动在项目中添加对实体框架的依赖)。
- 选择“从数据库生成”,给Northwind数据库建造模型
- 暂时只选Categories和Products这两个你感兴趣的表到你的实体数据模型中。
至此,生成了可以为我所用的实体数据模型,但在开始编写代码前,还有最后一步:关闭代码生成。毕竟你感兴趣的是POCO啊,请去除负责从Northwind.edmx生成基于EntityObject代码的Custom Tool(自定义工具)属性,这将关闭你的模型的相应代码生成。
现在,我们可以编写POCO实体了。
第二步: 编写你的POCO实体
我将为Category和Product编写简单的POCO实体,这些类将加到NorthwindModel项目中去。注意,我在这里展示的,不应该看成是最佳实践,这里的意图只是示范可以工作的最简单的例子。在以后我们将根据需要将对这些例子进行扩展和定制,之后建立在其基础之上,使用Repository和Unit of Work模式做进一步的扩展。
这里是Category实体的样例代码:
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
public byte[] Picture { get; set; }
public List<Product> Products { get; set; }
}
注意,在模型中我既为标量字段定义了属性,也定义了导航属性。模型中的导航属性(Navigation Property)转换成了List<Product>。
类似地,Product实体可以这样编写:
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public int SupplierID { get; set; }
public string QuantityPerUnit { get; set; }
public decimal UnitPrice { get; set; }
public Int16 UnitsInStock { get; set; }
public Int16 UnitsOnOrder { get; set; }
public Int16 ReorderLevel { get; set; }
public bool Discontinued { get; set; }
public Category Category { get; set; }
}
在这个例子中,因为其间的关系,一个Product只允许属于一个Category,因此我们有一个对Category的引用(而不象Category中的导航属性,是一个List<T>集合)。
第三步: 编写你的实体框架上下文
为把所有这些东西结合在一起,我所要做的最后一件事情是,提供一个上下文实现(就象使用默认的代码生成时你得到的ObjectContext实现一样)。上下文(context)是把持久化意识带进你的应用的胶水(glue),它允许你编写查询,将实体复原,以及将变化存回到数据库中去。该上下文将是NorthwindData项目的一部分。
为简单起见,我将我们的上下文类包括在了包含实体类型的同个类库项目中了,但在我们讨论诸如Repository 和Unit of Work等模式时,我们将做这样的设置,即,你将拥有一个纯粹的POCO类库,不带任何一丝与持久性相关的东西。
这里是针对我们场景的简单的上下文实现:
public class NorthwindContext : ObjectContext
{
public NorthwindContext() : base("name=NorthwindEntities",
"NorthwindEntities")
{
_categories = CreateObjectSet<Category>();
_products = CreateObjectSet<Product>();
}
public ObjectSet<Category> Categories
{
get
{
return _categories;
}
}
private ObjectSet<Category> _categories;
public ObjectSet<Product> Products
{
get
{
return _products;
}
}
private ObjectSet<Product> _products;
}
注意,ObjectSet<T> 是Entity Framework 4.0中引进的一个特殊的ObjectQuery<T>。
就这么简单!现在你有了纯粹的POCO实体,一个简单的上下文,允许你编写象这样的查询:
NorthwindContext db = new NorthwindContext();
var beverages = from p in db.Products
where p.Category.CategoryName == "Beverages"
select p;
从数据库中复原的实体是纯粹的POCO实体,你可以对这些实体做变动,将变动保存到数据库中,跟平常的EntityObject或IPOCO实体非常类似。你将得到实体框架提供的所有服务,唯一的区别是,你现在使用的是纯粹的POCO实体。
就我们的简化了的例子而言,尚有无数改进的可能性。但在那之前,我想先把一些基本的问题解决掉。
在使用POCO之前我需要一个实体数据模型么?
是的,Entity Framework 4.0中的POCO支持只是去除了在你的实体类中带特定于持久性的关注的需求而已。但还是需要你提供CSDL/SSDL/MSL (总称EDMX)元数据,这样实体框架才能够将你的实体和元数据结合起来,以允许你访问数据。我们还在另外开发一个东西,它允许你做真正的“代码优先(code-first)”的开发,而不需要预先定义好的EDMX模型。这个功能的社区预览版将在几个月内在网上发布,一有机会我们就会将其加入实体框架。一如既往,你的反馈对我们极其有用。
我始终都只能手写这些实体和上下文么?
不, Entity Framework 4.0中有一个非常强大和灵活的代码生成机制,该机制是基于T4之上的(Alex曾在这里的博客上讨论过)。你可以提供你自己的模板,允许你按照你自己选择的方式建造实体。我们还在努力,以提供一些标准的模板,可为你生成POCO实体。
使用POCO实体时,元数据是怎么映射的?
在Entity Framework 3.5中,基于EntityObject和IPOCO的实体都是依赖着使用映射特性(attributes),对实体类型和属性进行修饰和映射到概念性模型中对应的元素的。Entity Framework 4.0 引入了基于约定(convention)的映射,以允许不用显式的修饰,就可将实体类型,属性,复杂类型和关系映射到概念性模型。一个简单的规则是,在你的POCO类中使用的实体类型名称,属性名称,和复杂类型名称必须匹配那些在概念性模型中定义了的相应名称。命名空间的名称不在考虑之中,类中的命名空间定义和概念性模型中的命名空间定义不必相符。
我的实体类中的所有属性都需要有公开的getters和setters么?
你可以在你的POCO类型的属性上使用任何访问控制修饰符(access modifier),只要被映射的任何属性都不是虚拟的,以及你不需要局部信任(partial trust)支持。在局部信任下运行时,对你的实体类的访问控制修饰符的可见性有一些特定的要求。我们将对在涉及局部信任时,所支持的完整的访问控制修饰符集提供详细的文档。
对基于集合的导航属性都支持哪些集合类型?
任何属于ICollection<T>的类型都是支持的。如果你对ICollection<T>类型的字段不以具体的类型初始化的话,那么从数据库中复原实体集合时, 将提供List<T>。
我可以有单向的关系么?例如,我想在我的Product类中有一个 Category 属性,但在Category类中我不想要一个Products集合。
是的,这是支持的。唯一的限制是,你的实体类型必须反映模型中所定义的东西。如果你不想拥有对应于关系的某一边的导航属性的话,那么你需要从模型中将其完全剔除。
POCO支持延迟(懒式)装载么?
是的,POCO是通过使用代理类型来支持延迟(懒式)装载的,这些代理类型是在你的POCO类之上提供自动的懒式装载行为的。在讨论到延迟装载时,我们会对此做详述,在那之前,知道一下我们也支持通过使用“Include”来实现的早期装载(eager loading),象这样:
var beverageCategory = (from c in context.Categories
.Include("Products")
where c.CategoryName == "Beverages"
select c).Single();
如果我要使用延迟(懒式)装载的话,我不用这么做, 在讨论代理时我们会对此进行讨论。
修整(fix-up)关系
要了解有2种类型的修整:1)查询时的修整, 2) 对你的实体/关系进行改动时的修整。
查询时的修整
查询时的修整是在同一个ObjectContext上使用不同的查询来装载相关的实体时发生的。例如,如果我查询了一个分类实例“Beverages(饮料)”,之后又查询一个产品实例“Chai”(是在Beverages分类中的),我想要chai.Category 指向Beverages分类实例,不用我做额外的工作。
这在今天是自动的,而且已经工作。
对你的实体/关系进行改动时的修整
这是在添加/去除与另一个实体相关的实体或者改变关系时两个实体间的关系修整。设想一下这样的例子:在我创建一个名叫“Diet Chai”的新产品时,我想要将其与Beverages 分类相关联。
在Entity Framework 4.0 Beta1 中,在这种情形下,POCO实体得不到自动的关系修整。在处理实体间的关系变动时,你需要确认你的POCO类型包含了在关系两头正确处理关系修整的逻辑。
例如,在我的Northwind例子中,我在Category上定义了如下的方法,以支持添加/去除订单。
public void AddProduct(Product p)
{
if (Products == null)
{
Products = new List<Product>();
}
if (!Products.Contains(p))
{
Products.Add(p);
}
p.Category = this;
}
总的来说,使用象这样的模式来支持添加/去除相关项,要比使用关系集合来添加/去除相关实体为好。你还可以选择将实际由EF支持的集合的getter/setter变成私有或内部访问控制,来支持更为细颗粒的访问,但就象前面提到的那样,其中一些东西取决于你是否需要局部信任支持这样的需求。
我们在这个“简短的综述”中对总的POCO体验做了一番讨论。要讨论的东西还有很多很多,下个星期我将继续这个讨论。与此同时,请看一下附件中的完整解决方案,如果你对我们在这里讨论的所有概念的相应例程代码感兴趣的话。请记住,你必须在运行这个例程的机器上安装一个本地Northwind数据库。
在下一个贴子中,我们将看一下复杂类型,变化跟踪,代理,懒式装载和早期装载。在那之前,请阅读一下MSDN这里的关于POCO的文档,以了解Entity Framework 4.0中的POCO支持的细节。
请告知我们你们的想法!
Faisal Mohamood
Entity Framework的Program Manager