c# - DELETE 语句与 Entity Framework 的 SAME TABLE REFERENCE 约束冲突

标签 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);

当我尝试删除子项及其父项时,EF 向数据库发出的 DELETE 命令与我预期的顺序不符 - 它首先尝试删除父项记录。

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

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

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

最佳答案

我知道答案是一年前的,但我觉得它不完整。在我看来,自引用表用于表示任意深度。

例如,考虑以下结构:

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

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

我提交以下代码作为替代方案(Slauma 提供的答案的修改,顺便说一句,他做得很好)。

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

关于c# - DELETE 语句与 Entity Framework 的 SAME TABLE REFERENCE 约束冲突,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16463773/

相关文章:

c# - 带 cookie 的重定向 WebRequest 不起作用(Windows 内部版本 15063)

c# - IdentityServer 3 不会启动

c# - 从另一个项目/dll 引用的 Asp.NET 用户控件具有 NULL 属性

.net - AppFabric vs Unity vs Memcached 或任何其他可能的多服务器缓存机制

sql - 使用 IF/ELSE 并插入临时表

mysql - Erlang 和 SQL 注入(inject)攻击

c# - 库的设计时验证

c# - 序列化 HashSet

SQL Server 以用户身份执行过程

c# - 如何为动态添加的按钮创建方法。 ASP.NET C#