当实体具有与外键的交叉引用时的代码优先迁移
Code First migration when entities have cross references with foreign keys
我有相互引用的模型:
public class Dept
{
[Key]
public int DeptId { get; set; }
[ForeignKey("ManagerId")]
public Emp Manager { get; set; }
public int? ManagerId { get; set; }
public string DeptName { get; set; }
}
public class Emp
{
[Key]
public int EmpId { get; set; }
[Required]
[ForeignKey("DeptId")]
public Dept Dept { get; set; }
public int DeptId { get; set; }
public string Name { get; set; }
}
当我调用 Add-Migration 时,出现错误:
The ForeignKeyAttribute on property 'Manager' on type 'App.Dept' is not valid.
The foreign key name 'ManagerId' was not found on the dependent type 'App.Emp'.
The Name value should be a comma separated list of foreign key property names.
我应该如何使用这些 table 创建迁移?
更新:隐式可选管理器没有解决问题:
modelBuilder.Entity<Emp>().HasRequired(_ => _.Dept).WithOptional(_ => _.Manager);
UPD2: Dept:Emp 关系是 1:0..1
UPD3: 也许另一个关系会被添加到 Dept 模型中,但它也会是 1:0..1:
[ForeignKey("ManagerId")]
public Emp CTO { get; set; }
public int? CTOId { get; set; }
不是一对多关系:一个部门有零个或一个经理,零个或一个CTO。目前我只有一个关系,但我想将字段命名为 ManagerId,而不是 EmpId。
UPD4:
我的问题开头的架构有两个 primary/foreign 键关系(Dept.DeptId/Emp.DeptId、Emp.EmpId/Dept.ManagerId)在普通 SQL 中工作。我知道使用额外 table 或不使用外键的解决方法,但我需要一个答案,了解如何在上面创建工作模式或为什么它在 EF 中不起作用。
根据您的 class 代码,您有以下内容
对于 1-1 和 1-0..1,相同的主键应该出现在两个表中,而在您的设计中情况并非如此,因为两个表都有自己的主键
现在,根据您输入的代码,配置应该如下
modelBuilder.Entity<Dept>()
.HasKey(t => t.DeptId)
.HasOptional(t => t.Manager)
.WithRequired(t => t.Dept);
但这并不意味着 1-1 或 1-0..1 关系。
如果你想把你的代码转换成1-0..1,那么你的代码应该是这样的
- 从 class Emp
中删除 EmpId
配置应该是这样的
modelBuilder.Entity<Emp>()
.HasKey(t => t.DeptId)
.HasRequired(t => t.Dept);
modelBuilder.Entity<Dept>()
.HasKey(t => t.DeptId)
.HasOptional(t => t.Manager)
.WithRequired(t => t.Dept);
modelBuilder.Entity<Dept>()
.HasOptional(t => t.Manager)
.WithMany()
.HasForeignKey(t => t.ManagerId)
.WillCascadeOnDelete(false);
有关关系的更多信息,您可以阅读这篇文章Configure One-to-Zero-or-One Relationship:
希望对您有所帮助
你主要有三种配置一对一关系的方法(你的错误是第3种解释的情况)。
复杂类型
第一种方法是只有一个 table 并使用复杂类型。选择此配置会影响性能(通常,整体性能优于其他配置,但这取决于记录大小以及您同时拥有这两条记录的次数)。
在您的情况下,您只需用 ComplexType
属性标记其中一个实体
public class Dept
{
[Key]
public int DeptId { get; set; }
public Emp Manager { get; set; }
public string DeptName { get; set; }
}
[ComplexType]
public class Emp
{
public int EmpId { get; set; } // You can still have this property but it will not be a primary key
public string Name { get; set; }
}
使用此模型,这是创建的 table
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [Manager_EmpId] int not null
, [Manager_Name] text null
, [DeptName] text null
);
标准外键
第二种方法是使用标准外键。该模型可以在两个 类 上都有导航属性,有 2 个 table 具有 independent 主键,但只有 1 个 table 具有指向other table (您在问题中写过此配置)。您获得此配置覆盖 OnModelCreating
。
使用这种方式,您可以使用 fluent API 进行多种配置。主要选项是 EF 应该在哪里插入外键。
在每个配置中你都必须有 Map 方法(我在第三种方式中解释了没有 Map 方法会发生什么)
模型总是这个
public class Dept
{
[Key]
public int DeptId { get; set; }
public Emp Manager { get; set; }
public string DeptName { get; set; }
}
public class Emp
{
[Key]
public int EmpId { get; set; }
public Dept Department { get; set; }
public string Name { get; set; }
}
WithRequiredPrincipal (1-1)
From MSDN:
Configures the relationship to be required:required without a navigation property on the other side of the relationship. The entity type being > configured will be the principal in the relationship. The entity type that the relationship targets will be the dependent and contain a foreign > key to the principal.
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredPrincipal(_ => _.Department)
.Map(_ => _.MapKey("DepartmentId"));
这是生成的 DDL
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_c0491d33] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
, [DepartmentId] int not null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_c0491d33] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_DepartmentId] ON [Emps] ([DepartmentId])
ExecuteNonQuery==========
ALTER TABLE [Emps] ADD CONSTRAINT [FK_Emps_Depts_DepartmentId] FOREIGN KEY ([DepartmentId]) REFERENCES [Depts] ([DeptId])
WithRequiredDependent (1-1)
From MSDN:
Configures the relationship to be required:required without a navigation property on the other side of the relationship.
[For me is not clear this explanation, anyway, for the real behaviour see below]
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredDependent(_ => _.Department)
.Map(_ => _.MapKey("EmpId"));
这是生成的 DDL
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
, [EmpId] int not null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_bebceea2] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_bebceea2] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_EmpId] ON [Depts] ([EmpId])
ExecuteNonQuery==========
ALTER TABLE [Depts] ADD CONSTRAINT [FK_Depts_Emps_EmpId] FOREIGN KEY ([EmpId]) REFERENCES [Emps] ([EmpId])
可选 (1-0..1)
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithOptional(_ => _.Department)
.Map(_ => _.MapKey("ManagerId"));
这是生成的 DDL
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
, [ManagerId] int not null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_ee5245bb] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_ee5245bb] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_ManagerId] ON [Depts] ([ManagerId])
ExecuteNonQuery==========
ALTER TABLE [Depts] ADD CONSTRAINT [FK_Depts_Emps_ManagerId] FOREIGN KEY ([ManagerId]) REFERENCES [Emps] ([EmpId])
您可以使用其他方法获取类似的配置。我没有在这里展示每个例子,但我们可以混合这些配置
HasOptional
/WithRequired
HasOptional
/WithOptionalDependent
HasOptional
/WithOptionalPrincipal
EF 默认 0..1-1 1-0..1 1-1 配置
这就是 EF 解释您的配置的方式。在这种情况下,EF 生成 2 tables 与 dependent 主键。在一个 table 上有一个独立的主键(在你的例子中是 identity(1,1)),在另一个 table 上你有一个主键也是外键。这是默认配置。 这是在两个 tables 上都有外键的唯一方法(不是 2 个约束,没有办法有 2 个循环约束,见下文)
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredPrincipal(_ => _.Department);
这是生成的 DDL
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_b91ed7c4] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_b91ed7c4] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_EmpId] ON [Emps] ([EmpId])
ExecuteNonQuery==========
ALTER TABLE [Emps] ADD CONSTRAINT [FK_Emps_Depts_EmpId] FOREIGN KEY ([EmpId]) REFERENCES [Depts] ([DeptId])
这应该是一对一的关系,但如果我们看起来更好,就会缺少一个约束。 Dept table 的主键应该是第二个 table 的外键。为什么 EF 没有插入该约束?因为我们总是会违反约束,所以我们不能在 tables 上插入记录(在事务中也可能违反引用键约束)。
将配置更改为HasRequired
/WithRequiredDependent
我们得到具有独立主键的table将是Emps table
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredDependent(_ => _.Department);
这是生成的 DDL
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null
, [DeptName] text null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_58ab8622] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_58ab8622] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_DeptId] ON [Depts] ([DeptId])
ExecuteNonQuery==========
ALTER TABLE [Depts] ADD CONSTRAINT [FK_Depts_Emps_DeptId] FOREIGN KEY ([DeptId]) REFERENCES [Emps] ([EmpId])
您可以使用其他方法获取类似的配置。我没有在这里展示每个例子,但我们可以混合这些配置
HasOptional
/WithRequired
HasOptional
/WithOptionalDependent
HasOptional
/WithOptionalPrincipal
我有相互引用的模型:
public class Dept
{
[Key]
public int DeptId { get; set; }
[ForeignKey("ManagerId")]
public Emp Manager { get; set; }
public int? ManagerId { get; set; }
public string DeptName { get; set; }
}
public class Emp
{
[Key]
public int EmpId { get; set; }
[Required]
[ForeignKey("DeptId")]
public Dept Dept { get; set; }
public int DeptId { get; set; }
public string Name { get; set; }
}
当我调用 Add-Migration 时,出现错误:
The ForeignKeyAttribute on property 'Manager' on type 'App.Dept' is not valid. The foreign key name 'ManagerId' was not found on the dependent type 'App.Emp'. The Name value should be a comma separated list of foreign key property names.
我应该如何使用这些 table 创建迁移?
更新:隐式可选管理器没有解决问题:
modelBuilder.Entity<Emp>().HasRequired(_ => _.Dept).WithOptional(_ => _.Manager);
UPD2: Dept:Emp 关系是 1:0..1
UPD3: 也许另一个关系会被添加到 Dept 模型中,但它也会是 1:0..1:
[ForeignKey("ManagerId")]
public Emp CTO { get; set; }
public int? CTOId { get; set; }
不是一对多关系:一个部门有零个或一个经理,零个或一个CTO。目前我只有一个关系,但我想将字段命名为 ManagerId,而不是 EmpId。
UPD4: 我的问题开头的架构有两个 primary/foreign 键关系(Dept.DeptId/Emp.DeptId、Emp.EmpId/Dept.ManagerId)在普通 SQL 中工作。我知道使用额外 table 或不使用外键的解决方法,但我需要一个答案,了解如何在上面创建工作模式或为什么它在 EF 中不起作用。
根据您的 class 代码,您有以下内容
对于 1-1 和 1-0..1,相同的主键应该出现在两个表中,而在您的设计中情况并非如此,因为两个表都有自己的主键
现在,根据您输入的代码,配置应该如下
modelBuilder.Entity<Dept>()
.HasKey(t => t.DeptId)
.HasOptional(t => t.Manager)
.WithRequired(t => t.Dept);
但这并不意味着 1-1 或 1-0..1 关系。
如果你想把你的代码转换成1-0..1,那么你的代码应该是这样的
- 从 class Emp 中删除 EmpId
配置应该是这样的
modelBuilder.Entity<Emp>() .HasKey(t => t.DeptId) .HasRequired(t => t.Dept); modelBuilder.Entity<Dept>() .HasKey(t => t.DeptId) .HasOptional(t => t.Manager) .WithRequired(t => t.Dept); modelBuilder.Entity<Dept>() .HasOptional(t => t.Manager) .WithMany() .HasForeignKey(t => t.ManagerId) .WillCascadeOnDelete(false);
有关关系的更多信息,您可以阅读这篇文章Configure One-to-Zero-or-One Relationship:
希望对您有所帮助
你主要有三种配置一对一关系的方法(你的错误是第3种解释的情况)。
复杂类型
第一种方法是只有一个 table 并使用复杂类型。选择此配置会影响性能(通常,整体性能优于其他配置,但这取决于记录大小以及您同时拥有这两条记录的次数)。
在您的情况下,您只需用 ComplexType
属性标记其中一个实体
public class Dept
{
[Key]
public int DeptId { get; set; }
public Emp Manager { get; set; }
public string DeptName { get; set; }
}
[ComplexType]
public class Emp
{
public int EmpId { get; set; } // You can still have this property but it will not be a primary key
public string Name { get; set; }
}
使用此模型,这是创建的 table
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [Manager_EmpId] int not null
, [Manager_Name] text null
, [DeptName] text null
);
标准外键
第二种方法是使用标准外键。该模型可以在两个 类 上都有导航属性,有 2 个 table 具有 independent 主键,但只有 1 个 table 具有指向other table (您在问题中写过此配置)。您获得此配置覆盖 OnModelCreating
。
使用这种方式,您可以使用 fluent API 进行多种配置。主要选项是 EF 应该在哪里插入外键。
在每个配置中你都必须有 Map 方法(我在第三种方式中解释了没有 Map 方法会发生什么)
模型总是这个
public class Dept
{
[Key]
public int DeptId { get; set; }
public Emp Manager { get; set; }
public string DeptName { get; set; }
}
public class Emp
{
[Key]
public int EmpId { get; set; }
public Dept Department { get; set; }
public string Name { get; set; }
}
WithRequiredPrincipal (1-1)
From MSDN: Configures the relationship to be required:required without a navigation property on the other side of the relationship. The entity type being > configured will be the principal in the relationship. The entity type that the relationship targets will be the dependent and contain a foreign > key to the principal.
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredPrincipal(_ => _.Department)
.Map(_ => _.MapKey("DepartmentId"));
这是生成的 DDL
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_c0491d33] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
, [DepartmentId] int not null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_c0491d33] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_DepartmentId] ON [Emps] ([DepartmentId])
ExecuteNonQuery==========
ALTER TABLE [Emps] ADD CONSTRAINT [FK_Emps_Depts_DepartmentId] FOREIGN KEY ([DepartmentId]) REFERENCES [Depts] ([DeptId])
WithRequiredDependent (1-1)
From MSDN: Configures the relationship to be required:required without a navigation property on the other side of the relationship. [For me is not clear this explanation, anyway, for the real behaviour see below]
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredDependent(_ => _.Department)
.Map(_ => _.MapKey("EmpId"));
这是生成的 DDL
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
, [EmpId] int not null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_bebceea2] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_bebceea2] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_EmpId] ON [Depts] ([EmpId])
ExecuteNonQuery==========
ALTER TABLE [Depts] ADD CONSTRAINT [FK_Depts_Emps_EmpId] FOREIGN KEY ([EmpId]) REFERENCES [Emps] ([EmpId])
可选 (1-0..1)
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithOptional(_ => _.Department)
.Map(_ => _.MapKey("ManagerId"));
这是生成的 DDL
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
, [ManagerId] int not null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_ee5245bb] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_ee5245bb] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_ManagerId] ON [Depts] ([ManagerId])
ExecuteNonQuery==========
ALTER TABLE [Depts] ADD CONSTRAINT [FK_Depts_Emps_ManagerId] FOREIGN KEY ([ManagerId]) REFERENCES [Emps] ([EmpId])
您可以使用其他方法获取类似的配置。我没有在这里展示每个例子,但我们可以混合这些配置
HasOptional
/WithRequired
HasOptional
/WithOptionalDependent
HasOptional
/WithOptionalPrincipal
EF 默认 0..1-1 1-0..1 1-1 配置
这就是 EF 解释您的配置的方式。在这种情况下,EF 生成 2 tables 与 dependent 主键。在一个 table 上有一个独立的主键(在你的例子中是 identity(1,1)),在另一个 table 上你有一个主键也是外键。这是默认配置。 这是在两个 tables 上都有外键的唯一方法(不是 2 个约束,没有办法有 2 个循环约束,见下文)
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredPrincipal(_ => _.Department);
这是生成的 DDL
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_b91ed7c4] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_b91ed7c4] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_EmpId] ON [Emps] ([EmpId])
ExecuteNonQuery==========
ALTER TABLE [Emps] ADD CONSTRAINT [FK_Emps_Depts_EmpId] FOREIGN KEY ([EmpId]) REFERENCES [Depts] ([DeptId])
这应该是一对一的关系,但如果我们看起来更好,就会缺少一个约束。 Dept table 的主键应该是第二个 table 的外键。为什么 EF 没有插入该约束?因为我们总是会违反约束,所以我们不能在 tables 上插入记录(在事务中也可能违反引用键约束)。
将配置更改为HasRequired
/WithRequiredDependent
我们得到具有独立主键的table将是Emps table
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredDependent(_ => _.Department);
这是生成的 DDL
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null
, [DeptName] text null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_58ab8622] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_58ab8622] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_DeptId] ON [Depts] ([DeptId])
ExecuteNonQuery==========
ALTER TABLE [Depts] ADD CONSTRAINT [FK_Depts_Emps_DeptId] FOREIGN KEY ([DeptId]) REFERENCES [Emps] ([EmpId])
您可以使用其他方法获取类似的配置。我没有在这里展示每个例子,但我们可以混合这些配置
HasOptional
/WithRequired
HasOptional
/WithOptionalDependent
HasOptional
/WithOptionalPrincipal