如何處理 EF Core 的多對多關係?

如何處理 EF Core 的多對多關係?

多對多關係不像其他關係那麼簡單,在這篇文章中,我將向您展示如何建立多對多關係以及如何在 EF Core 中使用它們。

最後更新 2021/11/2 下午8:47
Zbigniew
預計閱讀 5 分鐘
分類
EF Core
標籤
.NET EF Core ORM

多對多關係不像其他關係那麼簡單,在這篇文章中,我將向您展示如何建立多對多關係以及如何在 EF Core 中使用它們。

模型

多對多的簡單而實用的例子可能是某種數位電子商務商店。使用者可以將商品放入購物車(一個購物車可以有多個商品),而商品屬於多個購物車。讓我們從建立 CartItem 類別開始。

public class Cart
{
    public int Id { get; set; }

    public ICollection<Item> Items { get; set; }
}
public class Item
{
    public int Id { get; set; }

    public string Name { get; set; }

    public int Quantity { get; set; }

    public ICollection<Cart> Carts { get; set; }
}

這看起來不錯,但它不起作用。在本文發表時,EF Core 無法處理這種情況。看起來 EF Core 不知道如何處理這種關係,當您嘗試新增遷移時,您會得到以下結果:

Unable to determine the relationship represented by navigation property ‘Cart.Items’ of type ‘ICollection’. Either manually configure the relationship, or ignore this property using the ‘[NotMapped]’ attribute or by using ‘EntityTypeBuilder.Ignore’ in ‘OnModelCreating’.

【無法確定類型為「ICollection<Item>」的導覽屬性「Cart.Items」表示的關係。請手動設定關係,或使用「[NotMapped]」屬性或在「OnModelCreating」中使用「EntityTypeBuilder.Ignore」忽略此屬性。】

我們需要做的第一件事是手動建立另一個「中間」類別(資料表),它將建立 CartItem 的多對多關係,讓我們建立這個類別:

public class CartItem
{
    public int CartId { get; set; }
    public Cart Cart { get; set; }

    public int ItemId { get; set; }
    public Item Item { get; set; }
}

我們建立了關聯 CartItem 的新類別 CartItem,我們還需要更改它們各自的導覽屬性:

public class Cart
{
    public int Id { get; set; }

    public ICollection<CartItem> Items { get; set; }
}
public class Item
{
    public int Id { get; set; }

    public string Name { get; set; }

    public int Quantity { get; set; }

    public ICollection<CartItem> Carts { get; set; }
}

如果您現在嘗試新增遷移,則會出現另一個錯誤:

The entity type ‘CartItem’ requires a primary key to be defined.

【實體類型「CartItem」需要定義一個主鍵。】

對,CartItem 沒有主鍵,由於它是多對多關係,因此它應該具有複合主鍵。複合主鍵類似於常規主鍵,但它由兩個屬性(資料行)而不是一個屬性組成。目前,建立複合鍵的唯一方法是在 OnModelCreating 中。

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    builder.Entity<CartItem>().HasKey(i => new { i.CartId, i.ItemId });
}

最後,我們的資料庫結構可以由 EntityFramework 處理,我們可以繼續遷移了。

插入多對多

假設我們已經有 CartItem 在我們的資料庫中,現在我們想將特定商品 (Item) 新增到特定購物車 (Cart),為了做到這一點,我們需要建立新的 CartItem 並儲存它。

var cart = db.Carts.First(i => i.Id == 256);
var item = db.Items.First(i => i.Id == 1024);

// 可以使用兩個類別的主鍵ID進行關聯
var cartItem = new CartItem
{
    CartId = cart.Id,
    ItemId = item.Id
};

// 也可以使用兩個類別實體進行關聯
var cartItem = new CartItem
{
    Cart = cart,
    Item = item
};

db.Add(cartItem);
db.SaveChanges();

在多對多中檢索相關資料

從資料庫中取得資料相當簡單,注意使用 Include 關聯檢索相關資料。這裡總共涉及 3 個資料表:CartItemCartItem(將商品 Item 與購物車 Cart 關聯起來)。

// 取得關聯所有商品的指定購物車
var cartIncludingItems = db.Carts.Include(cart => cart.Items).ThenInclude(row => row.Item).First(cart => cart.Id == 1);
// 取得指定購物車的所有商品
var cartItems = cartIncludingItems.Items.Select(row => row.Item);

另外,有些操作可以不使用關係來執行。例如,如果您有購物車 ID,則可以使用以下 Linq 一次取得所有商品:

var cartId = 1;
var cartItems = db.Items.Where(item => item.Carts.Any(j => j.CartId == cartId));

相同的原則適用於相反的用例,這意味著您可以套用上述模式來取得具有特定項目的所有購物車。

從多對多中刪除

刪除是指刪除購物車 Cart 和商品 Item 之間的關係 CartItem。在以下範例中,我們不會刪除購物車 Cart 或商品 Item,只會刪除購物車 Cart 和商品 Item 之間的關係 CartItem

讓我們從購物車 Cart 中刪除單一產品 Item 開始。

var cartId = 1;
var itemId = 1;
var cartItem = db.CartsItems.First(row => row.CartId == cartId && row.ItemId == itemId);

db.Remove(cartItem);
db.SaveChanges();

然後,讓我向您展示如何從購物車中刪除所有項目。

var cart = db.Carts.Include(c=> c.Items).First(i => i.Id == 2);

db.RemoveRange(cart.Items);
db.SaveChanges();

原文作者:Zbigniew

原文標題:How to handle Many-To-Many in Entity Framework Core

原文連結:https://softdevpractice.com/blog/many-to-many-ef-core/

翻譯:沙漠盡頭的狼

繼續探索

延伸閱讀

更多文章