首页 > 解决方案 > 如何使用变量定义 EF 6 父子关系?

问题描述

我尝试设置实体框架来定义父子关系和关系的变量。一个项目是由其他项目的比例组成的。如何使用 Entity Framework 6 进行设计。

假设我的父项是一磅蛋糕,我的孩子是鸡蛋、面粉、糖和黄油。对于磅蛋糕,每个孩子的比例是 1。让我们保持简单。

    public class Item
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }

    public class Composition
    {
        public Guid Id { get; set; }

        public Guid ParentId { get; set; } // Id of Parent Item

        public Guid ChildId { get; set; } //  Id of Child Item

        public int Ratio { get; set; } // Number of Child that compose the parent.
    }

我最初的 DbContext 是

    public class TestContext : DbContext
    {
        public DbSet<Item> Items { get; set; }
    }

使用Add-Migration Initial. 系统生成此迁移代码:

        public override void Up()
        {
            CreateTable(
                "dbo.Compositions",
                c => new
                    {
                        Id = c.Guid(nullable: false),
                        ParentId = c.Guid(nullable: false),
                        ChildId = c.Guid(nullable: false),
                        Ratio = c.Int(nullable: false),
                    })
                .PrimaryKey(t => t.Id);

            CreateTable(
                "dbo.Items",
                c => new
                    {
                        Id = c.Guid(nullable: false),
                        Name = c.String(),
                    })
                .PrimaryKey(t => t.Id);

        }

迁移创建了我的 2 个实体,但它们没有关系。所以我不能轻松地从我的项目表导航到我的所有作品。我使用 Linpad 和 LinqPad 不创建导航属性。这很正常,我从来没有对我的 POCO 说 ParentId 和 ChildId 必须是存在于我的 Item 表中的 Id。

要在我的数据库中创建约束并能够导航,我认为我必须添加导航属性并将其链接到外键。我将在这里尝试几种解决方案并发表评论。

解决方案 1 - 只需添加导航属性

    public class Composition
    {
        public Guid Id { get; set; }

        public Guid ParentId { get; set; } // Id of Parent Item

        [ForeignKey(nameof(ParentId))]
        public Item Parent { get; set; }

        public Guid ChildId { get; set; } //  Id of Child Item

        [ForeignKey(nameof(ChildId))]
        public Item Child { get; set; }

        public int Ratio { get; set; } // Number of Child that compose the parent.
    }

在使用 Add-Migration Initial 创建迁移时。系统生成此迁移代码:

        public override void Up()
        {
            CreateTable(
                "dbo.Compositions",
                c => new
                    {
                        Id = c.Guid(nullable: false),
                        ParentId = c.Guid(nullable: false),
                        ChildId = c.Guid(nullable: false),
                        Ratio = c.Int(nullable: false),
                    })
                .PrimaryKey(t => t.Id)
                .ForeignKey("dbo.Items", t => t.ChildId, cascadeDelete: true)
                .ForeignKey("dbo.Items", t => t.ParentId, cascadeDelete: true)
                .Index(t => t.ParentId)
                .Index(t => t.ChildId);

            CreateTable(
                "dbo.Items",
                c => new
                    {
                        Id = c.Guid(nullable: false),
                        Name = c.String(),
                    })
                .PrimaryKey(t => t.Id);

        }

但是为什么 cascadeDelete 为真呢?我不想在每次删除项目时都删除我的所有树。我知道我可以定义 Child 或 ChildId 可为空,而 Parent 或 ParentId 可为空,但我不能接受。在我的作文表中,Parent 和 Child 不能为空。这不是逻辑我创建一个链接,一侧是值,另一侧是 null。我可以删除合成链接,但如果我创建它,那么双方都必须存在。

另外,我不能Udpate-Database这个版本,因为

在表 'Compositions' 上引入 FOREIGN KEY 约束 'FK_dbo.Compositions_dbo.Items_ParentId' 可能会导致循环或多个级联路径。指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。

解决方案 2 - 使用 Fluent API

我采用我的解决方案 1,并尝试在 OnModelCreating() 覆盖中添加缺失的信息以帮助系统

    public class TestContext : DbContext
    {
        public DbSet<Item> Items { get; set; }
        public DbSet<Composition> Compositions { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Composition>()
                .HasRequired(c => c.Parent)
                // And I have no idea how to do here
        }
    }

解决方案 3 - 项目上的合成集合

    public class Item
    {
        public Guid Id { get; set; }

        public string Name { get; set; }

        public ICollection<Composition> Compositions { get; set; }
    }

    public class Composition
    {
        public Guid Id { get; set; }

        public Guid ParentId { get; set; } // Id of Parent Item

        //[ForeignKey(nameof(ParentId))]
        //public Item Parent { get; set; }

        public Guid ChildId { get; set; } //  Id of Child Item

        [ForeignKey(nameof(ChildId))]
        public Item Child { get; set; }

        public int Ratio { get; set; } // Number of Child that compose the parent.
    }

在使用 Add-Migration Initial 创建迁移时。系统生成此迁移代码:

public override void Up()
{
    CreateTable(
        "dbo.Compositions",
        c => new
            {
                Id = c.Guid(nullable: false),
                ParentId = c.Guid(nullable: false),
                ChildId = c.Guid(nullable: false),
                Ratio = c.Int(nullable: false),
            })
        .PrimaryKey(t => t.Id)
        .ForeignKey("dbo.Items", t => t.ChildId, cascadeDelete: true)
        .Index(t => t.ChildId);

    CreateTable(
        "dbo.Items",
        c => new
            {
                Id = c.Guid(nullable: false),
                Name = c.String(),
            })
        .PrimaryKey(t => t.Id);

}

