EF是一个ORM工具,映射永远是最核心的部分。所以接下来详细介绍Code First模式下EF的映射配置。
通过Code First来实现映射模型有两种方式Data Annotation和Fluent API。
Data Annotation需要在实体类的属性上以Attribute的方式表示主键、外键等映射信息。这种方式不符合解耦合的要求所以一般不建议使用。
第二种方式就是要重点介绍的Fluent API。Fluent API的配置方式将实体类与映射配置进行解耦合,有利于项目的扩展和维护。
Fluent API方式中的核心对象是DbModelBuilder。
在重写的DbContext的OnModelCreating方法中,我们可以这样配置一个实体的映射:
1
2
3
|
protected
override
void
OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity
base
.OnModelCreating(modelBuilder);
|
1
|
modelBuilder.Entity
"Product"
,
"dbo"
);
|
配置类对应于数据库中的表名,并指定表的所有者:
如果不指定表的所有者可以这样写
1
|
modelBuilder.Entity
"Product"
);
|
在默认约定的情况下,Entity Framework Code First创建的列名与类的属性名相同,可以根据需要进行重新指定类属性与列名之间的映射关系。
1
|
modelBuilder.Entity
"ProductId"
);
|
将ProductID改为ProductId.
1
2
3
|
modelBuilder.Entity
.HasColumnName(
"ProductName"
)
.HasMaxLength(100);
|
ProductName是必须的,映射到数据库的名字为ProductName,长度为100.
在默认情况下,int类型的属性生成的列名对应SQL SERVER列int类型;而String类型的属性则对应SQL SERVER列的NVARCHAR类型。若类的字符串类型属性未设置MaxLength,则生成对应的列类型为NVARCHAR(MAX)。 为属性指定对应的SQL SERVER数据类型:
1
2
3
|
modelBuilder.Entity
.HasColumnName(
"UnitPrice"
)
.HasColumnType(
"MONEY"
);
|
Entity Framework Code First的默认主键约束:属性名为[ID]或[类名 + ID]。如在Product类中,Entity Framework Code First会根据默认约定将类中名称为ID或ProductID的属性设置为主键。Entity Framework Code First主键的默认约定也一样可以进行重写,重新根据需要进行设置。
1
|
modelBuilder.Entity
|
若一个表有多个主键时:
1
|
modelBuilder.Entity
new
{ t.KeyID, t.CandidateID });
|
Entity Framework Code First对于int类型的主键,会自动的设置其为自动增长列。但有时我们确实不需是自动增长的,可以通过以下方式进行取消自动增长。
1
2
3
4
|
modelBuilder.Entity
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
//取消自动增长
modelBuilder.Entity
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
//将ProductId设置为自动增长
|
在Product类中,UnitPrice表示单价,对于价格类的字段,我们通常会希望其保留2为小数。这时可以使用Fluent API进行设置
1
2
3
|
modelBuilder.Entity
.HasColumnName(
"UnitPrice"
)
.HasPrecision(18, 2);
|
在类中,如果有一些属性不需要映射到对应生成的数据表中,可以通过以下方式设置。
1
|
modelBuilder.Entity
|
在使用Fluent API进行Entity Framework Code First数据库映射时,除了以上的在重写OnModelCreating方法中直接对Entity进行配置之外,也可以对Configurations进行配置。这时可以先写一个单独的类,将数据表的全部映射要求都写在构造函数中。
类要继承
EntityTypeConfiguration,然后再构造函数中添加映射,最后
1
|
modelBuilder.Configurations.Add(
new
T());
|
关联 1-1关联 Fluent API设置实体类生成的表引用与被引用通过WithRequiredPrincipal、WithRequiredDependent及WithOptionalPrincipal、WithOptionalDependent来设置,使用Principal属性的实体类将被另外的实体类生成的表引用,使用Dependent属性的实体类将引用另外的实体类。 这里说明一下 WithRequiredDependent 和 WithOptional(i => i.Product)是等价的;
//第一组(两条效果完全相同)
1
2
|
HasRequired(p => p.WarrantyCard).WithRequiredDependent(i => i.Product);
HasRequired(p => p.WarrantyCard).WithOptional(i => i.Product);
|
WithRequiredPrincipal 和 WithRequired是等价的
//第二组(两条效果完全相同)
1
2
|
HasRequired(p => p.WarrantyCard).WithRequiredPrincipal(i => i.Product);
HasOptional(p => p.WarrantyCard).WithRequired(i => i.Product);
|
这里新登场角色是和发票,发票有自己的编号,有些产品有发票,有些产品没有发票。我们希望通过产品找到发票而又不需要由发票关联到产品。
1
2
3
4
5
6
|
public
class
Invoice
{
public
int
Id {
get
;
set
; }
public
string
InvoiceNo {
get
;
set
; }
public
DateTime CreateDate {
get
;
set
; }
}
|
产品类新增的属性如下:
1
2
|
public
virtual
Invoice Invoice {
get
;
set
; }
public
int
? InvoiceId {
get
;
set
; }
|
可以使用如下代码创建Product到Invoice的关联
1
2
3
4
5
6
7
8
9
|
public
class
ProductMap : EntityTypeConfiguration
{
public
ProductMap()
{
ToTable(
"Product"
);
HasKey(p => p.Id);
HasOptional(p => p.Invoice).WithMany().HasForeignKey(p => p.InvoiceId);
}
}
|
HasOptional表示一个产品可能会有发票,WithMany的参数为空表示我们不需要由发票关联到产品,HasForeignKey用来指定Product表中的外键列。
还可以通过WillCascadeOnDelete()配置是否级联删除,这个大家都知道,就不多说了。
运行迁移后,数据库生成的Product表外键可为空(注意实体类中表示外键的属性一定要为Nullable类型,不然迁移代码不能生成)。
单向1 - *关联(不可为空)
为了演示这个关联,请出一个新对象合格证,合格证有自己的编号,而且一个产品是必须有合格证。
1
2
3
4
5
6
|
public
class
Certification
{
public
int
Id {
get
;
set
; }
public
string
Inspector {
get
;
set
; }
}
|
1
2
|
public
virtual
Certification Certification {
get
;
set
; }
public
int
CertificationId {
get
;
set
; }
|
public class ProductPhoto { public int Id { get; set; } public string FileName { get; set; } public float FileSize { get; set; } public virtual Product Product { get; set; } public int ProductId { get; set; } }
1
|
public
virtual
ICollection
get
;
set
; }
|
public class ProductMap : EntityTypeConfiguration{ public ProductMap() { ToTable("Product"); HasKey(p => p.Id); HasMany(p => p.Photos).WithRequired(pp => pp.Product).HasForeignKey(pp => pp.ProductId); } }
public class ProductPhotoMap : EntityTypeConfiguration{ public ProductPhotoMap() { ToTable("ProductPhoto"); HasKey(pp => pp.Id); HasRequired(pp => pp.Product).WithMany(p => p.Photos).HasForeignKey(pp => pp.ProductId); } }
1
2
3
4
5
6
|
public
class
Tag
{
public
int
Id {
get
;
set
; }
public
string
Text {
get
;
set
; }
public
virtual
ICollection
get
;
set
; }
}
|
public virtual ICollectionTags { get; set; } public class ProductMap : EntityTypeConfiguration { public ProductMap() { ToTable("Product"); HasKey(p => p.Id); HasMany(p => p.Tags).WithMany(t => t.Products).Map(m => m.ToTable("Product_Tag_Mapping")); } }