本文共 8514 字,大约阅读时间需要 28 分钟。
Entity Framework中有三种关系,一对一(one-to-one),一对多(one-to-many),多对多(many-to-many),前两种就不说了,园子里这方面的文章很多(dudu的:,杨延成的:,郝冠军的:),看过之后简单的使用基本没什么问题,这里要说的是第三种:多对多(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 ICollectionTags { 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 Questions { get; set; } 19 } 20 21 public class BlogDbContext : DbContext 22 { 23 protected override void OnModelCreating(DbModelBuilder modelBuilder) 24 { 25 modelBuilder.Entity () 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 Questions { get; set; } 40 public IDbSet 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 ICollectionQuestionTags { 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 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 IDbSetQuestions { get; set; } 4 public IDbSet Tags { get; set; } 5 public IDbSet 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的数据也同时删除了,正是我们需要的结果。
最后做个小结吧:
一直以来都是看的多,写的少,从老鸟那里吸收的多,贡献的少,总怕自己写的不好,以后争取慢慢改变这种状况,把自己学到的知识总结出来,争取能给新手一些帮助吧。
希望这篇文章对大家有所帮助,当然了,限于水平欢迎大家拍砖,提出更好的解决方案。