将一个实体映射到多个表

📅 2026/7/3 1:17:10 👁️ 阅读次数 📝 编程学习
将一个实体映射到多个表

在数据库设计中这常被称作垂直分割。还是通过例子来看具体实现。我们给产品类增加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();