实体间的关系,简单来说无非就是一对一、一对多、多对多,根据方向性来说又分为双向和单向。Code First在实体关系上有以下约定:
1. 两个实体,如果一个实体包含一个引用属性,另一个实体包含一个集合属性,Code First默认约定它们为一对多关系。
2. 两个实体,如果只有一个实体包含一个导航属性或一个集合属性,Code First也默认约定它们是一对多关系。
3. 两个实体分别包含一个集合属性,Code First默认约定它们为多对多关系。
4. 两个实体分别包含一个引用属性,Code First默认约定它们为一对一关系。
5. 在一对一关系情况下,需要提供给Code First额外的信息,以确定它们的主从关系。
6. 在实体中定义一个外键属性,Code First使用属性是否为空来确定关系是必须还是可选。
一、一对一
在Code First中,一对一关系总是需要配置,因为两个实体都包含有一个引用属性,无法确定它们的主从关系。
配置一对一关系常用的方法:
HasRequired ,HasOptional ,WithOptional ,WithRequiredPrincipal,WithRequiredDependent
下面是用到的类:
1: public class Person
2: {
3: public int PersonId { get; set; }
4: public int SocialSecurityNumber { get; set; }
5: public string FirstName { get; set; }
6: public string LastName { get; set; }
7: public byte[] RowVersion { get; set; }
8: public PersonPhoto Photo { get; set; }
9: }
10:
11: public class PersonPhoto
12: {
13: public int PersonId { get; set; }
14: public byte[] Photo { get; set; }
15: public string Caption { get; set; }
16: public Person PhotoOf { get; set; }
17: }
因为Photo是具体人的,所以PersonPhoto使用PersonId作为主键。
下面是一对一关系配置的几种情况:
1.PersonPhoto必须属于一个Person,但是Person不一定有PersonPhoto,这种关系是1:0..1,此种情况下Person是一定存在的,所以它是主从关系主的一方。
1: HasRequired(t => t.PhotoOf).WithOptional(t => t.Photo);
或
1: HasOptional(t => t.Photo).WithRequired(t => t.PhotoOf);
2.PersonPhoto必须属于一个Person,Person也必须有PersonPhoto,这种关系式1:1,此种情况下,两个都一定存在,要确定主从关系,需要使用WithRequiredPrincipal或WithRequiredDependent。
1: HasRequired(t => t.PhotoOf).WithRequiredDependent(t => t.Photo);
或
1: HasRequired(t => t.Photo).WithRequiredPrincipal(t => t.PhotoOf);
上述两种情况都是真实存在的,不真实存在的就不说了。
下面配置一对一关系贴出Demo:
class Person
2: {
int PersonId { get; set; }
int SocialSecurityNumber { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
byte[] RowVersion { get; set; }
public PersonPhoto Photo { get; set; }
9: }
10:
class PersonPhoto
12: {
int PersonId { get; set; }
byte[] Photo { get; set; }
string Caption { get; set; }
public Person PhotoOf { get; set; }
17: }
18:
//配置Person
class PersonConfiguration : EntityTypeConfiguration<Person>
21: {
public PersonConfiguration()
23: {
//主键
25: HasKey(t => t.PersonId);
//并发检查
27: Property(t => t.SocialSecurityNumber).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None).IsConcurrencyToken();
//长度50 不为空
29: Property(t => t.FirstName).IsRequired().HasMaxLength(50);
//长度50 不为空
31: Property(t => t.LastName).IsRequired().HasMaxLength(50);
//并发检查
33: Property(t => t.RowVersion).IsRowVersion();
//HasRequired(t => t.Photo).WithRequiredPrincipal(t => t.PhotoOf);
//HasOptional(t => t.Photo).WithRequired(t => t.PhotoOf);
36: }
37: }
38:
//配置PersonPhoto
class PersonPhotoConfiguration : EntityTypeConfiguration<PersonPhoto>
41: {
public PersonPhotoConfiguration()
43: {
//主键
45: HasKey(t => t.PersonId);
//长度50
47: Property(t => t.Caption).HasMaxLength(50);
//必须从属于Person
49: HasRequired(t => t.PhotoOf).WithRequiredDependent(t => t.Photo);
50: }
51: }
52:
class BreakAwayContext : DbContext
54: {
public DbSet<Person> People { get; set; }
public DbSet<PersonPhoto> Photos { get; set; }
57:
void OnModelCreating(DbModelBuilder modelBuilder)
59: {
new PersonConfiguration());
new PersonPhotoConfiguration());
base.OnModelCreating(modelBuilder);
63: }
64: }
65:
class Initializer : DropCreateDatabaseAlways<BreakAwayContext>
67: {
public Initializer()
69: {
70: }
71:
//创建数据库时 Seed数据
void Seed(BreakAwayContext context)
74: {
new Person()
76: {
,
,
79: SocialSecurityNumber = 123456,
new PersonPhoto()
81: {
,
byte[] { }
84: }
85: });
86: context.SaveChanges();
87: }
88: }
测试程序
1: [TestClass]
class UnitTest1
3: {
4: [TestMethod]
void ShouldReturnAPersonWithPhoto()
6: {
//Arrange
new Initializer();
9: Person person;
new BreakAwayContext())
11: {
12: init.InitializeDatabase(context);
//Act
14: person = context.People.Include(t => t.Photo).FirstOrDefault();
15: }
//Assert
17: Assert.IsNotNull(person);
18: Assert.IsNotNull(person.Photo);
19: }
20: }
测试结果:
二、一对多
下面是用到的类:
1: public class Blog
2: {
3: public Blog()
4: {
5: Posts = new List<Post>();
6: }
7:
8: public int Id { get; set; }
9: public DateTime Creationdate { get; set; }
10: public string ShortDescription { get; set; }
11: public string Title { get; set; }
12: public List<Post> Posts { get; set; }
13: }
14:
15: public class Post
16: {
17: public int Id { get; set; }
18: public string Title { get; set; }
19: public string Content { get; set; }
20: public DateTime PostedDate { get; set; }
21:
22: public Nullable<int> BlogId { get; set; }
23: public virtual Blog Blog { get; set; }
24:
25: public int PrimaryAuthorId { get; set; }
26: public virtual Author PrimaryAuthor { get; set; }
27: public Nullable<int> SecondaryAuthorId { get; set; }
28: public virtual Author SecondaryAuthor { get; set; }
29: }
30:
31: public class Author
32: {
33: public int Id { get; set; }
34: public string Name { get; set; }
35: public string Email { get; set; }
36: //个人简历
37: public string Bio { get; set; }
38:
39: public List<Post> PrimaryAuthorFor { get; set; }
40: public List<Post> SecondaryAuthorFor { get; set; }
41: }
配置一对多关系常用的方法有:
HasOptional ,HasRequired ,HasMany
Has方法后面往往跟着With方法
WithOptional ,WithRequired ,WithMany
下面配置一对多的几种情况:
1.Post一定归属于一个Blog,这种关系是1:n。
1: HasMany(x => x.Posts).WithRequired(x =>x.Blog)
或
1: HasRequired(x => x.Blog).WithMany(x => x.Posts)
2.Post可以单独存在,不用归属于Blog,这种关系是0..1:n。
1: HasMany(x => x.Posts).WithOptional(x => x.Blog)
或
1: HasOptional(x => x.Blog).WithMany(x => x.Posts)
设置外键
外键的默认约定:
[Target Type Key Name], [Target Type Name] + [Target Type Key Name], or [Navigation
Property Name] + [Target Type Key Name]
本例中,匹配的是[Target Type Name] + [Target Type Key Name],目标类型是Blog,目标类型主键是Id,加起来就是BlogId。下面使用Fluent API显示设置外键:
1: HasMany(x => x.Posts).WithOptional(x => x.Blog).HasForeignKey(x => x.BlogId)
设置级联删除
1: HasMany(x => x.Posts).WithOptional(x => x.Blog).HasForeignKey(x => x.BlogId).WillCascadeOnDelete();
反转属性
在Post实体中,有两个属性:PrimaryAuthor和SecondaryAuthor,第一作者和第二作者。在Author中有两个集合属性,Code First默认不能确定哪个集合属性和Post中的导航属性相匹配。使用Fluent API配置反转属性,如下:
1: HasRequired(t => t.PrimaryAuthor).WithMany(t => t.PrimaryAuthorFor);
2: HasOptional(t => t.SecondaryAuthor).WithMany(t => t.SecondaryAuthorFor);
下面是配置一对多关系的Demo:
class Blog
2: {
public Blog()
4: {
new List<Post>();
6: }
7:
int Id { get; set; }
public DateTime Creationdate { get; set; }
string ShortDescription { get; set; }
string Title { get; set; }
public List<Post> Posts { get; set; }
13: }
14:
class Post
16: {
int Id { get; set; }
string Title { get; set; }
string Content { get; set; }
public DateTime PostedDate { get; set; }
21:
//Post可以不归属到Blog独立存在,注意这里的外键属性要设置为可空的
int> BlogId { get; set; }
virtual Blog Blog { get; set; }
25:
int PrimaryAuthorId { get; set; }
virtual Author PrimaryAuthor { get; set; }
int> SecondaryAuthorId { get; set; }
virtual Author SecondaryAuthor { get; set; }
30: }
31:
class Author
33: {
int Id { get; set; }
string Name { get; set; }
string Email { get; set; }
//个人简历
string Bio { get; set; }
39:
public List<Post> PrimaryAuthorFor { get; set; }
public List<Post> SecondaryAuthorFor { get; set; }
42: }
43:
class BlogConfiguratioin : EntityTypeConfiguration<Blog>
45: {
public BlogConfiguratioin()
47: {
);
49: HasKey(t => t.Id);
50: Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
51: Property(t => t.Title).IsRequired().HasMaxLength(250);
).IsRequired();
);
//配置Blog和Post的一对多关系,Blog对Post是可选的,外键BlogId,并设置为级联删除
55: HasMany(t => t.Posts).WithOptional(t => t.Blog).HasForeignKey(t => t.BlogId).WillCascadeOnDelete();
56: }
57: }
58:
class PostConfiguration : EntityTypeConfiguration<Post>
60: {
public PostConfiguration()
62: {
);
64: HasKey(t => t.Id);
).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
).IsMaxLength();
);
).IsMaxLength();
//配置反转属性,集合属性PrimaryAuthorFor匹配PrimaryAuthor
70: HasRequired(t => t.PrimaryAuthor).WithMany(t => t.PrimaryAuthorFor);
//配置反转属性,集合属性SecondaryAuthorFor匹配SecondaryAuthor
72: HasOptional(t => t.SecondaryAuthor).WithMany(t => t.SecondaryAuthorFor);
73: }
74: }
75:
class AuthorConfiguration : EntityTypeConfiguration<Author>
77: {
public AuthorConfiguration()
79: {
);
).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
82: Property(t => t.Name).IsRequired().HasMaxLength(50);
83: Property(t => t.Email).IsRequired().HasMaxLength(50);
84: Property(t => t.Bio).HasMaxLength(1000);
85: }
86: }
87:
class BreakAwayContext : DbContext
89: {
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<Author> Authors { get; set; }
93:
void OnModelCreating(DbModelBuilder modelBuilder)
95: {
new BlogConfiguratioin());
new PostConfiguration());
new AuthorConfiguration());
base.OnModelCreating(modelBuilder);
100: }
101: }
102:
class Initializer : DropCreateDatabaseAlways<BreakAwayContext>
104: {
public Initializer()
106: {
107: }
108:
void Seed(BreakAwayContext context)
110: {
new Author()
112: {
,
,
116: };
new Author()
118: {
,
,
122: };
new Blog()
124: {
,
,
127: Creationdate = DateTime.Now
128: };
new Post()
130: {
,
132: PostedDate = DateTime.Now,
,
134: PrimaryAuthor = primaryAuthor,
135: SecondaryAuthor = secondaryAuthor
136: });
137: context.Blogs.Add(blog);
138: context.SaveChanges();
139: }
140: }
测试程序:
1: [TestClass]
class OneToManyTest
3: {
4: [TestMethod]
void ShouldReturnBlogWithPosts()
6: {
//Arrage
new Initializer());
new BreakAwayContext();
//Act
11: var blog = context.Blogs.Include(t => t.Posts).FirstOrDefault();
//Assert
13: Assert.IsNotNull(blog);
14: Assert.IsNotNull(blog.Posts);
15: Assert.IsNotNull(blog.Posts.FirstOrDefault().PrimaryAuthor);
16: }
17: }
测试结果:
三、多对多
下面是配置多对多关系用到的类,跟一对多差不多,只不过Post和Author的关系变成多对多的了。
1: public class Post
2: {
3: public int Id { get; set; }
4: public string Title { get; set; }
5: public string Content { get; set; }
6: public DateTime PostedDate { get; set; }
7:
8: public virtual List<Author> Authors { get; set; }
9: }
10:
11: public class Author
12: {
13: public int Id { get; set; }
14: public string Name { get; set; }
15: public string Email { get; set; }
16: //个人简历
17: public string Bio { get; set; }
18:
19: public virtual List<Post> Posts { get; set; }
20: }
一篇文章有多个作者,一个作者著有多篇文章。
配置多对多关系使用HasMany和WithMany方法,可以使用Map配置生成关联表的名字。
下面是配置多对多关系的Demo:
class Post
2: {
public Post()
4: {
new List<Author>();
6: }
7:
int Id { get; set; }
string Title { get; set; }
string Content { get; set; }
public DateTime PostedDate { get; set; }
12:
public virtual List<Author> Authors { get; set; }
14: }
15:
class Author
17: {
public Author()
19: {
new List<Post>();
21: }
22:
int Id { get; set; }
string Name { get; set; }
string Email { get; set; }
//个人简历
string Bio { get; set; }
28:
public virtual List<Post> Posts { get; set; }
30: }
31:
class PostConfiguration : EntityTypeConfiguration<Post>
33: {
public PostConfiguration()
35: {
);
37: HasKey(t => t.Id);
).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
).IsMaxLength();
);
).IsMaxLength();
//配置多对多关系 ToTable 配置生成的关联表名字 MapLeftKey默认表示调用HasMany的实体的主键
//本例中如果不使用MapLeftKey默认生成Post_Id
44: HasMany(t => t.Authors).WithMany(t => t.Posts).Map(m =>
45: {
);
);
);
49: });
50: }
51: }
52:
class AuthorConfiguration : EntityTypeConfiguration<Author>
54: {
public AuthorConfiguration()
56: {
);
58: HasKey(t => t.Id);
).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
).IsMaxLength();
61: Property(t => t.Email).HasMaxLength(100).IsRequired();
62: Property(t => t.Name).HasMaxLength(100).IsRequired();
63: }
64: }
65:
class TestContext : DbContext
67: {
public DbSet<Post> Posts { get; set; }
public DbSet<Author> Authors { get; set; }
70:
void OnModelCreating(DbModelBuilder modelBuilder)
72: {
new PostConfiguration());
new AuthorConfiguration());
base.OnModelCreating(modelBuilder);
76: }
77: }
78:
class Initializer : DropCreateDatabaseAlways<TestContext>
80: {
void Seed(TestContext context)
82: {
new Post()
84: {
,
,
87: PostedDate = DateTime.Now
88: };
new Author()
90: {
,
,
94: };
new Author()
96: {
,
,
100: };
new Author()
102: {
,
,
106: };
107: post.Authors.Add(author);
108: post.Authors.Add(author1);
109: context.Posts.Add(post);
new Post()
111: {
,
,
114: PostedDate = DateTime.Now
115: };
116: post.Authors.Add(author);
117: post.Authors.Add(author2);
118: context.Posts.Add(post);
119: context.SaveChanges();
120: }
121: }
测试程序:
1: [TestClass]
class ManyToManyTest
3: {
4: [TestMethod]
void ShouldReturnPostWithAuthors()
6: {
//Arrage
new Initializer();
new ManyToMany.TestContext();
10: init.InitializeDatabase(context);
//Act
12: var post = context.Posts.Include(t => t.Authors).FirstOrDefault();
//Assert
14: Assert.IsNotNull(post);
15: Assert.AreEqual(2, post.Authors.Count);
, post.Authors[1].Name);
17: }
18: }
测试结果:
现在关联表中只有两个字段,如下图所示:
如果再加个字段,比如DateAdd,这就需要给关联表定义一个实体。
1: public class PostAuthor
2: {
3: public int PostId { get; set; }
4: public int AuthorId { get; set; }
5:
6: public Post Post { get; set; }
7: public Author Author { get; set; }
8:
9: public DateTime DateAdd { get; set; }
10: }
另外需要在Post和Author实体中加入一个集合属性:
1: public virtual List<PostAuthor> PostAuthors { get; set; }
另外还需要配置PostAuthor实体,具体代码如下面的Demo所示:
class Post
2: {
public Post()
4: {
new List<PostAuthor>();
6: }
7:
int Id { get; set; }
string Title { get; set; }
string Content { get; set; }
public DateTime PostedDate { get; set; }
12:
//public virtual List<Author> Authors { get; set; }
virtual List<PostAuthor> PostAuthors { get; set; }
15: }
16:
class Author
18: {
public Author()
20: {
new List<PostAuthor>();
22: }
23:
int Id { get; set; }
string Name { get; set; }
string Email { get; set; }
//个人简历
string Bio { get; set; }
29:
//public virtual List<Post> Posts { get; set; }
virtual List<PostAuthor> PostAuthors { get; set; }
32: }
33:
//关联表的实体
class PostAuthor
36: {
int PostId { get; set; }
int AuthorId { get; set; }
39:
public Post Post { get; set; }
public Author Author { get; set; }
42:
public DateTime? DateAdd { get; set; }
44: }
45:
class PostConfiguration : EntityTypeConfiguration<Post>
47: {
public PostConfiguration()
49: {
);
51: HasKey(t => t.Id);
).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
).IsMaxLength();
);
).IsMaxLength();
56: }
57: }
58:
class AuthorConfiguration : EntityTypeConfiguration<Author>
60: {
public AuthorConfiguration()
62: {
);
64: HasKey(t => t.Id);
).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
).IsMaxLength();
67: Property(t => t.Email).HasMaxLength(100).IsRequired();
68: Property(t => t.Name).HasMaxLength(100).IsRequired();
69: }
70: }
71:
//配置关联表实体
class PostAuthorConfiguration : EntityTypeConfiguration<PostAuthor>
74: {
public PostAuthorConfiguration()
76: {
);
//配置组合主键
new { t.PostId, t.AuthorId });
80: Property(t => t.PostId).HasColumnOrder(0);
81: Property(t => t.AuthorId).HasColumnOrder(1);
//这里是配置一对多关系
83: HasRequired(t => t.Post).WithMany(t => t.PostAuthors).HasForeignKey(t => t.PostId);
84: HasRequired(t => t.Author).WithMany(t => t.PostAuthors).HasForeignKey(t => t.AuthorId);
85: }
86: }
87:
class TestContext : DbContext
89: {
public DbSet<Post> Posts { get; set; }
public DbSet<Author> Authors { get; set; }
public DbSet<PostAuthor> PostAuthors { get; set; }
93:
void OnModelCreating(DbModelBuilder modelBuilder)
95: {
new PostConfiguration());
new AuthorConfiguration());
new PostAuthorConfiguration());
base.OnModelCreating(modelBuilder);
100: }
101: }
102:
class Initializer : DropCreateDatabaseAlways<TestContext>
104: {
void Seed(TestContext context)
106: {
new Post()
108: {
,
,
111: PostedDate = DateTime.Now
112: };
113: post = context.Posts.Add(post);
new Author()
115: {
,
,
119: };
new Author()
121: {
,
,
125: };
126: author = context.Authors.Add(author);
127: author1 = context.Authors.Add(author1);
128: context.SaveChanges();
new PostAuthor()
130: {
131: PostId = post.Id,
132: AuthorId = author.Id,
133: DateAdd = DateTime.Now
134: };
new PostAuthor()
136: {
137: PostId = post.Id,
138: AuthorId = author1.Id,
139: DateAdd = DateTime.Now
140: };
141: context.PostAuthors.Add(pa1);
142: context.PostAuthors.Add(pa2);
143: context.SaveChanges();
144: }
145: }
测试程序:
1: [TestMethod]
void ShouldReturnAuthorsWithDateAdd()
3: {
//Arrage
new Initializer();
new ManyToMany.TestContext();
7: init.InitializeDatabase(context);
//Act
9: var post = context.Posts.Include(t => t.PostAuthors).FirstOrDefault();
//Assert
11: Assert.IsNotNull(post);
12: Assert.AreEqual(2, post.PostAuthors.Count);
13: Assert.IsNotNull(post.PostAuthors[0].DateAdd);
14: }
测试结果:
生成的关联表如下图所示:
四、结束语
点击查看《Entity Framework实例详解》系列的其他文章。
如果遇到问题,可以访问Entity Framework社区,网址是www.ef-community.com或www.ef-community.cn 。