EF Core删除同一张表上的一对一关系

Bas*_*sin 5 c# sql-server entity-framework-core .net-core ef-core-2.1

模型与自身具有可选关系

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; }      
}
Run Code Online (Sandbox Code Playgroud)

在 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)
}
Run Code Online (Sandbox Code Playgroud)

我想通过在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();         
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是当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 约束。

Iva*_*oev 5

像往常一样,当您不允许使用级联删除选项时(顺便说一下,SqlServer 限制,某些数据库(如 Oracle)没有此类问题),您需要(递归)在删除记录之前删除相关数据。

它可以逐个或按级别完成(较少的 SQL 命令,但可能使用大INPK 列表)。相关数据也可以使用基于 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);
}
Run Code Online (Sandbox Code Playgroud)

并且可以像这样在您的代码中使用:

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...  
}
Run Code Online (Sandbox Code Playgroud)