gpt4 book ai didi

c# - 如何删除级联底部的实体 -> EF中的非级联删除链?

转载 作者:行者123 更新时间:2023-11-30 14:10:17 25 4
gpt4 key购买 nike

抱歉,这个标题毫无用处...我无法用更好的词来表达这个问题。

我有以下实体,每个实体都由 Id 属性标识:

  • 底盘
  • 插槽
  • 卡片

当我使用 POCO 时,这些实体并没有什么突破性的。例如,Chassis 类定义如下:

public class Chassis
{
public int Id { get; set; }

// Other properties omitted for brevity.

public ICollection<Slot> Slots { get; set; }

public Chassis()
{
Slots = new Collection<Slot>();
}
}

关系如下:

  • 机箱有很多插槽(CASCADE)。
  • 一张卡有很多槽,因为一张卡可以是多槽卡(SET NULL)。
  • 插槽必须属于Chassis (pubic int ChassisId { get; set; })
  • 插槽不一定要有Card (public int?CardId { get; set; })。

因此,当删除机箱时,所有插槽都会被删除。 很好。但是,我还想删除所有安装在这些插槽中的卡。 不好。我正在尝试将一些代码挂接到 OnSavingChanges 事件(我在 SaveChanges() 之前触发),这样当机箱被标记为已删除时,我会删除卡片也是为了它。

首先,我试过:

OnSavingChanges += (x, y) =>
{
var ctx = x as DbContext;
var chassis = ctx.ChangeTracker.Entries<Chassis>().Where(e => e.State == EntityState.Deleted);

// Delete all cards on a deleted chassis.
foreach (var c in chassis)
{
// Cannot just do c.Slots, as EF seems to empty the nav. property now the
// chassis is deleted.
var slots = ctx.Slots.Where(s => s.ChassisId == c.Entity.Id).ToList();

foreach (var s in slots)
{
if (s.Card != null)
{
ctx.Cards.Remove(s.Card);
}
}
}
};

...但这会引发异常:

An exception of type 'System.Data.Entity.Infrastructure.DbUpdateException' occurred in EntityFramework.dll but was not handled in user code

Additional information: Unable to insert or update an entity because the principal end of the 'Chassis_Slots' relationship is deleted.

然后我尝试在我的内部 foreach 循环中添加 ctx.Detach(s); 以阻止 EF 尝试保存已删除的级联插槽实体:

foreach (var s in slots)
{
if (s.Card != null)
{
ctx.Cards.Remove(s.Card);
}

// Otherwise EF attempts to save the slot, which results in a exception saying the principle
// end of the relatioship Chassis_Slots has already been deleted.
ctx.Detach(s);
}

...然而 EF 然后哭泣,但出现以下异常:

Additional information: The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.Slots_dbo.Cards_CardId". The conflict occurred in database "...", table "dbo.Slots", column 'CardId'.

The statement has been terminated.

...这让我进退两难,完全没有想法。

谁能建议一种更成功的方式/方法来做到这一点?

最佳答案

这是顺序。

  • 当删除Chassis时,由于删除级联机制,所有Slots也将被删除。简单吧?
  • 然后您拦截保存的更改,您还想在同一事务 单个SaveChanges 上手动删除Card,我不是确定 EF 将如何生成 sql 查询,即使稍后添加了卡片删除语法,但是当我使用探查器检查它时,它首先删除了卡片(应该首先删除机箱)。
  • 发生的情况是,Unchanged Slot(将被 delete cascade 自动删除)变为 Modified Slot
  • 为什么?因为当你也删除Card时,相应的Slot的CardId必须设置为null。
  • 现在的最终结果是Slot被修改了,但是Chassis被删除了。

要解决这个问题,您需要引入新的事务/上下文。

public override int SaveChanges()
{
var deletedCardIds = new List<int>();
var chassises = ChangeTracker.Entries<Chassis>().Where(e => e.State == EntityState.Deleted);
foreach (var chassis in chassises)
{
var slots = Slots.Where(s => s.ChassisId == chassis.Entity.Id).ToArray();
foreach (var slot in slots)
{
if (slot.CardId.HasValue && !deletedCardIds.Contains(slot.CardId.Value))
{
deletedCardIds.Add(slot.CardId.Value);
}
}
}

// Commits original transaction.
var originalRowsAffected = base.SaveChanges();

int additionalRowsAffected = 0;
if (deletedCardIds.Count > 0)
{
// Opens new transaction.
using (var newContext = new AppContext())
{
foreach (var cardId in deletedCardIds)
{
var deletedCard = newContext.Cards.Find(cardId);
if (deletedCard != null)
{
newContext.Cards.Remove(deletedCard);
}
}

// Commits new transaction.
additionalRowsAffected = newContext.SaveChanges();
}
}

return originalRowsAffected + additionalRowsAffected;
}

附言

  • 您有两个独立的事务,这可能会导致意外行为(不保证 atomicity)。
  • 您可能想要重新设计数据库以获得理想的解决方案。

更新

今天我才意识到我们可以简单地使用 TransactionScopecommit several operations in a single transaction 。就像在数据库中执行此代码一样。

begin tran
delete from dbo.Chassis
delete from dbo.Cards
commit tran

如果您使用 EF6 及更高版本,您可以只使用 Database.BeginTransaction,否则使用 TransactionScope

public override int SaveChanges()
{
var deletedCardIds = new List<int>();
var chassises = ChangeTracker.Entries<Chassis>().Where(e => e.State == EntityState.Deleted);
foreach (var chassis in chassises)
{
var cardIds = Slots.Where(s => s.ChassisId == chassis.Entity.Id)
.Where(s => s.CardId.HasValue)
.Select(s => s.CardId.Value)
.ToArray();
deletedCardIds.AddRange(cardIds);
}

int originalRowsAffected;
int additionalRowsAffected;
using (var transaction = new TransactionScope())
{
originalRowsAffected = base.SaveChanges();

deletedCardIds.Distinct().ToList()
.ForEach(id => Entry(new Card { Id = id }).State = EntityState.Deleted);
additionalRowsAffected = base.SaveChanges();

transaction.Complete();
}

return originalRowsAffected + additionalRowsAffected;
}

通过在上面更新的代码中引入事务,SaveChanges要么全部发生,要么什么都不发生,现在保证了原子性。

关于c# - 如何删除级联底部的实体 -> EF中的非级联删除链?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24828603/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com