Entity Framework多對多關(guān)系實(shí)踐(many-to-many)
Entity Framework中有三種關(guān)系,一對一(one-to-one),一對多(one-to-many),多對多(many-to-many),前兩種就不說了,園子里這方面的文章很多(dudu的:Entity Framework 實(shí)踐系列,楊延成的:EF框架step by step,郝冠軍的:Entity Framework系列文章),看過之后簡單的使用基本沒什么問題,這里要說的是第三種:多對多(many-to-many)。
這里單獨(dú)把多對多關(guān)系拿出來說,不是因?yàn)樯鲜鱿盗形恼轮袥]有,只不過需求不同,我的需求用上述系列文章中的方法實(shí)現(xiàn)不了。這里先用一個例子說一下我的需求吧:我要用EF處理 question(QID,Title)與tag(TID,TagName)之間的關(guān)系,這是一個多對多關(guān)系(一個問題有多個標(biāo)簽,一個標(biāo)簽有多個問題),因此在數(shù)據(jù)庫中除了question與tag表外應(yīng)該還有他們的關(guān)系表question_tag表,問題就出在question_tag表上。
如果我的question_tag表僅僅只有兩個字段QID與TID,那么用上面系列文章中提到的方法就可以實(shí)現(xiàn),關(guān)鍵代碼如下:
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 }
現(xiàn)在的問題是我的question_tag表為了業(yè)務(wù)需求不僅僅只有這兩個字段(這個需求應(yīng)該很常見,本例中增加一個時間字段DateAdded作為示例),因此用上面的方案就不行了。那么要怎么處理呢,找了好多資料都不行,沒辦法只好自己動手,豐衣足食。
首先想到的是,既然question_tag表中還有其它字段,那么這個實(shí)體肯定要表現(xiàn)出來。然后想到的是,按原來的方法question跟tag是直接產(chǎn)生聯(lián)系的,EF根據(jù)question和tag的定義可以判斷出是多對多關(guān)系,但現(xiàn)在加了一個關(guān)系實(shí)體question_tag,question跟question_tag的關(guān)系是一對多,tag跟question_tag的關(guān)系也是一對多,因此可以通過question_tag來聯(lián)接question跟tag(數(shù)據(jù)庫中這個表的存在本來就是這個意思),也就是說question跟tag不直接產(chǎn)生聯(lián)系。有了上面的想法,經(jīng)過多次嘗試后,我把實(shí)體間的關(guān)系修改為如下形式:
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 //顯示數(shù)據(jù)
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 }
很幸運(yùn)地通過了,運(yùn)行后數(shù)據(jù)庫中生成的表如下:

要注意的是這里QID和TID不僅僅是PK,也是FK。
程序的輸出如下:

可以看到程序能很好地滿足我的需求。在上面的刪除代碼中刪除了QID為1的question后,數(shù)據(jù)庫中question_tag表中的數(shù)據(jù)如下:

我們可以看到,question_tag表中QID為1的數(shù)據(jù)也同時刪除了,正是我們需要的結(jié)果。
最后做個小結(jié)吧:
一直以來都是看的多,寫的少,從老鳥那里吸收的多,貢獻(xiàn)的少,總怕自己寫的不好,以后爭取慢慢改變這種狀況,把自己學(xué)到的知識總結(jié)出來,爭取能給新手一些幫助吧。
希望這篇文章對大家有所幫助,當(dāng)然了,限于水平歡迎大家拍磚,提出更好的解決方案。

作者:Artwl
本文首發(fā)博客園,版權(quán)歸作者跟博客園共有。轉(zhuǎn)載必須保留本段聲明,并在頁面顯著位置給出本文鏈接,否則保留追究法律責(zé)任的權(quán)利。
浙公網(wǎng)安備 33010602011771號