[C#.NET][Entity Framework] DbSet<TEntity>.Remove 刪除關聯 - 預設行為
一般來講資料表關聯必需要自己處理,比如 Detail 要先刪除才能刪 Master,不過使用 ORM 則會幫我們處理關聯,這裡我們使用 Code First @ EF6,進行演練
一般來講資料表關聯必需要自己處理,比如 Detail 要先刪除才能刪 Master,不過使用 ORM 則會幫我們處理關聯,這裡我們使用 Code First @ EF6,進行演練,
假資料準備如下圖:
DbContext如下:
public class MyDbContext : DbContext { public DbSet<Product> Products { get; set; } public DbSet<Order> Orders { get; set; } public MyDbContext(string connectString) : base(connectString) { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); } }
POCO Proxy 定義規則
- 導覽(navigation)屬性必須宣告為 public、 virtual
- 關聯性中的「多」端的導覽屬性必須傳回會實作 ICollection 的型別
POCO(DTO)如下:
public class Product { public Product() { this.Orders = new HashSet<Order>(); } [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } public string Name { get; set; } public virtual ICollection<Order> Orders { get; set; } }
這裡的 Relation (ProductId) 為必填
public class Order { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } public string Company { get; set; } public decimal Price { get; set; } public int ProductId { get; set; } public virtual Product Product { get; set; } }
這樣所產生出來的DB結構,Delete Rule:Cascade 如下圖:
刪除 Master 程式碼如下:
using (var context = new MyDbContext("LocalDB")) { var find = context.Products.Find(1); if (find == null) { return; } context.Products.Remove(find); context.SaveChanges(); }
這時可以看到 Detail 也一並被幹掉了,如下圖:
假使需要留下Detail,可以將 Relation(ProductId) 設為非必要欄位,也就是將 ProductId 定義成 Nullable
public class Order { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } public string Company { get; set; } public decimal Price { get; set; } public int? ProductId { get; set; } public virtual Product Product { get; set; } }
這時候產生出來的關聯表 Delete rule:No Action,這是SQL的預設行為,如下圖:
調用刪除命令
因為消極載入(Lazy Loading)機制,在此我偷懶調用 context.Products.Load() 把 Detail 載入進來,或者用 Include 載入關聯資料載
若沒有處理關聯資料就調用刪除命令,會跳出例外
using (var context = new MyDbContext("LocalDB")) { context.Orders.Load(); context.Products.Load(); var find = context.Products.FirstOrDefault(o => o.Id == 1); if (find == null) { return; } context.Products.Remove(find); context.SaveChanges(); }
載入關聯的方式還可用Load/Include方法
1.context.Orders.Where(o => o.ProductId == 1).Load();
2.var find = context.Products.Include("Orders").FirstOrDefault(o => o.Id == 1)
刪除結果,Detail 的 FK 被設成 null,如下圖:
只要關聯資料在快取裡,當刪除 Master 時,EF 就會幫我把 Detail 設為 null
若不定義 FK (ProductId ),EF會幫我自動產生 SQL 欄位 Product_Id 並設為非必填,FK設為 No Action,
效果跟自行定義 public int? ProductId { get; set; } 相同
PS.我們也可以移除所有的 Cascade Delete
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
本文出自:http://www.dotblogs.com.tw/yc421206/archive/2014/04/16/144762.aspx
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET