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 约束。
像往常一样,当您不允许使用级联删除选项时(顺便说一下,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);
}
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)