似乎更好,但我仍然有一个级联删除为真。

解决方案 4 - 在 Item 上引入父子组合

public class Item
{
    public Guid Id { get; set; }

    public string Name { get; set; }

    public ICollection<Composition> ChildCompositions { get; set; }

    public ICollection<Composition> ParentCompositions { get; set; }
}

public class Composition
{
    public Guid Id { get; set; }

    public Guid ParentId { get; set; } // Id of Parent Item

    [ForeignKey(nameof(ParentId))]
    public Item Parent { get; set; }

    public Guid ChildId { get; set; } //  Id of Child Item

    [ForeignKey(nameof(ChildId))]
    public Item Child { get; set; }

    public int Ratio { get; set; } // Number of Child that compose the parent.
}

然后是迁移脚本

        public override void Up()
        {
            CreateTable(
                "dbo.Compositions",
                c => new
                    {
                        Id = c.Guid(nullable: false),
                        ParentId = c.Guid(nullable: false),
                        ChildId = c.Guid(nullable: false),
                        Ratio = c.Int(nullable: false),
                        Item_Id = c.Guid(),
                        Item_Id1 = c.Guid(),
                    })
                .PrimaryKey(t => t.Id)
                .ForeignKey("dbo.Items", t => t.Item_Id)
                .ForeignKey("dbo.Items", t => t.Item_Id1)
                .ForeignKey("dbo.Items", t => t.ChildId, cascadeDelete: true)
                .ForeignKey("dbo.Items", t => t.ParentId, cascadeDelete: true)
                .Index(t => t.ParentId)
                .Index(t => t.ChildId)
                .Index(t => t.Item_Id)
                .Index(t => t.Item_Id1);

            CreateTable(
                "dbo.Items",
                c => new
                    {
                        Id = c.Guid(nullable: false),
                        Name = c.String(),
                    })
                .PrimaryKey(t => t.Id);

        }

逻辑。系统无法知道我命名的 ParentComposition 集合必须链接到 ParentId 外键,而 ChildComposition 集合必须链接到 ChildId 外键?所以系统会创建新的外键。

Update-Database得到错误

在表 'Compositions' 上引入 FOREIGN KEY 约束 'FK_dbo.Compositions_dbo.Items_ParentId' 可能会导致循环或多个级联路径。指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。

解决方案 5 - 再次返回 Fluent API

但是我现在可以浏览属性,所以也许我会找到我正在寻找的东西:

public class Item
    {
        public Guid Id { get; set; }

        public string Name { get; set; }

        public ICollection<Composition> ParentCompositions { get; set; }

        public ICollection<Composition> ChildCompositions { get; set; }
    }

    public class Composition
    {
        public Guid Id { get; set; }

        public Guid ParentId { get; set; } // Id of Parent Item

        [ForeignKey(nameof(ParentId))]
        public Item Parent { get; set; }

        public Guid ChildId { get; set; } //  Id of Child Item

        [ForeignKey(nameof(ChildId))]
        public Item Child { get; set; }

        public int Ratio { get; set; } // Number of Child that compose the parent.
    }

    public class TestContext : DbContext
    {
        public DbSet<Item> Items { get; set; }
        public DbSet<Composition> Compositions { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Item>()
                .HasMany(c => c.ChildCompositions)
                .WithRequired(c => c.Parent)
                //.HasForeignKey(c => c.ParentId)
                .WillCascadeOnDelete(false);

            modelBuilder.Entity<Item>()
                .HasMany(c => c.ParentCompositions)
                .WithRequired(c => c.Child)
                //.HasForeignKey(c => c.ChildId)
                .WillCascadeOnDelete(false);
        }
    }

给我迁移脚本:

        public override void Up()
        {
            CreateTable(
                "dbo.Compositions",
                c => new
                    {
                        Id = c.Guid(nullable: false),
                        ParentId = c.Guid(nullable: false),
                        ChildId = c.Guid(nullable: false),
                        Ratio = c.Int(nullable: false),
                    })
                .PrimaryKey(t => t.Id)
                .ForeignKey("dbo.Items", t => t.ParentId)
                .ForeignKey("dbo.Items", t => t.ChildId)
                .Index(t => t.ParentId)
                .Index(t => t.ChildId);

            CreateTable(
                "dbo.Items",
                c => new
                    {
                        Id = c.Guid(nullable: false),
                        Name = c.String(),
                    })
                .PrimaryKey(t => t.Id);

        }

我可以用这个更新我的数据库。但是,奇怪的是,当我测试他在 LinqPad 中的结果时,似乎 ChildCompositions 列表和 ParentCompositions 列表之间存在反转。

在此处输入图像描述

我试图在我的 Fluent API 中更改它但没有成功。

标签: c#entity-framework-6parent-childef-fluent-api

解决方案


一种解决方案可能是删除ForeignKey属性,而是通过模型构建器添加关系:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Composition>().HasOne<Item>(x => x.ChildItem).WithMany().OnDelete(DeleteBehavior.Restrict);
    modelBuilder.Entity<Composition>().HasOne<Item>(x => x.ParentItem).WithMany().OnDelete(DeleteBehavior.Restrict);
}

因此,您可以显式设置删除级联行为。

我尝试通过添加一个Composition实体和两个Items,然后删除该Composition实体。这两个Items 保留在数据库中。我认为这就是它的意图。


推荐阅读