簡體   English   中英

EF Core刪除同一個表上的一對一關系

[英]EF Core delete one-to-one relation on same table

模型與其自身具有可選關系

public class Item
{
    public Guid Id { get; set; }
    public string Description { get; set; }
    public Guid StockId { get; set; }

    // optionally reference to another item from different stock
    public Guid? OptionalItemId { get; set; }

    public virtual Item OptionalItem { get; set; }      
}

在DbContext模型中配置如下:

protected override void OnModelCreating(ModelBuilder builder)
{
     builder.Entity<Item>().HasOne(item => item.OptionalItem)
                           .WithOne()
                           .HasForeignKey<Item>(item => item.OptionalItemId)
                           .HasPrincipalKey<Item>(item => item.Id)
                           .IsRequired(false)
}

我想用新項目替換已存在的項目,在更新Stock之前刪除已存在的新項目。

// Given Stock contains only new items
public void Update(Stock stock)
{
    using (var context = CreateContext())
    {
        // Remove old items
        var oldItems = context.Items
                              .Where(item => item.StockId == stock.Id)
                              .Select(item => new Item { Id = item.Id })
                              .ToList();
        context.Items.RemoveRange(oldItems);

        // Remove optional items from another stock
        var oldOptionalItems = context.Items
                                      .Where(item => item.StockId == stock.RelatedStock.Id)
                                      .Select(item => new Item { Id = item.Id })
                                      .ToList();
        context.Items.RemoveRange(oldOptionalItems);   

        context.Stocks.Update(stock);
        context.SaveChanges();         
    }
}

問題是當Update方法執行時,行context.SaveChanges()拋出異常:

SqlException:DELETE語句與SAME TABLE REFERENCE約束“FK_Item_Item_OptionalItemId”沖突。 沖突發生在數據庫“local-database”,表“dbo.Item”,列“OptionalItemId”中。

我發現了另一個類似問題的問題: DELETE語句與實體框架的SAME TABLE REFERENCE約束沖突
但看起來所有答案都與Entity Framework(不是EF Core)有關。

我嘗試過更改刪除行為
- .OnDelete(DeleteBehavior.Cascade)

- .OnDelete(DeleteBehavior.SetNull)
但是在應用遷移到數據庫期間,這兩種行為都會拋出異常。

在表'Item'上引入FOREIGN KEY約束'FK_Item_Item_OptionalItemId'可能會導致循環或多個級聯路徑。
指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他FOREIGN KEY約束。

像往常一樣,當你不允許使用級聯刪除選項(SqlServer限制btw,某些數據庫如Oracle沒有這樣的問題)時,你需要(遞歸地)刪除相關數據,然后再刪除記錄。

它可以逐個或按級別完成(較少的SQL命令,但可能使用大的IN PK列表)。 相關數據也可以使用基於CTE的SQL來確定 - 這是最有效但數據庫無關的方式。

以下方法實現第二種方法:

static void DeleteItems(DbContext context, Expression<Func<Item, bool>> filter)
{
    var items = context.Set<Item>().Where(filter).ToList();
    if (items.Count == 0) return;
    var itemIds = items.Select(e => e.Id);
    DeleteItems(context, e => e.OptionalItemId != null && itemIds.Contains(e.OptionalItemId.Value));
    context.RemoveRange(items);
}

並可以在您的代碼中使用,如下所示:

using (var context = CreateContext())
{
    // Remove old items
    DeleteItems(context, item => item.StockId == stock.Id);

    // Remove optional items from another stock
    DeleteItems(context, item => item.StockId == stock.RelatedStock.Id);

    // The rest...  
}

只是作為@Ivan的答案的補充。

Item具有OptionalItem的外鍵,這意味着Item依賴於OptionalItem

`Item`(dependent) -> `OptionalItem`(principal)

EF Core支持從主體到依賴的“級聯刪除”。 正如Ivan Stoev所提到的,遷移期間的異常是Sql Server限制。 但是EF Core仍然支持它,你可以試試
- 添加.OnDelete(DeleteBehavior.Cascade)
- 運行dotnet ef migrations add <migration-name>
- 通過刪除CASCADE操作更新生成的遷移腳本
- 使用剛創建的遷移更新數據庫

在將遷移應用於數據庫期間,您不會遇到異常。
注意:
1.(再次)EF Core支持從主體到依賴的級聯刪除
刪除OptionalItem記錄時,將刪除相關Item
2. EF Core將自動刪除已經由DbContext跟蹤的相關記錄(加載到內存中)

因此,在您的情況下,您可以嘗試在依賴Item之前刪除主項( OptionalItem ),但是在單獨的命令中。
在事務中執行all,因此在發生錯誤時將回滾操作。

public void Update(Stock stock)
{
    using (var context = CreateContext())
    using (var transaction = context.Database.BeginTransaction())
    {
        // Remove optional items from another stock
        // This is principal record in the items relation
        var oldOptionalItems = context.Items
                                      .Where(item => item.StockId == stock.RelatedStock.Id)
                                      .Select(item => new Item { Id = item.Id })
                                      .ToList();
        context.Items.RemoveRange(oldOptionalItems);

        // Remove them actually from the database
        context.SaveChanges();

        // Remove old items
        var oldItems = context.Items
                          .Where(item => item.StockId == stock.Id)
                          .Select(item => new Item { Id = item.Id })
                          .ToList();
        context.Items.RemoveRange(oldItems);

        context.Stocks.Update(stock);
        context.SaveChanges();         
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM