【配置映射】—Entity Framework实例详解
前两篇博文中的配置属性和配置关系都是配置映射,配置属性是属性的映射,配置关系式关系的映射,本篇从讲讲实体的映射。
首先,配置实体映射到表,使用ToTable方法,它接受两个参数,第一个参数是表的名称,第二个参数是Schema名称。
1: ToTable("Destination", "baga");
一、配置多个实体到一个表
下面是用到的类:
1: public class Blog
2: {
3: public int Id { get; set; }
4: public DateTime Creationdate { get; set; }
5: public string ShortDescription { get; set; }
6: public string Title { get; set; }
7: public virtual BlogLogo BlogLogo { get; set; }
8: }
9:
10: public class BlogLogo
11: {
12: public int Id { get; set; }
13: public byte[] Logo { get; set; }
14: }
映射实体到一个表中,实体需要遵循以下规则:
1. 实体之间必须是一对一的关系。
2. 实体必须共用主键。
使用Fluent API 配置Blog和BlogLogo映射到一个表,使用ToTable方法,如下:
1: modelBuilder.Entity<Blog>().ToTable("Blogs");
2: modelBuilder.Entity<BlogLogo>().ToTable("Blogs");
完整的Demo如下:
1: public class Blog
2: {
3: public int Id { get; set; }
4: public DateTime Creationdate { get; set; }
5: public string ShortDescription { get; set; }
6: public string Title { get; set; }
7: public virtual BlogLogo BlogLogo { get; set; }
8: }
9:
10: public class BlogLogo
11: {
12: public int Id { get; set; }
13: public byte[] Logo { get; set; }
14: }
15:
16: public class BlogConfiguration : EntityTypeConfiguration<Blog>
17: {
18: public BlogConfiguration()
19: {
20: ToTable("Blogs");
21: HasKey(x => x.Id);
22: Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("BlogId");
23: Property(x => x.Title).HasMaxLength(175);
24: HasRequired(x => x.BlogLogo).WithRequiredPrincipal();
25: }
26: }
27:
28: public class BlogLogoConfiguration : EntityTypeConfiguration<BlogLogo>
29: {
30: public BlogLogoConfiguration()
31: {
32: ToTable("Blogs");
33: HasKey(x => x.Id);
34: Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("BlogId");
35: }
36: }
37:
38:
39: public class BlogContext : DbContext
40: {
41: public DbSet<Blog> Blogs { get; set; }
42:
43: protected override void OnModelCreating(DbModelBuilder modelBuilder)
44: {
45: modelBuilder.Configurations.Add(new BlogConfiguration());
46: modelBuilder.Configurations.Add(new BlogLogoConfiguration());
47: base.OnModelCreating(modelBuilder);
48: }
49:
50: public IQueryable<T> Find<T>() where T : class
51: {
52: return this.Set<T>();
53: }
54:
55: public void Refresh()
56: {
57: this.ChangeTracker.Entries().ToList().ForEach(x => x.Reload());
58: }
59: public void Commit()
60: {
61: this.SaveChanges();
62: }
63: }
64:
65: public class Initializer : DropCreateDatabaseAlways<BlogContext>
66: {
67: public Initializer()
68: {
69: }
70:
71: protected override void Seed(BlogContext context)
72: {
73: context.Set<Blog>().Add(new Blog()
74: {
75: Creationdate = DateTime.Now,
76: ShortDescription = "Testing",
77: Title = "Test Blog",
78: BlogLogo = new BlogLogo() { Logo = new byte[0] }
79: });
80: context.SaveChanges();
81: }
82: }
测试程序:
1: [TestMethod]
2: public void ShouldReturnABlogWithLogo()
3: {
4: //Arrange
5: var init = new Initializer();
6: var context = new BlogContext();
7: init.InitializeDatabase(context);
8: //Act
9: var post = context.Blogs.FirstOrDefault();
10: //Assert
11: Assert.IsNotNull(post);
12: Assert.IsNotNull(post.BlogLogo);
13: }
测试结果:
二、配置一个实体到多个表
拿用户和用户信息来说,在映射到已有的数据库时,有可能用户包含的信息比较多,为了性能或其他一些原因,不经常用的用户信息存放到单独的表中,经常使用的信息则存放到User表中,但是使用类表示时,希望将用户所有的信息放到一个类中,在映射时,就需要将实体分割。
下面是使用到的类:
1: public class Blog
2: {
3: public int Id { get; set; }
4: public DateTime Creationdate { get; set; }
5: public string ShortDescription { get; set; }
6: public string Title { get; set; }
7: public string Description { get; set; }
8: public string AboutTheAuthor { get; set; }
9: }
使用Fluent API 将Blog个映射到多个表,使用Map方法,如下:
1: Map(m =>
2: {
3: m.Properties(t => new { t.Id, t.Title, t.ShortDescription });
4: m.ToTable("Blog");
5: })
6: .Map(m =>
7: {
8: m.Properties(t => new { t.Description, t.Creationdate, t.AboutTheAuthor });
9: m.ToTable("BlogDetails");
10: });
完整的Demo如下:
1: public class Blog
2: {
3: public int Id { get; set; }
4: public DateTime Creationdate { get; set; }
5: public string ShortDescription { get; set; }
6: public string Title { get; set; }
7: public string Description { get; set; }
8: public string AboutTheAuthor { get; set; }
9: }
10:
11: public class BlogConfiguration : EntityTypeConfiguration<Blog>
12: {
13: public BlogConfiguration()
14: {
15: Map(m =>
16: {
17: m.Properties(t => new { t.Id, t.Title, t.ShortDescription });
18: m.ToTable("Blog");
19: })
20: .Map(m =>
21: {
22: m.Properties(t => new { t.Description, t.Creationdate, t.AboutTheAuthor });
23: m.ToTable("BlogDetails");
24: });
25: HasKey(x => x.Id);
26: Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("BlogId");
27: }
28: }
29:
30: public class BlogContext : DbContext, IUnitOfWork
31: {
32: protected override void OnModelCreating(DbModelBuilder modelBuilder)
33: {
34: modelBuilder.Configurations.Add(new BlogConfiguration());
35: base.OnModelCreating(modelBuilder);
36: }
37: public DbSet<Blog> Blogs { get; set; }
38:
39: public IQueryable<T> Find<T>() where T : class
40: {
41: return this.Set<T>();
42: }
43:
44: public void Refresh()
45: {
46: this.ChangeTracker.Entries().ToList().ForEach(x => x.Reload());
47: }
48:
49: public void Commit()
50: {
51: this.SaveChanges();
52: }
53: }
54:
55: public interface IUnitOfWork
56: {
57: IQueryable<T> Find<T>() where T : class;
58: void Refresh();
59: void Commit();
60: }
61:
62: public class Initializer : DropCreateDatabaseAlways<BlogContext>
63: {
64: public Initializer()
65: {
66: }
67: protected override void Seed(BlogContext context)
68: {
69: context.Set<Blog>().Add(new Blog()
70: {
71: Creationdate = DateTime.Now,
72: ShortDescription = "Testing",
73: Title = "Test Blog",
74: Description = "Long Test",
75: AboutTheAuthor = "Me me me"
76: });
77: context.SaveChanges();
78: }
79: }
测试程序:
1: [TestMethod]
2: public void ShouldReturnABlogWithAuthorDetails()
3: {
4: //Arrange
5: var init = new Initializer();
6: var context = new BlogContext();
7: init.InitializeDatabase(context);
8: //Act
9: var post = context.Blogs.FirstOrDefault();
10: //Assert
11: Assert.IsNotNull(post);
12: Assert.IsNotNull(post.AboutTheAuthor);
13: }
测试结果:
三、继承
TPH,Table Per Hierarchy,就是基类和派生类都映射到一张表,使用辨别列区分。
TPT,Table Per Type,就是基类存放到一张主表,每个派生类存放到一张表,通过外键与主表关联。
TPC,Table Per Concrete Type,就是基类和派生类都存放到单独的表中,没有主表。
下面是继承部分使用到的类:
1: public class Blog
2: {
3: public int Id { get; set; }
4: public DateTime Creationdate { get; set; }
5: public string ShortDescription { get; set; }
6: public string Title { get; set; }
7: public string AboutTheAuthor { get; set; }
8: }
9:
10: public class PictureBlog : Blog
11: {
12: //想不起来用什么字段好了,弄个“图片介绍”吧
13: public string PicDescription { get; set; }
14: }
15:
16: public class VideoBlog : Blog
17: {
18: //视频介绍
19: public string VideoDescription { get; set; }
20: }
首先来看一下TPH
配置继承为TPH,使用到一些新的配置方法:Requires和HasValue。Code First默认辨别列的名称为Discriminator,辨别列的值为类的名称。Requires配置辨别列的名称,HasValue定义辨别列的值。
下面看TPH的Demo:
1: [TestMethod]
2: public void ShouldReturnABlogWithTypeSafety()
3: {
4: //Arrange
5: var init = new Initializer();
6: var context = new
7: BlogContext(Settings.Default.BlogConnection);
8: init.InitializeDatabase(context);
9: //Act
10: var pictureBlog =
11: context.Set<PictureBlog>().FirstOrDefault();
12: var videoBlog = context.Set<VideoBlog>().FirstOrDefault();
13: //Assert
14: Assert.IsNotNull(pictureBlog);
15: Assert.IsNotNull(videoBlog);
16: }
17: }
测试程序:
1: [TestMethod]
2: public void ShouldReturnABlogWithTypeSafety()
3: {
4: //Arrange
5: var init = new Initializer();
6: var context = new BlogContext();
7: init.InitializeDatabase(context);
8: //Act
9: var pictureBlog =context.Set<PictureBlog>().FirstOrDefault();
10: var videoBlog = context.Set<VideoBlog>().FirstOrDefault();
11: //Assert
12: Assert.IsNotNull(pictureBlog);
13: Assert.IsNotNull(videoBlog);
14: }
测试结果:
TPT
配置继承为TPT,只需使用ToTable显示将派生类映射到表即可。
TPT的Demo,只需修改PictureBlog和VideoBlog的配置,其他全部一样:
1: public class PictureBlogConfiguration : EntityTypeConfiguration<PictureBlog>
2: {
3: public PictureBlogConfiguration()
4: {
5: Map(m =>
6: {
7: m.ToTable("PictureBlogs");
8: });
9: }
10: }
11:
12: public class VideoBlogConfiguration : EntityTypeConfiguration<VideoBlog>
13: {
14: public VideoBlogConfiguration()
15: {
16: Map(m =>
17: {
18: m.ToTable("VideoBlogs");
19: });
20: }
21: }
最后生成的数据库表如下图所示:
Blogs表,只包含基类中的属性:
PictureBlogs表,只包含派生类中的属性:
VideoBlogs表:
TPC
TPC和TPT差不多,TPC使用MapInheritedProperties配置。MapInheritedProperties告诉Code First要映射基类中的属性到派生类的表中。
代码如下所示:
1: public class BlogConfiguration : EntityTypeConfiguration<Blog>
2: {
3: public BlogConfiguration()
4: {
5: ToTable("Blogs");
6: HasKey(t => t.Id);
7: Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("BlogId");
8: Property(t => t.Title).HasMaxLength(175);
9: }
10: }
11:
12: public class PictureBlogConfiguration : EntityTypeConfiguration<PictureBlog>
13: {
14: public PictureBlogConfiguration()
15: {
16: Map(m =>
17: {
18: m.ToTable("PictureBlogs");
19: m.MapInheritedProperties();
20: });
21: }
22: }
现在再运行程序,会出现下面的错误:
出现这个问题的主要问题是因为不支持多态关联。归根到底,就是在数据库中关联表示为外键关系,在TPC中,子类都映射到不同的表中,所以有多个关联指向基类不能使用简单的外键关系表示。
解决办法:
配置Blog的主键产生之的方式为None,在创建对象时,手动给主键赋值。
1: public class BlogConfiguration : EntityTypeConfiguration<Blog>
2: {
3: public BlogConfiguration()
4: {
5: ToTable("Blogs");
6: HasKey(t => t.Id);
7: //配置不自动生成值
8: Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None).HasColumnName("BlogId");
9: Property(t => t.Title).HasMaxLength(175);
10: }
11: }
1: protected override void Seed(BlogContext context)
2: {
3: context.Set<PictureBlog>().Add(new PictureBlog()
4: {
5: //手动给主键赋值
6: Id = 1,
7: Creationdate = DateTime.Now,
8: ShortDescription = "Testing",
9: Title = "Test Blog",
10: AboutTheAuthor = "Me me me",
11: PicDescription = "This is a picture description"
12: });
13: context.Set<VideoBlog>().Add(new VideoBlog()
14: {
15: Id = 2,
16: Creationdate = DateTime.Now,
17: ShortDescription = "Testing",
18: Title = "Test Blog",
19: AboutTheAuthor = "Me me me",
20: VideoDescription = "This is a video description"
21: });
22: context.SaveChanges();
23: }
24: }
运行结果:
PictureBlogs表:
VideoBlogs表:
关于TPC的内容,可以查看这篇文章:Inheritance with EF Code First: Part 3 – Table per Concrete Type (TPC)
四、结束语
点击查看《Entity Framework实例详解》系列的其他文章。
如果遇到问题,可以访问Entity Framework社区,网址是www.ef-community.com。