【原文地址】Code Only Enhancements
【原文发表日期】 03 August 09 11:11
自从第一个预览版发布之后,我们一直在奋力增强Code Only功能。
在下一个版本中,你将能够指定本贴的余下部分将依次对这些特性进行详述。
你现在可以注册倒转关系,即一个导航属性到另一个导航属性的倒转(inverse)关系,象这样:
builder.RegisterInverse(
(Customer c) => c.Orders,
(Order o) => o.Customer)
);
这代码表示Customer.Orders是Order.Customer关系的另一头。把order1 加到customer1.Orders集合中去,与把order1.Customer设置成customer1具有同等的效果。
你还可以指定属性的细节,即象Nullability(可null性), MaxLength(最大长度), Precision(精度)等等这样的东西,象这样:
var customerConfig = new EntityConfiguration<Customer>();
// 我们可以推断出ID是主键
// 但无法推断出它在插入时是在数据库生成的
customerConfig.ForProperty(c => c.ID)
.Identity();
customerConfig.ForProperty(c => c.Name)
.MaxLength(100)
.NonUnicode();
customerConfig.ForProperty(c => c.Website)
.MaxLength(200)
.Nullable()
builder.Configure(customerConfig);
这把Customer类型配置成:
这些细节是针对概念模型Conceptual Model,即CSDL)的,从那里,也传到数据库(即SSDL)。
你可以创建一个EntityConfiguration<T>的继承类来封装所有这些配置。
例如:
public class CustomerConfig: EntityConfiguration<Customer>
{
public CustomerConfig(){
ForProperty(c => c.ID)
.Identity();
ForProperty(c => c.Name)
.MaxLength(100)
.NonUnicode();
ForProperty(c => c.Website)
.MaxLenght(200)
.Nullable();
}
}
我们建议你创建象这样的类,而不是配置EntityConfiguration<>,因为封装的好处。
在你使用Configure<T>(..) 时,实体框架为你推断出默认的映射,继承策略(TPH)和表名。
但如果你要指定表名,你可以这么做:
var customerConfig = new EntityConfiguration<Customer>();
// 象上面那样配置细节
...
// 用一个特定的表名注册配置
builder.Tables[“dbo.Custs”] = customerConfig;
如果你需要对映射更多的控制(例如,要映射到一个现有的数据库或者使用企业的命名规则),那么你可以象这样指定映射:
EntityMap<Customer> customerMap =
Map.OfType<Customer>(
c => new {
cid = c.ID,
c.Name,
csite = c.Website
}
);
这个映射表明,ID是映射到‘cid’字段,Name属性是保存到‘Name’字段,而Website是映射到‘csite’ 字段的。
没有被引用的属性是不会被持久化的,就象使用实体框架默认的代码生成时生成的部分类上的属性一样。
你甚至也可以使用LINQ内涵句法来指定同样的事情:
EntityMap<Customer> customerMap =
from c in Map.OfType<Customer>()
select new {
cid = c.ID,
c.Name,
csite = c.Website
};
在配置完映射后,你还可以象这样在映射上指定facets:
customerMap.ForProperty(c => c.ID)
.Identity();
customerMap.ForProperty(c => c.Name)
.MaxLength(100)
.NonUnicode();
customerMap.ForProperty(c => c.Website)
.MaxLenght(200)
.Nullable();
最后一步是指派数据表映射。
builder.Tables[“dbo.Custs”] = customerMap;
至此,我们为Customer类指定了自定义表,映射和自定义facets。
CodeOnly默认所用的继承策略是Table Per Hierarchy (每个类分层结构共用一个数据表)(或 TPH)。
但如果你需要一个不同的策略,你需要动手配置相应映射
设想一下,如果你要映射三个类:Vehicle , Car 和 Boat,其中Car 和 Boat是从Vehicle继承而来,而Vehicle本身是个抽象类。
如果你想要使用TPH做映射,你可以这么做:
var vehicleMap =
Map.OfTypeOnly<Vehicle>(
v => new {
vid = v.ID,
v.Name,
vdesc = v.Description
v.MaxPassengers,
}
).Union(Map.OfTypeOnly<Car>(
c => new {
vid = c.ID,
c.Name,
vdesc = c.Description
c.MaxPassengers,
trans = c.Transmission,
tspd = c.Topspeed,
ccty = c.EngineCapacity,
ncyld = c.NoCylinder,
discriminator = “CAR”
})
).Union(Map.OfTypeOnly<Boat>(
b => new {
vid = b.ID,
b.Name,
vdesc = b.Description
b.MaxPassengers,
lng = b.Length,
b.HasSail,
b.HasEngine
discriminator = “BOAT”
})
);
builder.Tables[“dbo.vehicles”] = vehicleMap;
在TPH映射中:
如果你想要使用Table Per Type (每个类型一个表) 或TPT来映射同个类分层结构,你该这么做:
builder.Table[“dbo.Vehicles”]=
Map.OfType<Vehicle>(
v => new {
vid = v.ID,
v.Name,
vdesc = v.Description
v.MaxPassengers,
}
);
builder.Tables[“dbo.Cars”] =
Map.OfType<Car>(
c => new {
cid = c.ID,
trans = c.Transmission,
tspd = c.Topspeed,
ccty = c.EngineCapacity,
ncyld = c.NoCylinders,
}
);
builder.Tables[“dbo.Boats”] =
Map.OfType<Boat>(
b => new {
bid = b.ID,
lng = b.Length,
b.HasSail,
b.HasEngine
}
);
在TPT映射中:
你还可以象这样使用TPC来映射这个类分层结构:
builder.Tables[“dbo.Cars”] =
Map.OfTypeOnly<Car>(
c => new {
cid = c.ID,
c.Name,
vdesc = c.Description
c.MaxPassengers,
trans = c.Transmission,
tspd = c.Topspeed,
ccty = c.EngineCapacity,
ncyld = c.NoCylinders,
}
);
builder.Tables[“dbo.Boats”] =
Map.OfTypeOnly<Boat>(
b => new {
bid = b.ID,
b.Name,
description = b.Description
b.MaxPassengers,
lng = b.Length,
b.HasSail,
b.HasEngine
}
);
在TPC映射中:
如果我们看到一个引用(譬如Order.Customer),我们假定其多重性(multiplicity)为0..1。意即,其外键或FK是可null的。
如果我们看到一个集合(譬如Customer.Orders),我们假定其多重性为多(many)。
然后,在注册倒转(inverse)时,我们知道一个关系的两头的多重性,譬如,在上面的例子中,我们知道,每个Order可以有0..1个 Customer,每个Customer可以有多个Order。
所以按约定,共有三种主要的关系类型,我们需要推断出其FK的位置:
0..1 to many –> 按约定,我们把FK放在many一端,所以上面的Customer.Orders例子中, FK放在Orders 表上。
many to many –> 没什么选择,只能引进一个连接表(join table)。
0..1 to 0..1 –> 你可以配置FK应该在什么地方,但不配置的话,我们会引进一个连接表(join table)。
但有时候,引用可以不是0..1,而是1,例如,FK(不管它在什么地方),也许不可以null的。
你可以这样来指定FK不是nullable的:
var orderConfig = builder.Configure<Order>();
orderConfig.RegisterInverse(o => o.Customer, c => c.Orders);
orderConfig.ForProperty(o => o.Customer).NonNullable();
这告诉我们,每个Order正好有一个1个Customer,而每个Customer有多个Orders。
能够区分一个引用是非Nullable,会引进几个新的多重性组合,对此,我们也需要约定:
1 to many –> 按约定,我们把FK放在many的一端, 并且在数据库中将其设置为非nullable。
0..1 to 1 –> 按约定,我们把FK放在1的一端, 并且将其设置为非nullable。
1 to 1 –> 跟0..1 to 0..1一样,我们无法决定该把FK放在何处,所以按约定,我们要引进一个连接表(join table)。
至此,我们为实体的属性创建了映射。
那么导航属性和外键怎么办?
所有的关系类型(除了many to many)可以不用数据库中的连接表(join table)来建模,所以我们允许你象这样,作为EntityMap的一部分来映射外键:
EntityMap<Customer> customerMap =
from c in Map.OfType<Customer>()
select new {
cid = c.ID,
c.Name,
csite = c.Website,
salesPersonFK = c.SalesPerson.ID
};
customerMap.RegisterInverse(c => c.SalesPerson, s => c.Clients);
builder.Tables[“dbo.Custs”] = customerMap;
这指定,Customer.SalesPerson导航属性,以及它的倒转(inverse)SalesPerson.Customers是保存在 dbo.Custs表的salesPersonFK字段中的。因为映射片段把 salesPersonFK 字段映射到了c.SalesPerson.ID上,而 SalesPerson.ID是相关的SalesPerson实体的主键(或者是主键的一部分),当然,外键是指向主键的。
在many to many关系的情形下,你必须有一个连接表,所以按约定,没有映射信息,我们就会产生一个连接表(join table)。
但假如你需要更多的控制,你可以这么做:
var blogPostsMap = new AssociationMap<Blog, Post>(
b => b.Posts
).Map(
(b, p) => new {BlogId = b.ID, PostId = p.ID}
);
builder.Tables[“dbo.BlogPosts”] = blogPostsMap;
这是说, Blog 与 Post间的many to many关系是保存在dbo.BlogPosts表中的,它有2个字段:
Code Only甚至还支持象分割实体这样高级的映射策略:
builder.Tables[“dbo.Customer”] = Map.OfType<Customer>(
c => new {
cid = c.ID,
c.Name,
active = c.IsActive
}
);
builder.Tables[“dbo.CustomerDetails”] = Map.OfType<Customer>(
c => new {
cid = c.ID,
c.Size,
c.Industry
}
);
这是说,Customer实体是分开保存在dbo.Customer和dbo.CustomerDetails两个表中的。
你还可以编写一个类,从EntityMap<T>继承而来,含有所有的映射,facets等等。例如,下面是一个类,包含了Product的配置:
public class ProductMap: EntityMap<Product>
{
public ProductMap{
this.Map( p => new {
pid = p.ID,
pcode = p.Name,
cid = p.Category.ID
});
this.ForProperty(p => p.ID).Identity();
this.ForProperty(p => p.Name).MaxLength(100)
.NonUnicode();
this.ForProperty(p => p.Category).NonNullable();
this.RegisterInverse(p => p.Category,
c => c.Products);
}
}
这是个高度推荐的做法,因为配置Product类型变得容易之极:
builder.Tables[“dbo.Products”] = new ProductMap();
正如你看到的,我们正在计划许多的增强,以允许最核心的场景。
你觉得怎么样?你喜欢这些API么?有什么东西你想要改变的?
一如既往,我们期待听到你的反馈。
Alex James
微软Entity Framework开发团队的Program Manager
本贴是Entity Framework开发团队的透明设计实践的一部分。想了解其工作原理,以及你的反馈是如何被使用的,请参阅 这个贴子。