有这两个实体:
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:"尝试保存更改时,"Employee.Id"的值未知。这是因为该属性也是外键的一部分,关系中的主体实体未知。">
请注意,这些实体只是示例,在我的情况下不应更改数据库设计。
更新
经过进一步的调查,我发现我的问题是:
将X
(主体)和Y
(依赖)作为两个表,其中X.Id
是X
的PK,Y.Id
是Y
的PK,还有FK是X
的PK,在EF Core中不能插入X
的记录。
所以我终于发现了问题,将属性配置为PK
和FK
是可能的,而且非常容易。在程序集中从EF6
迁移到EFCore
后,我们有了旧代码。该项目是一个框架,因此在OnModelCreating
中,我们在基础DbContext
中使用modelBuilder.ApplyConfigurationsFromAssembly
来在来宾项目中注册配置。项目将在应用程序路径中自动查找项目或DLL引用的所有程序集中的所有配置
关键是:在EF Core
中,显式流畅的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();
在第一部分中使用的参数CCD_ 27是类型CCD_。因此,我们的迁移程序将旧代码转换为:
b.HasOne(t => t.CompanyVehicle)
.WithOne(t => t.Employee)
.HasForeignKey<CompanyVehicle>(t => t.Id).IsRequired();
这是不正确的。泛型参数应为依赖表类型。后来我们在一个新的名称空间中修复了这个问题,但ApplyConfigurationsFromAssembly
方法在配置后也一直应用过时的代码
我在OnModelCreating
末尾使用了以下代码块来调查该问题:
foreach (var entity in modelBuilder.Model.GetEntityTypes())
foreach(var key in entity.GetForeignKeys())
{
//Check what is in the key...
}
注意到为我的实体配置了重复的密钥。
Entity Framework Core通过能够检测外键属性来配置一对一关系,从而识别关系中的主体和从属实体。
首先查看现有数据库并检查依赖表是什么,假设它是Employee
,它应该有一个指向CompanyVehicle
表的foriegn键。(在你的情况下可能是另一种情况。)
1.使用EF Core对流
如果Employee
是depentant表,那么将确切的foriegn键属性名称(假设它是Vehicle_Id
)添加到Employee
实体中。如果您不想向类中添加属性,请使用第二个方法。
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; }
}
正如我前面提到的,如果没有这个属性,就无法确定一对一关系的子女/受抚养方。(在数据库中检查您的属性并添加该属性,否则您将在Employee
表中获得两个外键)
并使用fluent API对关系进行如下配置。(请注意如何使用a和b来分隔两个导航属性,在您的实现中,您对这两个属性都使用了t,当您说.HasForeignKey<Employee>(t => t.Id)
时,您正在将foriegn键设置为Employee
表的主键Id
,这可能是错误的原因)。
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约定
如果您不想向依赖表添加属性,请使用数据库中现有的foriegn键(假设它是Vehicle_Id
),fluent 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
表的foriegn键,请将Fluent API修改为这样,再次注意指定lambdas时的a和b。
modelBuilder.Entity<CompanyVehicle>()
.HasOne(a => a.Employee)
.WithOne(b => b.CompanyVehicle)
.HasForeignKey<Employee>(b => b.Id);
我遇到了与A.Morel相同的问题。
当手动插入ManyToMany的自定义联接表时,外键为0,我收到了这个错误。
通过更改父表的种子值以2:开始修复
DBCC CHECKIDENT ('program_contact', RESEED, 1);
因为这个问题
DBCC CHECKIDENT将标识设置为0
对我来说,这似乎是由创建;空白";我没有添加到上下文中的实体。在EF6中,这些被忽略了,因为它们没有被添加,但在EF Core中,它们似乎是自动添加的。
我通过缩小我的";可写";上下文一直到进行更改的单行,并使用单独的";只读";其他一切的上下文。
我可以通过不直接在视图中使用实体类型来进一步更正这一点,这样我就可以制作非实体的空白条目。