在数据库设计中这常被称作垂直分割。还是通过例子来看具体实现。我们给产品类增加2个新属性:
1 2 3 4 5 6 7 8 9 | publicclassProduct
{
publicintId {get;set; }
publicstringName {get;set; }
publicstringDescription {get;set; }
//new property
publicfloatPrice {get;set; }
publicfloatWeight {get;set; }
}
|
我们希望将新属性存储在另一张数据表中,可以按如下方式配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | publicclassProductMap : EntityTypeConfiguration<Product>
{
publicProductMap()
{
Map(m =>
{
m.Properties(t =>new{ t.Id, t.Name, t.Description });
m.ToTable("Product");
})
.Map(m =>
{
m.Properties(t =>new{ t.Id, t.Price, t.Weight });
m.ToTable("ProductDetail");
});
HasKey(p => p.Id);
}
}
|
代码一目了然,分开指定属性和相应的表即可。生成的迁移代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | CreateTable(
"sample.Product",
c =>new
{
Id = c.Int(nullable:false, identity:true),
Name = c.String(),
Description = c.String(maxLength: 200),
})
.PrimaryKey(t => t.Id);
CreateTable(
"sample.ProductDetail",
c =>new
{
Id = c.Int(nullable:false),
Price = c.Single(nullable:false),
Weight = c.Single(nullable:false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("sample.Product", t => t.Id)
.Index(t => t.Id);
|
是不是很眼熟,对!和之前配置1 - 1映射生成的迁移代码一模一样。当然生成的查询语句也是一样的。
将两个实体映射到一张表
我们把上一个例子中给Product增加的属性独立出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | publicclassProduct
{
publicintId {get;set; }
publicstringName {get;set; }
publicstringDescription {get;set; }
publicvirtualProductDetail ProductDetail {get;set; }
}
publicclassProductDetail
{
publicintId {get;set; }
publicfloatPrice {get;set; }
publicfloatWeight {get;set; }
publicvirtualProduct Product {get;set; }
}
|
现在我们有2个实体类,接下来的配置将把它们映射到一张表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | publicclassProductDetailMap : EntityTypeConfiguration<ProductDetail>
{
publicProductDetailMap()
{
HasKey(pd=>pd.Id).HasRequired(pd => pd.Product).WithRequiredPrincipal(p=>p.ProductDetail);
ToTable("Product");
}
}
publicclassProductMap : EntityTypeConfiguration<Product>
{
publicProductMap()
{
HasKey(p => p.Id);
ToTable("Product");
}
}
|
生成的迁移代码可以看出,两个实体将被保存到一张表:
1 2 3 4 5 6 7 8 9 10 11 | CreateTable(
"dbo.Product",
c =>new
{
Id = c.Int(nullable:false, identity:true),
Name = c.String(),
Description = c.String(maxLength: 200),
Price = c.Single(nullable:false),
Weight = c.Single(nullable:false),
})
.PrimaryKey(t => t.Id);
|
映射部分就到这里了。休息下吧。
中场休息
借中场休息时间鄙视一下那些转载不保留原链接的网站,尤其像numCTO这种。
变更跟踪
变更跟踪指的是对缓存于EF Context中的实体的状态的跟踪与改变。所以了解变更跟踪先看了解一下实体在EF Context中的几种状态。下面是国外某网站看到的一幅很不错的图,直接拿过来用了。
![]()
图3. EF Context中实体状态 来源
支持变更跟踪最关键的一点是实体必须有主键(如前文介绍通过Fluent API的HasKey<TKey>方法指定主键)。这样实体才能被EF Context这个缓存容器进行维护,并与数据库中相应的条目实现一一对应来支持增删改查。
变更跟踪是默认启用的,可以通过配置DbContext来关闭这个功能,如下代码:
1 | context.Configuration.AutoDetectChangesEnabled =false;
|
注意:
一般来说不建议关闭变更跟踪,除非是只读(只读情况下用AsNoTracking获取实体并自己做缓存应该更好)。
在关闭变更跟踪的情况下,可以通过如下方法手动调用一次变更检测(或者用下文将介绍的手动状态改变),这样后续的SavaChanges操作才能正确完成。
1 | context.ChangeTracker.DetectChanges();
|
另外要注意的一点是,变更跟踪只能在一个上下文内有效。即如果有两个DbContext的实例,两个DbContext各自作用域内的变更跟踪是独立的。
除了使用自动变更跟踪,在对性能要求极端的情况下,也可以手动控制实体的状态(另一种情况是实体本不在当前Context中,要加入当前Context控制下必须手动完成)。
与实体变更控制最密切的就是DBEntityEntry类,这个类的对象正是通过前文介绍的DbContext的Entry<T>方法获得的。DBEntityEntry最重要的属性就是获取实体状态的State属性。
1 2 3 | varentry = dbCtx.Entry(student);
Console.WriteLine("Entity State: {0}", entry.State );
context.Entry(student).State = EntityState.Deleted;
|
上面几行代码展示了查询与修改EF Context中实体状态的方法。
最后这段综合的代码示例演示了在关闭变更跟踪的情况下,手动修改实体状态实现更新。
1 2 3 4 5 6 | context.Configuration.AutoDetectChangesEnabled =false;
varstudent = context.Set<Student>().FirstOrDefault(s => s.StudentName =="张三");
student.StudentName ="王五";
varstuEntry = context.Entry(student);
stuEntry.State = EntityState.Modified;
context.SaveChanges();
|
AsNoTracking
对于只读操作,强烈建议使用AsNoTracking进行数据获取,这样省去了访问EF Context的时间,会大大降低数据获取的时间。
1 | varstudent = context.Set<Student>().AsNoTracking().FirstOrDefault(s => s.StudentName =="王五");
|
由于没有受EF Context管理,对于这样获取到的数据,更新的话需要先Attach然后手动修改状态并SaveChanges。
1 2 3 4 5 | student.StudentName ="张三";
context.Set<Student>().Attach(student);
varstuEntry = context.Entry(student);
stuEntry.State = EntityState.Modified;
context.SaveChanges();
|