EF代码优先复杂级联删除场景

7

I have this scenario: I have these classes:

public class A
{
   public int Id {get;set;}
   public virtual ICollection<B> bCollection {get; set; }
}

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

}

public class C1 : BaseClass1
{
   public int Id{get;set;}
   public virtual B B{get;set;}
}

public class C2 : BaseClass2
{
   public int Id {get;set;}
   public virtual B B {get;set;}
}

...
 public class C100 : BaseClass100
{
   public int Id {get;set;}
   public virtual B B {get;set;}
}

类A拥有类B的集合,而类Ci只有一个类B和不同的基类。当在类A的集合中只有B没有被Ci引用时,我可以删除类A并且所有的B集合也会被级联删除。但是,当在类A的集合中有被类Ci引用的B时,我不能删除类A的实例...

我的期望行为:

类A将被删除以及它所拥有的所有B集合。如果类Ci引用了其中的某些B,则在删除结束时它们将变为空(null)。(类Ci的实例将不会被删除!)我也不想遍历所有的Ci类来查看它是否引用了需要删除的B集合。

我不希望使用这段代码:

MyDbContext db=new MyDbContext();
Hash<int> bCollectionToDelete=GetBCollectionToDeleteHashSet();
var C1_db_collection= db.C1Collection.ToList();
foreach(var c1Item in C1Collection)
{
    if(bCollectionToDelete.Contains(c1Item.refrenceIdToB)
    {
       c1Item.refrenceIdToB=null;
    }
}

 var C2_db_collection= db.C2Collection.ToList();
foreach(var c2Item in C1Collection)
{
    if(bCollectionToDelete.Contains(c2Item.refrenceIdToB)
    {
       c2Item.refrenceIdToB=null;
    }
}

...
 var C100_db_collection= db.C100Collection.ToList();
foreach(var c100Item in C100Collection)
{
    if(bCollectionToDelete.Contains(c100Item.refrenceIdToB)
    {
       c100Item.refrenceIdToB=null;
    }
}

有人知道如何实现吗?

而且我认为它违反了开放/封闭原则:明天会有更多的类,这将导致所有检查Ci是否引用B的函数发生更多的变化... - ilay zeidman
在这些Ci类中,是否可能将外键可空设置为B - Mat J
@ilayzeidman 是的。在这种情况下,你不能配置一个 On Delete Set Null 吗? - Mat J
1
这里的基本问题是EF没有配置级联删除和更新操作的选项。因此,您必须通过代码或管理工具手动更新每个表的级联操作。 - Mat J
你是在设置所有从不同BaseClass派生的C类,还是所有C类都从同一个类派生? - Pedro.The.Kid
显示剩余15条评论
3个回答

3
这可能是您编写存储过程来处理级联删除逻辑的情况,而EF代码只需调用“DeleteMyObject”存储过程一次,然后您的ef代码需要刷新其上下文以立即重新加载更改。
刷新上下文会带来轻微的性能损失,但如果我猜测将逻辑移动到sp中,并在那里执行相互关联的删除操作的性能收益,将抵消刷新的小的性能损失。
不确定存储过程是否适合您,但是当您需要时它们可供使用。
编辑:
简化的示例存储过程,它接受表'MainTable'的主键,然后从3个'ChildTables'中删除任何相关记录(如果存在)。 您的实际逻辑可能会更加复杂。
CREATE PROCEDURE DeleteMyObject @Id INT
as
   BEGIN TRAN
     delete from ChildTable1 WHERE ParentId=@Id
     delete from ChildTable2 WHERE ParentId=@Id
     delete from ChildTable3 WHERE ParentId=@Id
     delete from MainTable WHERE ID=@Id

   COMMIT TRAN

调用未映射到您的EF模型中的存储过程示例:

SqlParameter param1 = new SqlParameter("@Id", 12345); context.Database.ExecuteSqlCommand("DeleteMyObject @Id",param1);

进一步阅读: http://msdn.microsoft.com/en-us/data/gg699321.aspx


我不太熟悉这个,你能提供一个解释这种方法的链接吗? - ilay zeidman
你对存储过程一点都不熟悉吗?或者不知道如何从 EF 中调用它们? - E.J. Brennan
我会在接下来的几天里尝试它的性能,对于这项研究加一点点赞... - ilay zeidman

0

不必在所有C类中搜索,您可以让B自己移除

通过使用接口

public interface IRemoveB
{
    void RemoveB();
}

public class C1 : BaseClass1, IDeleteB
{
    public int Id { get; set; }
    public virtual B B { get; set; }

    #region IDeleteB Member

    public void RemoveB()
    {
        this.B = null;
    }

    #endregion
}


public class B
{
    public int Id { get; set; }
    public ICollection<IDeleteB>  list{ get; set; }

}
public class A
{
    public int Id { get; set; }
    public virtual ICollection<B> bCollection { get; set; }

    public void prepareForDelete()
    {
        foreach (var item in bCollection)
        {
            foreach (var deref in item.list)
            {
                deref.RemoveB();
            }
        }
    }
}

通过使用委托

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

    public virtual ICollection<B> bCollection { get; set; }

    public void prepareForDelete()
    {
        foreach (var item in bCollection)
        {
            item.RemoveMe();
        }
    }
}


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

    public event EventHandler Remove;

    public void RemoveMe()
    {
        EventHandler removeHandler = Remove;
        if (removeHandler != null)
        {
            removeHandler(this, EventArgs.Empty);
        }
    }
}
public class C2 : BaseClass2
{
    public int Id { get; set; }

    private B internB;

    // maybe you need an otherform to set B because of your virtual 
    public B B
    {
        get { return internB; }
        set
        {
            if (internB != value)
                if (internB != null)
                    internB.Remove -= this.RemoveB;
                else if(value != null)
                    value.Remove += this.RemoveB;
            internB = value;
        }
    }

    public void RemoveB(object sender, EventArgs args)
    {
        internB.Remove -= this.RemoveB;
        B = null;

    }
}

编辑:

回答你的问题:

你的意思是每个类都继承自同一个接口吗?
是的,没错。这里有更多关于接口的信息

如果是这样,ICollection<IDeleteB>会被存储在数据库中吗?
不会,它只存在于你的程序中。可以将其视为临时的1:N引用表。

在委托示例中,它是如何存储在数据库中的?
实际上与集合是基本相同的。

而且,这仅在B in A == B in C的情况下才有效,这意味着它是类B的相同实例


你的例子听起来很有趣,但对我来说不是完全清晰。能否再解释一下? - ilay zeidman
你想了解什么?(哪个部分不够清晰,这样我就可以专注于那个部分 :)) - WiiMaxx
你的意思是每个类都从同一个接口继承吗?如果是的话,ICollection<IDeleteB>会被存储在数据库中吗?在委托的例子中,它是如何存储在数据库中的? - ilay zeidman
啊,我明白我的错误了。我以为我们总是在A和C中谈论同一个B对象,但是当A中的B != C中的B时,你不能使用我的解决方案,我能想到的唯一方法是使用静态的List<B>,让A和C从中获取它们的B,这样A中的B == C中的B - WiiMaxx

-2

您可以在 OnModelCreating 方法中为每个类 Ci 设置 delete cascade 选项为 true,方法是添加以下内容

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // for each class Ci write the following
    modelBuilder.Entity<Ci>()
            .HasRequired(t=>T.B)
            .WithMany()
            .WillCascadeOnDelete(true);
}

通过这个,如果存在B类的Ci,删除将会为您删除它。希望能对您有所帮助。


我觉得你错过了一行:“如果Ci引用集合中的某些B,那么在删除操作结束时它将为空”,我的意思是我不想删除Ci实例... - ilay zeidman
是的,因为删除B => 所有Ci都将自动级联删除。 - Monah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接