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

时间:2013-05-09 14:08:15

标签: c# .net sql-server entity-framework fluent-interface

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

this.HasOptional(t => t.ParentValue)
    .WithMany(t => t.ChildValues)
    .HasForeignKey(t => t.ParentId);

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

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

  1. 首先删除子记录,执行完整保存/提交,然后删除父记录。由于我的模型的复杂性和维护它的逻辑,这不是一个选项 - 不能随时发出多个提交命令。
  2. 在删除任何内容之前解除关系。这似乎是一个更明智的解决方案,但同样,我必须在DELETE之前使用UPDATE语句发出单独的提交。我想避免多次保存/提交调用。
  3. 在删除父记录之前,使用触发器删除子项。但我想尽可能避免触发器及其有问题的性质。
  4. 所以问题是..有没有办法在父记录之前强制删除子项?也许我错过了某种明确的方式告诉EF它需要在父母之前照顾这些孩子?也许有一种方法可以指示EF按ID的降序删除?我不知道......想法?

3 个答案:

答案 0 :(得分:17)

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

例如,请考虑以下结构:

/*  
 *  earth
 *      europe
 *          germany
 *          ireland
 *              belfast
 *              dublin
 *      south america
 *          brazil
 *              rio de janeiro
 *          chile
 *          argentina                 
 *               
 */

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

我提交以下代码作为替代方案(修改了由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);
}

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

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

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

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

然后像这样调用删除:

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

    var deleteme = parent.Children.First();

    ctx.DeleteMyEntity(deleteme);
}

这导致我的数据库现在具有如下结构:

 /*  
 *  earth
 *      south america
 *          brazil
 *              rio de janeiro
 *          chile
 *          argentina                 
 *               
 */

欧洲及其所有儿童被删除。

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

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

ctx.DeleteMyEntity(parent);

或树中您想要的任何节点。

显然,我没有获得赏金,但希望我的帖子可以帮助那些寻找适用于任意深度自我引用实体的解决方案的人。

以下是完整的来源,这是来自所选答案的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();
        }
    }
}

答案 1 :(得分: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();
}

(此处需要使用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();
}

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

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

screen 1

最后SaveChanges之后SQL Profiler的屏幕截图:

screen 2

即。 <{1}}(Id = 2)和Child 1(Id = 3)在 Child 2之前删除(Id = 1)。

答案 2 :(得分:0)

还有另外一种方式,(在做之前考虑退缩......) 您可以将关系设置为ON DELETE CASCADE,并尝试仅删除父行。