EntityFramework:再谈 “如何映射聚合?”
背景返回目录
在之前的文章中《DDD:使用EntityFramework的话,如果只为聚合根设计仓储,其它实体如何处理?》,我介绍了如何映射聚合以保证其语义,当时的结论是:聚合内除了聚合根之外的实体必须使用多主键,否则删除操作(Order.OrderItems.Remove(1))只会将外键更新为 Null,最开始学习如何使用 EntityFramework 来映射聚合的时候,就纠结这个问题,当时汤雪华大哥就告诉了更新为 Null 就算删除了,当时感觉是接受了,不过没有内化,这篇文章也是为了内化这种思想。
使用多主键映射三级聚合返回目录
模型
1 public class Level1 2 { 3 public virtual int Level1Id { get; set; } 4 5 public virtual ICollection<Level2> Level2s { get; set; } 6 } 7 8 public class Level2 9 { 10 public virtual int Level2Id { get; set; } 11 public virtual int Level1Id { get; set; } 12 13 public virtual ICollection<Level3> Level3s { get; set; } 14 } 15 16 public class Level3 17 { 18 public virtual int Level3Id { get; set; } 19 public virtual int Level2Id { get; set; } 20 public virtual int Level1Id { get; set; } 21 }
映射
1 modelBuilder.Entity<Level1>() 2 .HasKey(x => x.Level1Id) 3 .Property(x => x.Level1Id) 4 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 5 modelBuilder.Entity<Level1>() 6 .HasMany(x => x.Level2s) 7 .WithRequired() 8 .HasForeignKey(x => x.Level1Id) 9 .WillCascadeOnDelete(); 10 11 modelBuilder.Entity<Level2>() 12 .HasKey(x => new { x.Level2Id, x.Level1Id }) 13 .Property(x => x.Level2Id) 14 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 15 modelBuilder.Entity<Level2>() 16 .HasMany(x => x.Level3s) 17 .WithRequired() 18 .HasForeignKey(x => new { x.Level2Id, x.Level1Id }) 19 .WillCascadeOnDelete(); 20 21 modelBuilder.Entity<Level3>() 22 .HasKey(x => new { x.Level3Id, x.Level2Id, x.Level1Id }) 23 .Property(x => x.Level3Id) 24 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
测试添加
1 for (var i = 1; i <= 2; i++) 2 { 3 var l1 = new Level1 4 { 5 Level2s = new List<Level2> 6 { 7 new Level2 8 { 9 Level3s = new List<Level3> 10 { 11 new Level3() 12 } 13 } 14 } 15 }; 16 context.Level1s.Add(l1); 17 context.SaveChanges(); 18 }
测试删除
1 using (var context = new StudyContext()) 2 { 3 context.Level1s.First().Level2s.First().Level3s.Clear(); 4 5 context.SaveChanges(); 6 }
说明
因为采用了多主键,所以 context.Level1s.First().Level2s.First().Level3s.Clear() 会真正的生成 delete 语句,否则只会把外键更新为 null,但是多主键感觉真是不爽,两级还可以接受。
多主键映射这种风格可以导致“物理删除”,单主键这种风格可以导致“逻辑删除”。
如果是逻辑删除,需要注意哪些事项?返回目录
- 确定何时物理删除?如何物理删除?可以后台自动根据配置的元数据定时物理删除。
- 报表查询的时候注意查询语句。
为啥不用级联删除?返回目录
级联删除是解决:rep.delete(order),双主键是解决:order.OrderItems.Clear()。
备注返回目录
前几天我还暗自高兴找到了“物理删除”的映射方式,今天又更喜欢“逻辑删除”了,害苦了我一位兄弟,他都是用的 “双主键”。