异常尝试保存更改时 'X' 的值未知
Exception The value of 'X' is unknown when attempting to save changes
有这两个实体:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public CompanyVehicle CompanyVehicle { get; set; }
}
和
public class CompanyVehicle
{
public int Id { get; set; }
public string Name { get; set; }
public Employee Employee { get; set; }
}
在 SQL Server 2019
上使用 Entity Framework Core 5.0.8
,CompanyVehicle 的配置是:
entityBuilder.HasOne(t => t.Employee)
.WithOne(t => t.CompanyVehicle)
.HasForeignKey<Employee>(t => t.Id)
.IsRequired();
我们将尝试插入一些内容:
public void Create(Employee employee)
{
employee.CompanyVehicle = new CompanyVehicle();
dbContext.Add<Employee>(employee);
dbContext.SaveChanges();
}
以上代码在 EF6
中运行良好。 Employee 和 CompanyVehicle 表中的两条新记录是使用相同的 Id
创建的。迁移到EF Core 5.0.8
后,dbContext.SaveChanges()抛出异常:
System.InvalidOperationException: 'The value of 'Employee.Id' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.'
请注意,这些实体只是示例,在我的案例中不应更改数据库设计。
更新
经过更多调查,我发现我的问题是:
将 X
(主体)和 Y
(从属)作为两个表,其中 X.Id
是 X
的 PK,Y.Id
是 Y
的 PK 和还FK到X
,在EF Core中无法插入X
的记录。
Entity Framework Core 通过能够检测外键 属性 配置一对一关系,从而识别关系中哪个是主体,哪个是依赖实体。
先查看现有的数据库,看看依赖的是什么table,假设是Employee
,应该有外键到CompanyVehicle
table . (在你的情况下可能是其他方式。)
1.使用 EF Core 转换。
如果 Employee
是依赖项 table,请将确切的外键 属性 名称(假设它是 Vehicle_Id
)添加到您的 Employee
实体。如果您不想将 属性 添加到 class.
,请按照第二种方法
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int Vehicle_Id { get; set; } // <-- This right here.
public CompanyVehicle CompanyVehicle { get; set; }
}
没有这个属性,正如我前面提到的,一对一关系无法确定child/dependent方。 (检查你在数据库中的内容并添加 属性,否则你将在 Employee
table 中得到两个外键)
并使用 fluent API,像这样配置关系。 (注意 a 和 b 如何用于分隔两个导航属性,在您的实现中您使用了t,对于两者,当您说 .HasForeignKey<Employee>(t => t.Id)
时,您将外键设置为主键 Id
of Employee
table, 这可能是您出错的原因)。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CompanyVehicle>()
.HasOne(a => a.Employee)
.WithOne(b => b.CompanyVehicle)
.HasForeignKey<Employee>(b => b.Vehicle_Id);
}
2。不使用 EF Core 约定。
如果你不喜欢在依赖table中添加一个属性,使用数据库中现有的外键(假设它是Vehicle_Id
),流畅API 配置应如下所示。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CompanyVehicle>()
.HasOne(a => a.Employee)
.WithOne(b => b.CompanyVehicle)
.HasForeignKey<Employee>("Vehicle_Id");
}
编辑:
Has/With
模式用于关闭循环并完全定义关系。在这种情况下,由于要配置的关系是一对一的,因此 HasOne
方法与 WithOne
方法链接在一起。然后通过将依赖实体 (Employee
) 作为类型参数传递给 HasForeignKey
方法来识别依赖实体,该方法采用 lambda 指定依赖类型中的哪个 属性 是外键。
因此,如果您希望 Employee Id
作为 CompanyVehicle
table 的外键,请修改您的 Fluent API,再次注意 a 和 b 当指定 lambdas 时。
modelBuilder.Entity<CompanyVehicle>()
.HasOne(a => a.Employee)
.WithOne(b => b.CompanyVehicle)
.HasForeignKey<Employee>(b => b.Id);
所以我终于找到了问题,配置一个属性既是PK
又是FK
是可以的,而且很容易。在程序集中从 EF6
迁移到 EFCore
后,我们有了旧代码。该项目是一个框架,因此在 OnModelCreating
中,我们在基础 DbContext
中使用 modelBuilder.ApplyConfigurationsFromAssembly
来在来宾项目中注册配置。项目将自动查找项目引用的所有程序集中的所有配置或应用程序路径中的 DLL。
关键点是:在EF Core
explicit fluent FK
配置中与EF6
相比顺序相反。所以在 EF6
中,对于 Employee
我们曾经写成:
this.HasRequired(t => t.CompanyVehicle)
.WithRequiredDependent(t => t.Employee)
.HasForeignKey(d => d.Id);
在EF Core
中我们应该写:
b.HasOne(t => t.CompanyVehicle)
.WithOne(t => t.Employee)
.HasForeignKey<Employee>(t => t.Id).IsRequired();
第一部分使用的参数d
是CompanyVehicle
类型。所以我们的迁移器将旧代码转换为:
b.HasOne(t => t.CompanyVehicle)
.WithOne(t => t.Employee)
.HasForeignKey<CompanyVehicle>(t => t.Id).IsRequired();
这是不正确的。通用参数应该是从属 table 类型。我们后来在一个新的命名空间中解决了这个问题,但是 ApplyConfigurationsFromAssembly
方法在我们的配置之后也继续应用过时的代码。
我在 OnModelCreating
末尾使用了以下代码块来调查该问题:
foreach (var entity in modelBuilder.Model.GetEntityTypes())
foreach(var key in entity.GetForeignKeys())
{
//Check what is in the key...
}
并注意到为我的实体配置了重复的密钥。
有这两个实体:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public CompanyVehicle CompanyVehicle { get; set; }
}
和
public class CompanyVehicle
{
public int Id { get; set; }
public string Name { get; set; }
public Employee Employee { get; set; }
}
在 SQL Server 2019
上使用 Entity Framework Core 5.0.8
,CompanyVehicle 的配置是:
entityBuilder.HasOne(t => t.Employee)
.WithOne(t => t.CompanyVehicle)
.HasForeignKey<Employee>(t => t.Id)
.IsRequired();
我们将尝试插入一些内容:
public void Create(Employee employee)
{
employee.CompanyVehicle = new CompanyVehicle();
dbContext.Add<Employee>(employee);
dbContext.SaveChanges();
}
以上代码在 EF6
中运行良好。 Employee 和 CompanyVehicle 表中的两条新记录是使用相同的 Id
创建的。迁移到EF Core 5.0.8
后,dbContext.SaveChanges()抛出异常:
System.InvalidOperationException: 'The value of 'Employee.Id' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.'
请注意,这些实体只是示例,在我的案例中不应更改数据库设计。
更新
经过更多调查,我发现我的问题是:
将 X
(主体)和 Y
(从属)作为两个表,其中 X.Id
是 X
的 PK,Y.Id
是 Y
的 PK 和还FK到X
,在EF Core中无法插入X
的记录。
Entity Framework Core 通过能够检测外键 属性 配置一对一关系,从而识别关系中哪个是主体,哪个是依赖实体。
先查看现有的数据库,看看依赖的是什么table,假设是Employee
,应该有外键到CompanyVehicle
table . (在你的情况下可能是其他方式。)
1.使用 EF Core 转换。
如果 Employee
是依赖项 table,请将确切的外键 属性 名称(假设它是 Vehicle_Id
)添加到您的 Employee
实体。如果您不想将 属性 添加到 class.
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int Vehicle_Id { get; set; } // <-- This right here.
public CompanyVehicle CompanyVehicle { get; set; }
}
没有这个属性,正如我前面提到的,一对一关系无法确定child/dependent方。 (检查你在数据库中的内容并添加 属性,否则你将在 Employee
table 中得到两个外键)
并使用 fluent API,像这样配置关系。 (注意 a 和 b 如何用于分隔两个导航属性,在您的实现中您使用了t,对于两者,当您说 .HasForeignKey<Employee>(t => t.Id)
时,您将外键设置为主键 Id
of Employee
table, 这可能是您出错的原因)。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CompanyVehicle>()
.HasOne(a => a.Employee)
.WithOne(b => b.CompanyVehicle)
.HasForeignKey<Employee>(b => b.Vehicle_Id);
}
2。不使用 EF Core 约定。
如果你不喜欢在依赖table中添加一个属性,使用数据库中现有的外键(假设它是Vehicle_Id
),流畅API 配置应如下所示。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CompanyVehicle>()
.HasOne(a => a.Employee)
.WithOne(b => b.CompanyVehicle)
.HasForeignKey<Employee>("Vehicle_Id");
}
编辑:
Has/With
模式用于关闭循环并完全定义关系。在这种情况下,由于要配置的关系是一对一的,因此 HasOne
方法与 WithOne
方法链接在一起。然后通过将依赖实体 (Employee
) 作为类型参数传递给 HasForeignKey
方法来识别依赖实体,该方法采用 lambda 指定依赖类型中的哪个 属性 是外键。
因此,如果您希望 Employee Id
作为 CompanyVehicle
table 的外键,请修改您的 Fluent API,再次注意 a 和 b 当指定 lambdas 时。
modelBuilder.Entity<CompanyVehicle>()
.HasOne(a => a.Employee)
.WithOne(b => b.CompanyVehicle)
.HasForeignKey<Employee>(b => b.Id);
所以我终于找到了问题,配置一个属性既是PK
又是FK
是可以的,而且很容易。在程序集中从 EF6
迁移到 EFCore
后,我们有了旧代码。该项目是一个框架,因此在 OnModelCreating
中,我们在基础 DbContext
中使用 modelBuilder.ApplyConfigurationsFromAssembly
来在来宾项目中注册配置。项目将自动查找项目引用的所有程序集中的所有配置或应用程序路径中的 DLL。
关键点是:在EF Core
explicit fluent FK
配置中与EF6
相比顺序相反。所以在 EF6
中,对于 Employee
我们曾经写成:
this.HasRequired(t => t.CompanyVehicle)
.WithRequiredDependent(t => t.Employee)
.HasForeignKey(d => d.Id);
在EF Core
中我们应该写:
b.HasOne(t => t.CompanyVehicle)
.WithOne(t => t.Employee)
.HasForeignKey<Employee>(t => t.Id).IsRequired();
第一部分使用的参数d
是CompanyVehicle
类型。所以我们的迁移器将旧代码转换为:
b.HasOne(t => t.CompanyVehicle)
.WithOne(t => t.Employee)
.HasForeignKey<CompanyVehicle>(t => t.Id).IsRequired();
这是不正确的。通用参数应该是从属 table 类型。我们后来在一个新的命名空间中解决了这个问题,但是 ApplyConfigurationsFromAssembly
方法在我们的配置之后也继续应用过时的代码。
我在 OnModelCreating
末尾使用了以下代码块来调查该问题:
foreach (var entity in modelBuilder.Model.GetEntityTypes())
foreach(var key in entity.GetForeignKeys())
{
//Check what is in the key...
}
并注意到为我的实体配置了重复的密钥。