11. 主体和唯一标识
在这一节中,让我们来回顾一下HasPrincipalKey方法和唯一标识。
在EF CORE中,主体(Principal Entity)指的是包含主键/备用键的实体。所以在一般情况下,所有的实体都是主体。而主体键(Principal Key)指的是主体中的主键/备用键。大家都知道,主键/备用键都是不可为空且唯一的,这就引出了唯一标识列的写法。
唯一标识列一般有“主体键”,“唯一索引”两种写法,其中主体键中的主键没有什么讨论的价值。让我们来看看其他两种的写法。
1. 备用键
备用键在之前的小节中已经提过,使用以下代码配置的列将自动设置为唯一标识列。
modelBuilder.Entity<Car>()
.HasAlternateKey(c => new {c.LicensePlate, c.Model})
.HasName("AlternateKey_LicensePlate");
modelBuilder.Entity<Post>()
.HasOne(p => p.Blog)
.WithMany(b => b.Posts)
.HasForeignKey(p => p.BlogUrl)
.HasPrincipalKey(b => b.Url);
注意这里的HasPrincipalKey方法,它通常跟在HasForeignKey和WithMany方法后,用以指定实体中的一个或多个属性作为备用键。再次重申一遍,备用键和主键有相似之处,它通常用来指定一个明确的外键目标——当开发者不想用单纯无意义的Id作为外键标识时。
虽然主体键也包括主键,但是主键在EF CORE中时强制定义的,所以HasPrincipalKey只会将属性配置为备用键。
2. 唯一索引
索引及其唯一性只由Fluent Api方式指定,由索引来指定唯一列是比备用键更好的选择。
modelBuilder.Entity<Blog>()
.HasIndex(b => b.Url)
.IsUnique();
12. 继承
继承通常被用来控制实体类接口如何映射到数据库表结构中。在EF CORE 当前版本中,TPC和TPT暂不被支持,TPH是默认且唯一的继承方式。
那么什么是TPH(table-per-hierarchy)呢?顾名思义,一种继承结构全部映射到一张表中,比如Person父类,Student子类和Teacher子类,由EF CORE映射到数据库中时,将会只存在Person类,而Student和Teacher将以列标识的形式出现。
目前只有Fluent Api方式支持TPH,具体实体类代码如下,其中RssBlog继承自Blog。
public class Blog{ public int BlogId { get; set; } public string Url { get; set; }
}public class RssBlog : Blog{ public string RssUrl { get; set; }
}
在MyContext中,我们将原来的代码修改成如下形式:
class MyContext : DbContext{ public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<Blog>()
.HasDiscriminator<string>("blog_type")
.HasValue<Blog>("blog_base")
.HasValue<RssBlog>("blog_rss");
}
}public class Blog{ public int BlogId { get; set; } public string Url { get; set; }
}public class RssBlog : Blog{ public string RssUrl { get; set; }
}
观察OnModelCreating方法,HasDiscriminator提供修改标识列名的功能,HasValue提供新增或修改实体时,根据实体类型将不同的标识自动写入标识列中。如新增Blog时,blog_type列将写入blog_base字符串,新增RssBlog时,blog_type列将写入blog_rss字符串。
笔者不推荐用继承的方式设计数据库,只是这个功能相对新奇,就列出来说了。
13. 关系
关系型数据库模型的设计中,最重要的一点便是“关系”的设计了。常见的关系有1-1,1-n,n-n,除此以外,关系的两边还有可空不可空的控制。那么在EF CORE中,我们怎么实现这些关系呢?
以下内容用代码的方式给出了一对一,一对多和多对多的关系,两边关系设为不可空。其实可空不可空的控制十分简单,只要注意是否需要加上IsRequired的扩展Api即可。
不得不说,相比EF6.X的HasRequired和WithOptional等方法,EF CORE中的Api和关系配置清晰直观了不少。
唯一需要注意的是,关系设置请从子端(如User和Blog呈一对多对应时,从Blog开始)开始,否则配置不慎容易出现多个外键的情况。
public class MyContext : DbContext
{ public MyContext(DbContextOptions<MyContext> options) : base(options) {
} public DbSet<User> Users { get; set; } public DbSet<UserAccount> UserAccounts { get; set; } public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } public DbSet<PostTag> PostTags { get; set; } public DbSet<Tag> Tags { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { // Blog与Post之间为 1 - N 关系
modelBuilder.Entity<Post>()
.HasOne(p => p.Blog)
.WithMany(b => b.Posts)
.HasForeignKey(p => p.BlogId) // 使用HasConstraintName方法配置外键名称
.HasConstraintName("ForeignKey_Post_Blog")
.IsRequired(); // User与UserAccount之间为 1 - 1 关系
modelBuilder.Entity<UserAccount>()
.HasKey(c => c.UserAccountId);
modelBuilder.Entity<UserAccount>()
.HasOne(c => c.User)
.WithOne(c => c.UserAccount)
.HasForeignKey<UserAccount>(c => c.UserAccountId)
.IsRequired(); // User与Blog之间为 1 - N 关系
modelBuilder.Entity<Blog>()
.HasOne(b => b.User)
.WithMany(c => c.Blogs)
.HasForeignKey(c => c.UserId)
.IsRequired(); // Post与Tag之间为 N - N 关系
modelBuilder.Entity<PostTag>()
.HasKey(t => new {t.PostId, t.TagId});
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId)
.IsRequired();
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId)
.IsRequired();
}
} public class Blog
{ public int BlogId { get; set; } public string Url { get; set; } public List<Post> Posts { get; set; } public int UserId { get; set; } public User User { get; set; }
} public class User
{ public int UserId { get; set; } public string UserName { get; set; } public List<Blog> Blogs { get; set; } public UserAccount UserAccount { get; set; }
} public class UserAccount
{ public int UserAccountId { get; set; } public string UserAccountName { get; set; } public bool IsValid { get; set; } public User User { get; set; }
} public class Post
{ public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public int BlogId { get; set; } public Blog Blog { get; set; } public List<PostTag> PostTags { get; set; }
} public class Tag
{ public string TagId { get; set; } public List<PostTag> PostTags { get; set; }
} public class PostTag
{ public int PostId { get; set; } public Post Post { get; set; } public string TagId { get; set; } public Tag Tag { get; set; }
}
14. Console中的EntityframeworkCore(2017年7月21日新增)
工作中时常会用到一些简单的EF场景,使用Console是最方便不过了,所以特此记录下。
新建一个APS.NET CORE WEB模板项目
安装相关Nuget包
//Sql Server Database Provider Install-Package Microsoft.EntityFrameworkCore.SqlServer
//提供熟悉的Add-Migration,Update-Database等Powershell命令,不区分关系型数据库类型 Install-Package Microsoft.EntityFrameworkCore.Tools
public class MyContext : DbContext{ protected override void OnModelCreating(ModelBuilder modelBuilder) {
}
}
注意我们删除了 public MyContext(DbContextOptions<MyContext> options) : base(options)
构造方法,以便我们可以直接 using(var context = new MyContext())
的方法。简单来说,当你有依赖注入的需求时,便需要使用第一种构造模型。
Install-Package System.Text.Encoding.CodePagesInstall-Package Microsoft.Extensions.Configuration.JsonInstall-Package Microsoft.Extensions.Configuration.EnvironmentVariables
// 需要新增appsettings.json文件,并添加ConnectionStrings节点,用于存放连接字符串。"ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=Console4;Trusted_Connection=True;MultipleActiveResultSets=true;"
},
private static IConfigurationRoot Configuration { get; set; }protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){ //.SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), @"..\.."))
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
}
Add-Migration InitializeUpdate-Database
之后便可以在Console中使用形如 using (var context = new MyContext())
的语法对数据库进行操作了。
15. 参考链接和优秀博客
EF CORE OFFICIAL DOC
Introduction to Entity Framework
Feature Comparison
Entity Framework教程(第二版)