模型本身具有可选关系
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冲突 参考约束 “ 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约束。
答案 0 :(得分:3)
通常,当不允许使用级联删除选项时(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...
}
答案 1 :(得分:1)
仅作为@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支持从主体到从属的级联删除
与相关的Item
将在您删除OptionalItem
的记录时被删除
2. EF Core将仅自动删除DbContext已跟踪的相关记录(已加载到内存中)
因此,在您的情况下,您可以尝试删除相关项OptionalItem
之前的主体项目(Item
),但要使用单独的命令。
在事务中执行所有操作,因此发生错误时将回滚操作。
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();
}
}