实体与外键交叉引用时的代码优先迁移

时间:2016-09-14 14:17:00

标签: entity-framework ef-code-first entity-framework-6 ef-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时,我收到错误:

  

属性'经理'上的ForeignKeyAttribute在类型' App.Dept'无效。       外键名称' ManagerId'在依赖类型' App.Emp'中找不到。       Name值应该是以逗号分隔的外键属性名称列表。

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

UPD:隐式可选管理器无法解决问题:

modelBuilder.Entity<Emp>().HasRequired(_ => _.Dept).WithOptional(_ => _.Manager);

UPD2:部门: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中工作。

2 个答案:

答案 0 :(得分:1)

从您的班级代码中您可以获得以下内容

对于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,那么您的代码应该是这样的

  1. 从Emp类
  2. 中删除EmpId
  3. 配置应如下所示

    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);
    
  4. 有关这些关系的更多信息,请阅读本文Configure One-to-Zero-or-One Relationship:

    希望这会对你有所帮助

答案 1 :(得分:1)

您主要有三种配置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个表具有独立主键,但只有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)

  

来自MSDN:   配置要求的关系:在关系的另一侧没有导航属性。实体类型是&gt;配置将是关系中的主体。关系所针对的实体类型将是依赖的并包含外部&gt;校长的关键。

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)

  

来自MSDN:   配置要求的关系:在关系的另一侧没有导航属性。   [对我来说,这个解释并不清楚,无论如何,真实的行为见下文]

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])

这应该是1-1的关系,但如果我们看起来更好,则有一个缺失的约束。 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