Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/67.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何删除级联底部的实体->;EF中的非级联删除链?_C#_Sql_Entity Framework - Fatal编程技术网

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

C# 如何删除级联底部的实体->;EF中的非级联删除链?,c#,sql,entity-framework,C#,Sql,Entity Framework,很抱歉这个毫无用处的标题。。。我无法用更好的话来形容这个问题 我有以下实体,每个实体都由Id属性标识: 底盘 槽 纸牌 当我使用POCO时,这些实体并不是什么突破性的东西。例如,机箱类的定义如下: public class Chassis { public int Id { get; set; } // Other properties omitted for brevity. public ICollection<Slot> Slots { get;

很抱歉这个毫无用处的标题。。。我无法用更好的话来形容这个问题

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

  • 底盘
  • 纸牌
当我使用POCO时,这些实体并不是什么突破性的东西。例如,
机箱
类的定义如下:

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>();
    }
}
。。。但这引发了一个例外:

EntityFramework.dll中出现“System.Data.Entity.Infrastructure.DbUpdateException”类型的异常,但未在用户代码中处理

其他信息:无法插入或更新实体,因为“机箱\ U插槽”关系的主体端已删除

然后我尝试添加
ctx.Detach
在我的内部
foreach
循环中,要停止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会发出哭声,但以下情况除外:

其他信息:DELETE语句与引用约束“FK_dbo.Slots_dbo.Cards_cardd”冲突。冲突发生在数据库“…”、表“dbo.Slots”、列“cardd”中

声明已终止

。。。这让我在岩石和坚硬的地方之间,完全失去了想法

有人能推荐一种更成功的方法吗?

以下是顺序

  • 机箱
    被删除时,由于删除级联机制,所有
    插槽
    也将被删除。简单对吧
  • 然后拦截保存更改,您还希望在同一事务单上手动删除
    Card
    ,我不确定EF将如何生成sql查询,即使稍后添加了cards deletation语法,但当我使用探查器检查时,它首先删除了卡(应该是机箱优先)
  • 发生的情况是,
    未更改的插槽
    (将由
    删除级联
    自动删除)更改为
    修改的插槽
  • 为什么??因为当您同时删除
    时,相应的
    插槽的CardId
    必须设置为空
  • 现在,最终结果是
    插槽
    被修改,但
    机箱
    已被删除
要解决这个问题,您需要引入新的事务/上下文

如果您使用EF6以后的版本,您只需使用其他版本即可使用
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;
}
public override int SaveChanges()
{
var deletedCardIds=新列表();
var chassises=ChangeTracker.Entries()。其中(e=>e.State==EntityState.Deleted);
foreach(底盘中的var底盘)
{
var cardIds=Slots.Where(s=>s.ChassisId==chassis.Entity.Id)
.Where(s=>s.cardd.HasValue)
.Select(s=>s.cardd.Value)
.ToArray();
删除CardCardDS.AddRange(CardDS);
}
内部原始rowsaffected;
int-additionalRowsAffected;
使用(var transaction=new TransactionScope())
{
originalRowsAffected=base.SaveChanges();
deletedCardIds.Distinct().ToList()文件
.ForEach(id=>Entry(新卡{id=id}).State=EntityState.Deleted);
additionalRowsAffected=base.SaveChanges();
transaction.Complete();
}
返回原始rowsaffected+附加rowsaffected;
}

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

为什么要删除机箱,然后在额外事件中只删除卡?我相信这是你所有问题的根源。在我的项目中,每当我想删除数据库中没有明确级联关系的父项和子项时,我总是从子项开始,然后毫无问题地删除父项

在删除机箱的代码中,获取所有插槽并遍历它们,然后移除所有卡,就像您当前在事件中所做的那样。如果在删除机箱或插槽之前将卡标记为已删除,则在删除卡时不会将插槽标记为已修改。这将防止您的错误,并允许您在一个事务中删除所有内容


将所有图元标记为在一个点中删除也更为清晰易读。现在,阅读chassis delete的任何人都很容易意识到,它也会删除与之相关的任何卡。

当前解决方案中有一些因素使级联删除变得麻烦

  • 实体是自参考的。也就是说,一张卡可以是叶级卡(没有子卡),也可以是聚合子卡的分支级卡。从域的角度来看,我想象一张卡要么没有子卡,要么只有一组子卡。我将使用这个假定的领域知识作为解决方案的一部分。如果有了自由统治,我会把它改造成两个实体:
    Card
    CardWithChildren
    ,并将其明确化

  • 在不了解您的域的情况下,
    插槽
    实体似乎纯粹是一个面向对象的设备,允许
    实体属于一个
    机箱
    或另一个
    。然而,我会从另一个角度来看待这种关系,看看问题中是如何表述的。与插槽可以属于机箱或卡不同,我会将其建模为卡属于插槽,然后在卡上以多态方式表示关系。这可以进一步简化为仅表示一张卡由机箱或另一张卡拥有

  • 下面是一个考虑到上述因素的示例。它具有以下特点:

  • 为了允许卡与包含机箱和父卡之间的关系,我对
    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;
    }
    
    begin tran
    delete from dbo.Chassis
    delete from dbo.Cards
    commit tran
    
    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;
    }
    
    public abstract class CardContainer
    {
        public int Id { get; set; }
        public ICollection<Card> Cards { get; set; }
    
        protected CardContainer()
        {
            Cards = new List<Card>();
        }
    }
    
    public class Chassis : CardContainer
    {             
    }
    
    public class Card : CardContainer
    {
        public CardContainer Container { get; set; }
        public int ContainerId { get; set; }
    }    
    
    public class CardContainerConfiguration : EntityTypeConfiguration<CardContainer>
    {
        public CardContainerConfiguration()
        {
            ToTable("CardContainers");
            HasKey(k => k.Id);
            Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);   
            HasMany(f => f.Cards)
                .WithRequired(p => p.Container)
                .HasForeignKey(p => p.ContainerId)
                .WillCascadeOnDelete(true);
        }
    }
    
    public class ChassisConfiguration : EntityTypeConfiguration<Chassis>
    {
        public ChassisConfiguration()
        {
            ToTable("Chassis");
        }
    }
    
    public class CardConfiguration : EntityTypeConfiguration<Card>
    {
        public CardConfiguration()
        {
            ToTable("Cards");
            HasRequired(x => x.Container).WithMany(x => x.Cards).HasForeignKey(x => x.ContainerId);            
        }
    }    
    
    public class EfContext : DbContext
    {              
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {           
            modelBuilder.Configurations.Add(new CardContainerConfiguration());
            modelBuilder.Configurations.Add(new ChassisConfiguration());
            modelBuilder.Configurations.Add(new CardConfiguration());           
    
            base.OnModelCreating(modelBuilder);
        }
    }
    
    public void CanCascadeDeleteCards()
    {
        using (var context = new EfContext())
        {
            // We have to tell Entity Framework of the maximum depth of the 
            // card-->card relationship using the Include method
            var chassis = context.CardContainers.OfType<Chassis>()
                .Include(x => x.Cards.Select(y => y.Cards))
                .First();
            context.CardContainers.Remove(chassis);             
            context.SaveChanges();
        }
    }