【问题标题】:EF Core delete one-to-one relation on same tableEF Core 删除同一张表上的一对一关系
【发布时间】:2019-02-07 17:31:05
【问题描述】:

模型与其自身具有可选关系

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”。这 数据库“本地数据库”中发生冲突,表 “dbo.Item”,列“OptionalItemId”。

我发现另一个有类似问题的问题:The DELETE statement conflicted with the SAME TABLE REFERENCE constraint with Entity Framework.
但看起来所有答案都与实体框架(而不是 EF Core)相关。

我尝试将删除行为更改为
- .OnDelete(DeleteBehavior.Cascade)

- .OnDelete(DeleteBehavior.SetNull)
但是在将迁移应用到数据库期间,这两种行为都会在下面引发异常。

引入 FOREIGN KEY 约束“FK_Item_Item_OptionalItemId” 表“项目”可能会导致循环或多个级联路径。
指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。

【问题讨论】:

  • 尝试在DBContext中为此实体设置.OnDelete(DeleteBehavior.SetNull)
  • @viveknuna,尝试过 - 但将迁移应用到数据库时抛出异常。
  • 你遇到了什么异常?
  • @viveknuna,有问题

标签: c# sql-server .net-core entity-framework-core ef-core-2.1


【解决方案1】:

像往常一样,当您不允许使用级联删除选项时(顺便说一句,SqlServer 限制,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...  
}

【讨论】:

    【解决方案2】:

    仅作为@Ivan 答案的补充。

    Item 有一个外键OptionalItem,这意味着Item 依赖于OptionalItem

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

    EF Core 支持从主体到依赖的“级联删除”。正如 Ivan Stoev 提到的,迁移期间的异常是 Sql Server 限制。但是EF Core还是会支持的,你可以试试
    - 添加.OnDelete(DeleteBehavior.Cascade)
    - 运行dotnet ef migrations add &lt;migration-name&gt;
    - 通过删除 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();         
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-13
      • 2011-10-04
      • 1970-01-01
      • 2019-07-25
      相关资源
      最近更新 更多