[英]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.