entity-framework - 当实体具有与外键的交叉引用时的代码优先迁移

标签 entity-framework ef-code-first entity-framework-6 entity-framework-migrations

我有相互引用的模型:

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.

我应该如何使用这些表创建迁移?

更新:隐式可选管理器没有解决问题:

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: 我的问题开头的架构与两个主/外键关系(Dept.DeptId/Emp.DeptId、Emp.EmpId/Dept.ManagerId)在普通 SQL 中工作。我知道使用附加表或不使用外键的解决方法,但我需要一个答案,说明如何在上面创建工作模式或为什么它在 EF 中不起作用。

最佳答案

您主要有三种配置 1-1 关系的方法(您的错误是第 3 种解释的情况)。

复杂类型
第一种方法是只有一个表并使用复杂类型。选择此配置会影响性能(通常,整体性能优于其他配置,但这取决于记录大小以及同时拥有这两条记录的次数)。

在您的情况下,您只需要用 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; }
}

对于这个模型,这是创建的表

CREATE TABLE [Depts] (
 [DeptId] int not null identity(1,1)
, [Manager_EmpId] int not null
, [Manager_Name] text null
, [DeptName] text null
);

标准外键
第二种方法是使用标准外键。该模型可以在两个类上都具有导航属性,有 2 个带有 independent 主键的表,但只有 1 个表具有另一个表的外键(您在问题中写过此配置)。您获得此配置覆盖 OnModelCreating。 使用这种方式,您可以使用流畅的 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])

WithOptional (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 个具有相关 主键的表。在一张表上有一个独立的主键(在您的情况下为 identity(1,1)),在另一张表上您有一个主键,它也是外键。这是默认配置。 这是在两个表上都有外键的唯一方法(不是 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 表的主键应该是第二个表的外键。为什么 EF 没有插入该约束?因为我们总是会违反约束,所以我们不能在表中插入记录(在事务中也可能违反引用键约束)。

修改配置为HasRequired/WithRequiredDependent我们得到的主键独立的表就是Emps表

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

关于entity-framework - 当实体具有与外键的交叉引用时的代码优先迁移,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39492757/

相关文章:

c# - Entity Framework : Mocking with JustMock

c# - 在或包含查询使用 System.Linq.Dynamic

c# - Entity Framework 灵活缓存

c# - 如何创建动态 Entity Framework 过滤表达式,如 Expression<Func<T, bool>>

c# - 如何插入带有 1 :n relationship in Azure App Service 的实体

c# - 在关系数据库中保持实体顺序的最佳模式是什么?

c# - Entity Framework 4.1 - EFTracingProvider

c# - Entity Framework - 在 Seed() 方法中实现一对多外键关系

entity-framework - 当我只想要一个返回时,EntityFramework 4 返回所有列

entity-framework - 如何解决使用 TPH 和复杂类型的初始非常慢的 EF 实体调用?