[C#.NET][Entity Framework] DbSet TEntity Remove 刪除關聯 - 預設行為

[C#.NET][Entity Framework] DbSet<TEntity>.Remove 刪除關聯 - 預設行為
一般來講資料表關聯必需要自己處理,比如 Detail 要先刪除才能刪 Master,不過使用 ORM 則會幫我們處理關聯,這裡我們使用 Code First @ EF6,進行演練

一般來講資料表關聯必需要自己處理,比如 Detail 要先刪除才能刪 Master,不過使用 ORM 則會幫我們處理關聯,這裡我們使用 Code First @ EF6,進行演練,

假資料準備如下圖:

image

 

 

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 如下圖:

image

 

 

 

刪除 Master 程式碼如下:


using (var context = new MyDbContext("LocalDB")) 
{ 
    var find = context.Products.Find(1); 
    if (find == null) 
    { 
        return; 
    } 
    context.Products.Remove(find); 
    context.SaveChanges(); 
}

 

 

 

這時可以看到 Detail 也一並被幹掉了,如下圖:

image

 

 

 

假使需要留下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的預設行為,如下圖:

image

 

 

 

調用刪除命令

因為消極載入(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,如下圖:

image

 

只要關聯資料在快取裡,當刪除 Master 時,EF 就會幫我把 Detail 設為 null

若不定義 FK (ProductId ),EF會幫我自動產生 SQL 欄位 Product_Id 並設為非必填,FK設為 No Action,

效果跟自行定義 public int? ProductId { get; set; } 相同

image

 

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

Image result for microsoft+mvp+logo