【配置映射】—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: }

测试结果:

QQ截图20121118211721

二、配置一个实体到多个表

拿用户和用户信息来说,在映射到已有的数据库时,有可能用户包含的信息比较多,为了性能或其他一些原因,不经常用的用户信息存放到单独的表中,经常使用的信息则存放到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: }

测试结果:

QQ截图20121118220013

三、继承

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: }

测试结果:

QQ截图20121119213918

QQ截图20121119214034

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表,只包含基类中的属性:

QQ截图20121119222136

PictureBlogs表,只包含派生类中的属性:

QQ截图20121119222153

VideoBlogs表:

QQ截图20121119222203

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: }

现在再运行程序,会出现下面的错误:

QQ截图20121119232344

出现这个问题的主要问题是因为不支持多态关联。归根到底,就是在数据库中关联表示为外键关系,在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表:

QQ截图20121119234414

VideoBlogs表:

QQ截图20121119234424

关于TPC的内容,可以查看这篇文章:Inheritance with EF Code First: Part 3 – Table per Concrete Type (TPC)

四、结束语

点击查看《Entity Framework实例详解》系列的其他文章。

如果遇到问题,可以访问Entity Framework社区,网址是www.ef-community.com

posted @ 2012-11-20 00:24  BobTian  阅读(15690)  评论(2编辑  收藏  举报