加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

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

发布时间:2020-12-16 02:02:29 所属栏目:百科 来源:网络整理
导读:对不起,完全无益的标题……我无法将这个问题变成更好的话. 我有以下实体,每个实体都由Id属性标识: 底盘 老虎机 卡片 当我使用POCO时,这些实体并没有什么突破性的.例如,Chassis类的定义如下: public class Chassis{ public int Id { get; set; } // Other p
对不起,完全无益的标题……我无法将这个问题变成更好的话.

我有以下实体,每个实体都由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).
>插槽必须属于机箱(pubic int ChassisId {get; set;})
>一个插槽没有卡(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.

然后我尝试添加ctx.Detach(s);在我的内部foreach循环中,停止EF试图保存已经删除的通过级联Slots实体:

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.

…有点让我在岩石和坚硬的地方之间,完全出于想法.

任何人都可以建议一个更成功的方法/方法来做到这一点?

解决方法

这是序列.

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

要解决此问题,您需要引入新的事务/上下文.

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;
}

PS

>您有两个独立的事务,可能会导致意外行为(atomicity无法保证).
>您可能希望重新设计数据库以获得理想的解决方案.

UPDATE

今天我才意识到我们可以简单地使用TransactionScope到commit 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既可以全部发生,也不会发生任何事情,现在保证原子性.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读