【发布时间】:2020-03-19 10:28:10
【问题描述】:
我尝试设置实体框架来定义父子关系和关系的变量。一个项目是由其他项目的比例组成的。如何使用 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 为 true?我不想在每次删除项目时都删除我的所有树。我知道我可以定义 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 - Item 上的合成集合
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 中更改它但没有成功。
【问题讨论】:
-
您能从迁移脚本中发布一个 sn-p 吗? - 我想你可以手动适应。删除级联不受数据注解控制,仅受流式 API 调用
OnDelete()控制,可在其中设置行为。 -
@lzydrmr 我用我所有的测试重新写了我的问题
标签: c# entity-framework-6 parent-child ef-fluent-api