Entity Framework中有三种关系,一对一(one-to-one),一对多(one-to-many),多对多(many-to-many),前两种就不说了,园子里这方面的文章很多(dudu的:Entity Framework 实践系列,杨延成的:EF框架step by step,郝冠军的:Entity Framework系列文章),看过之后简单的使用基本没什么问题,这里要说的是第三种:多对多(many-to-many)。
这里单独把多对多关系拿出来说,不是因为上述系列文章中没有,只不过需求不同,我的需求用上述系列文章中的方法实现不了。这里先用一个例子说一下我的需求吧:我要用EF处理 question(QID,Title)与tag(TID,TagName)之间的关系,这是一个多对多关系(一个问题有多个标签,一个标签有多个问题),因此在数据库中除了question与tag表外应该还有他们的关系表question_tag表,问题就出在question_tag表上。
如果我的question_tag表仅仅只有两个字段QID与TID,那么用上面系列文章中提到的方法就可以实现,关键代码如下:
1 [Table("Question")]
2 public class Question
3 {
4 [Key]
5 [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
6 public int QID { get; set; }
7 public string Title { get; set; }
8 public virtual ICollection<Tag> Tags { get; set; }
9 }
10
11 [Table("Tag")]
12 public class Tag
13 {
14 [Key]
15 [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
16 public int TID { get; set; }
17 public string TagName { get; set; }
18 public virtual ICollection<Question> Questions { get; set; }
19 }
20
21 public class BlogDbContext : DbContext
22 {
23 protected override void OnModelCreating(DbModelBuilder modelBuilder)
24 {
25 modelBuilder.Entity<Question>()
26 .HasMany(q => q.Tags)
27 .WithMany(t => t.Questions)
28 .Map
29 (
30 m =>
31 {
32 m.MapLeftKey("QID");
33 m.MapRightKey("TID");
34 m.ToTable("Question_Tag");
35 }
36 );
37 base.OnModelCreating(modelBuilder);
38 }
39 public IDbSet<Question> Questions { get; set; }
40 public IDbSet<Tag> Tags { get; set; }
41 }
现在的问题是我的question_tag表为了业务需求不仅仅只有这两个字段(这个需求应该很常见,本例中增加一个时间字段DateAdded作为示例),因此用上面的方案就不行了。那么要怎么处理呢,找了好多资料都不行,没办法只好自己动手,丰衣足食。
首先想到的是,既然question_tag表中还有其它字段,那么这个实体肯定要表现出来。然后想到的是,按原来的方法question跟tag是直接产生联系的,EF根据question和tag的定义可以判断出是多对多关系,但现在加了一个关系实体question_tag,question跟question_tag的关系是一对多,tag跟question_tag的关系也是一对多,因此可以通过question_tag来联接question跟tag(数据库中这个表的存在本来就是这个意思),也就是说question跟tag不直接产生联系。有了上面的想法,经过多次尝试后,我把实体间的关系修改为如下形式:
1 [Table("Question")]
2 public class Question
3 {
4 [Key]
5 [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
6 public int QID { get; set; }
7 public string Title { get; set; }
8 public virtual ICollection<QuestionTag> QuestionTags { get; set; }
9 }
10
11 [Table("Tag")]
12 public class Tag
13 {
14 [Key]
15 [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
16 public int TID { get;set;}
17 public string TagName { get; set; }
18 public virtual ICollection<QuestionTag> QuestionTags { get; set; }
19 }
20
21 [Table("QuestionTag")]
22 public class QuestionTag
23 {
24 [Key]
25 [Column(Order=0)]
26 [ForeignKey("Question")]
27 public int QID { get; set; }
28
29 [Key]
30 [Column(Order = 1)]
31 [ForeignKey("Tag")]
32 public int TID { get; set; }
33
34 public DateTime DateAdded { get; set; }
35
36 public virtual Question Question { get; set; }
37
38 public virtual Tag Tag { get; set; }
39 }
有了上面的定义后,DbContext的定义就很简单了,不需要重写OnModelCreating:
1 public class BlogDbContext : DbContext
2 {
3 public IDbSet<Question> Questions { get; set; }
4 public IDbSet<Tag> Tags { get; set; }
5 public IDbSet<QuestionTag> QuestionTags { get; set; }
6 }
好了,下面开始写测试代码,就是增删改查操作:
1 public class EFTest
2 {
3 public void Insert()
4 {
5 using (var db = new BlogDbContext())
6 {
7 //添加一个question,两个tag
8 var question = new Question() { Title = "abc" };
9 var tagA = new Tag() { TagName = "a" };
10 var tagB = new Tag() { TagName = "b" };
11 var qes = db.Questions.Add(question);
12 var tA = db.Tags.Add(tagA);
13 var tB = db.Tags.Add(tagB);
14 db.SaveChanges();
15
16 //添加question_tag
17 var questiontaga = new QuestionTag() { QID = qes.QID, TID = tA.TID, DateAdded = DateTime.Now };
18 var questiontagb = new QuestionTag() { QID = qes.QID, TID = tB.TID, DateAdded = DateTime.Now };
19 var qtA = db.QuestionTags.Add(questiontaga);
20 var qtB = db.QuestionTags.Add(questiontagb);
21 db.SaveChanges();
22 Console.WriteLine("Insert Success");
23
24 //显示数据
25 Show();
26 }
27 }
28
29 public void Delete()
30 {
31 using (var db = new BlogDbContext())
32 {
33 var qes = db.Questions.SingleOrDefault(q => q.QID == 1);
34 if (qes != null)
35 {
36 db.Questions.Remove(qes);
37 db.SaveChanges();
38 Console.WriteLine("Delete Success");
39
40 Show();
41 }
42 }
43 }
44
45 public void Update()
46 {
47 using (var db = new BlogDbContext())
48 {
49 var qes = db.Questions.SingleOrDefault(q => q.QID == 2);
50 if (qes != null)
51 {
52 qes.Title = "update abc";
53 db.SaveChanges();
54 Console.WriteLine("Update Success");
55
56 Show();
57 }
58 }
59 }
60
61 public void Select()
62 {
63 using (var db = new BlogDbContext())
64 {
65 var qes = db.Questions.SingleOrDefault(q => q.QID == 2);
66 if (qes != null)
67 {
68 Console.WriteLine("Select Question:");
69 Console.Write("QID:"+qes.QID + "\tTitle:" + qes.Title+"\tTag:");
70 qes.QuestionTags.ForEach(t => Console.Write(t.Tag.TagName+"\t"));
71 }
72 Console.WriteLine("\nSelect Success");
73 }
74 }
75
76 public void Show()
77 {
78 using (var db = new BlogDbContext())
79 {
80 //显示question
81 var qes = db.Questions;
82 if (qes != null)
83 {
84 Console.WriteLine("Question:");
85 qes.ForEach(q =>
86 {
87 Console.Write("QID:"+q.QID+"\tTitle:"+q.Title+"\tTag:");
88 q.QuestionTags.ForEach(t =>
89 {
90 Console.Write(t.Tag.TagName+"\t");
91 });
92 Console.WriteLine();
93 });
94 }
95
96 //显示tag
97 var tag = db.Tags;
98 if (tag != null)
99 {
100 Console.WriteLine("Tag:");
101 tag.ForEach(t =>
102 {
103 Console.Write("TID:" + t.TID + "\tTagName:" + t.TagName + "\tQuestion:");
104 t.QuestionTags.ForEach(q =>
105 {
106 Console.Write(q.Question.Title + "\t");
107 });
108 Console.WriteLine();
109 });
110 }
111
112 Console.WriteLine();
113 }
114 }
115 }
很幸运地通过了,运行后数据库中生成的表如下:
要注意的是这里QID和TID不仅仅是PK,也是FK。
程序的输出如下:
可以看到程序能很好地满足我的需求。在上面的删除代码中删除了QID为1的question后,数据库中question_tag表中的数据如下:
我们可以看到,question_tag表中QID为1的数据也同时删除了,正是我们需要的结果。
最后做个小结吧:
一直以来都是看的多,写的少,从老鸟那里吸收的多,贡献的少,总怕自己写的不好,以后争取慢慢改变这种状况,把自己学到的知识总结出来,争取能给新手一些帮助吧。
希望这篇文章对大家有所帮助,当然了,限于水平欢迎大家拍砖,提出更好的解决方案。