DELETE语句与实体框架的SAME TABLE REFERENCE约束冲突

Kon*_*Kon 15 .net c# sql-server entity-framework fluent-interface

我有一个带有自引用的表,其中ParentId是ID(PK)的FK.
使用EF(代码优先),我建立了如下关系:

this.HasOptional(t => t.ParentValue)
    .WithMany(t => t.ChildValues)
    .HasForeignKey(t => t.ParentId);
Run Code Online (Sandbox Code Playgroud)

当我尝试删除子节点及其父节点时,DELETE命令对数据库的EF问题不按我预期的顺序 - 它首先尝试删除父节点.

我意识到我在这里有几个选项(我都不喜欢):

  1. 首先删除子记录,执行完整保存/提交,然后删除父记录.由于我的模型的复杂性和维护它的逻辑,这不是一个选项 - 不能随时发出多个提交命令.
  2. 在删除任何内容之前解散关系.这似乎是一个更明智的解决方案,但同样,我必须在DELETE之前使用UPDATE语句发出单独的提交.我想避免多次保存/提交调用.
  3. 在删除父记录之前使用触发器删除子项.但我想尽可能避免触发器及其有问题的性质.

所以问题是..有没有办法在父记录之前强制删除子项?也许我错过了某种明确的方式告诉EF它需要在父母之前照顾这些孩子?也许有一种方法可以指示EF按ID的降序删除?我不知道......想法?

gee*_*ter 18

我意识到答案是一岁,但我发现它不完整.在我看来,自引用表用于表示任意深度.

例如,请考虑以下结构:

/*  
 *  earth
 *      europe
 *          germany
 *          ireland
 *              belfast
 *              dublin
 *      south america
 *          brazil
 *              rio de janeiro
 *          chile
 *          argentina                 
 *               
 */
Run Code Online (Sandbox Code Playgroud)

答案并没有解决如何从上面的结构中删除地球或欧洲的问题.

我提交以下代码作为替代方案(由Slauma提供的答案的修改,谁做得很好btw).

在MyContext类中,添加以下方法:

public void DeleteMyEntity(MyEntity entity)
{
    var target = MyEntities
        .Include(x => x.Children)
        .FirstOrDefault(x => x.Id == entity.Id);

    RecursiveDelete(target);

    SaveChanges();

}

private void RecursiveDelete(MyEntity parent)
{
    if (parent.Children != null)
    {
        var children = MyEntities
            .Include(x => x.Children)
            .Where(x => x.ParentId == parent.Id);

        foreach (var child in children)
        {
            RecursiveDelete(child);
        }
    }

    MyEntities.Remove(parent);
}
Run Code Online (Sandbox Code Playgroud)

我使用以下类使用代码优先填充数据:

public class TestObjectGraph
{
    public MyEntity RootEntity()
    {
        var root = new MyEntity
        {
            Name = "Earth",
            Children =
                new List<MyEntity>
                    {
                        new MyEntity
                        {
                            Name = "Europe",
                            Children =
                                new List<MyEntity>
                                {
                                    new MyEntity {Name = "Germany"},
                                    new MyEntity
                                    {
                                        Name = "Ireland",
                                        Children =
                                            new List<MyEntity>
                                            {
                                                new MyEntity {Name = "Dublin"},
                                                new MyEntity {Name = "Belfast"}
                                            }
                                    }
                                }
                        },
                        new MyEntity
                        {
                            Name = "South America",
                            Children =
                                new List<MyEntity>
                                {
                                    new MyEntity
                                    {
                                        Name = "Brazil",
                                        Children = new List<MyEntity>
                                        {
                                            new MyEntity {Name = "Rio de Janeiro"}
                                        }
                                    },
                                    new MyEntity {Name = "Chile"},
                                    new MyEntity {Name = "Argentina"}
                                }
                        }
                    }
        };

        return root;
    }
}
Run Code Online (Sandbox Code Playgroud)

我使用以下代码保存到我的数据库:

ctx.MyEntities.Add(new TestObjectGraph().RootEntity());
Run Code Online (Sandbox Code Playgroud)

然后像这样调用删除:

using (var ctx = new MyContext())
{
    var parent = ctx.MyEntities
        .Include(e => e.Children)
        .FirstOrDefault();

    var deleteme = parent.Children.First();

    ctx.DeleteMyEntity(deleteme);
}
Run Code Online (Sandbox Code Playgroud)

这导致我的数据库现在有这样的结构:

 /*  
 *  earth
 *      south america
 *          brazil
 *              rio de janeiro
 *          chile
 *          argentina                 
 *               
 */
Run Code Online (Sandbox Code Playgroud)

欧洲及其所有儿童被删除的地方.

在上面,我指定了根节点的第一个子节点,以证明使用我的代码可以递归地从层次结构中的任何位置删除节点及其所有子节点.

如果你想测试删除每一个,你可以简单地修改这样的行:

ctx.DeleteMyEntity(parent);
Run Code Online (Sandbox Code Playgroud)

或者您想要在树中的任何节点.

显然,我不会得到赏金,但希望我的帖子能帮助那些寻找适用于任意深度自我引用实体的解决方案的人.

这是完整的源代码,它是来自所选答案的Slauma代码的修改版本:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;

namespace EFSelfReference
{
    public class MyEntity
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public int? ParentId { get; set; }
        public MyEntity Parent { get; set; }

        public ICollection<MyEntity> Children { get; set; }
    }

    public class MyContext : DbContext
    {
        public DbSet<MyEntity> MyEntities { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<MyEntity>()
                .HasOptional(e => e.Parent)
                .WithMany(e => e.Children)
                .HasForeignKey(e => e.ParentId);
        }


        public void DeleteMyEntity(MyEntity entity)
        {
            var target = MyEntities
                .Include(x => x.Children)
                .FirstOrDefault(x => x.Id == entity.Id);

            RecursiveDelete(target);

            SaveChanges();

        }

        private void RecursiveDelete(MyEntity parent)
        {
            if (parent.Children != null)
            {
                var children = MyEntities
                    .Include(x => x.Children)
                    .Where(x => x.ParentId == parent.Id);

                foreach (var child in children)
                {
                    RecursiveDelete(child);
                }
            }

            MyEntities.Remove(parent);
        }
    }

    public class TestObjectGraph
    {
        public MyEntity RootEntity()
        {
            var root = new MyEntity
            {
                Name = "Earth",
                Children =
                    new List<MyEntity>
                    {
                        new MyEntity
                        {
                            Name = "Europe",
                            Children =
                                new List<MyEntity>
                                {
                                    new MyEntity {Name = "Germany"},
                                    new MyEntity
                                    {
                                        Name = "Ireland",
                                        Children =
                                            new List<MyEntity>
                                            {
                                                new MyEntity {Name = "Dublin"},
                                                new MyEntity {Name = "Belfast"}
                                            }
                                    }
                                }
                        },
                        new MyEntity
                        {
                            Name = "South America",
                            Children =
                                new List<MyEntity>
                                {
                                    new MyEntity
                                    {
                                        Name = "Brazil",
                                        Children = new List<MyEntity>
                                        {
                                            new MyEntity {Name = "Rio de Janeiro"}
                                        }
                                    },
                                    new MyEntity {Name = "Chile"},
                                    new MyEntity {Name = "Argentina"}
                                }
                        }
                    }
            };

            return root;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Database.SetInitializer<MyContext>(
               new DropCreateDatabaseAlways<MyContext>());
            using (var ctx = new MyContext())
            {
                ctx.Database.Initialize(false);

                ctx.MyEntities.Add(new TestObjectGraph().RootEntity());
                ctx.SaveChanges();
            }

            using (var ctx = new MyContext())
            {
                var parent = ctx.MyEntities
                    .Include(e => e.Children)
                    .FirstOrDefault();

                var deleteme = parent.Children.First();

                ctx.DeleteMyEntity(deleteme);
            }

            Console.WriteLine("Completed....");
            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Sla*_*uma 12

像以下一样删除父母和孩子对我有用.子项在父项之前被删除,它是单个数据库往返(一次调用SaveChanges),当然在单个事务中有三个DELETE语句:

using (var ctx = new MyContext())
{
    var parent = ctx.MyEntities.Include(e => e.Children).FirstOrDefault();

    foreach (var child in parent.Children.ToList())
        ctx.MyEntities.Remove(child);

    ctx.MyEntities.Remove(parent);

    ctx.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

(ToList()这里使用是必要的,因为调用Remove子代也会从父代的Children集合中删除.如果不使用ToList运行时异常,则会抛出foreach循环迭代的集合已被修改.)

Remove为孩子和父母调用的顺序无关紧要.这也有效:

using (var ctx = new MyContext())
{
    var parent = ctx.MyEntities.Include(e => e.Children).FirstOrDefault();

    var children = parent.Children.ToList();

    ctx.MyEntities.Remove(parent);

    foreach (var child in children)
        ctx.MyEntities.Remove(child);

    ctx.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

在两种情况下,EF都以正确的顺序对DELETE语句进行排序.

完整的测试程序(EF 5/.NET 4.5/SQL Server):

using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;

namespace EFSelfReference
{
    public class MyEntity
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public int? ParentId { get; set; }
        public MyEntity Parent { get; set; }

        public ICollection<MyEntity> Children { get; set; }
    }

    public class MyContext : DbContext
    {
        public DbSet<MyEntity> MyEntities { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<MyEntity>()
                .HasOptional(e => e.Parent)
                .WithMany(e => e.Children)
                .HasForeignKey(e => e.ParentId);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Database.SetInitializer<MyContext>(
                new DropCreateDatabaseAlways<MyContext>());
            using (var ctx = new MyContext())
            {
                ctx.Database.Initialize(false);

                var parent = new MyEntity { Name = "Parent",
                    Children = new List<MyEntity>() };

                parent.Children.Add(new MyEntity { Name = "Child 1" });
                parent.Children.Add(new MyEntity { Name = "Child 2" });

                ctx.MyEntities.Add(parent);

                ctx.SaveChanges();
            }

            using (var ctx = new MyContext())
            {
                var parent = ctx.MyEntities.Include(e => e.Children)
                    .FirstOrDefault();

                foreach (var child in parent.Children.ToList())
                    ctx.MyEntities.Remove(child);

                ctx.MyEntities.Remove(parent);

                ctx.SaveChanges();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

using删除实体之前,在DB表中包含当前内容的第一个块之后的屏幕截图:

屏幕1

从最后一个SQL profiler截取SaveChanges:

屏幕2

即,Child 1(Id = 2)和Child 2(Id = 3) Parent(Id = 1)之前被删除.