SQL Server 尝试保存更改时,“X”的值未知

bprjcwpo  于 2023-01-29  发布在  其他
关注(0)|答案(4)|浏览(109)

有以下两个实体:

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()抛出异常:
系统操作无效异常:尝试保存更改时,"Employee.Id"的值未知。这是因为该属性也是外键的一部分,而该外键在关系中的主体实体未知。
请注意,这些实体只是示例,在我的示例中不应更改数据库设计。

    • 更新**

经过一番调查,我发现我的问题是:
X(主要)和Y(从属)作为两个表,其中X.IdX的PK,Y.IdY的PK,并且FK至X,在EF Core中无法插入X的记录。

zvokhttg

zvokhttg1#

所以我终于找到了问题所在,配置一个属性为PKFK是可能的,而且非常容易。我们在程序集中从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();

第一部分中使用的参数d的类型为CompanyVehicle,因此我们的迁移程序将旧代码转换为:

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...
    }

并注意到为我的实体配置了重复的密钥。

nkkqxpd9

nkkqxpd92#

实体框架核心通过能够检测外键属性来配置一对一关系,从而识别关系中的主体和从属实体。
首先查看现有的数据库并检查依赖表是什么,假设它是Employee,它应该有一个指向CompanyVehicle表的foriegn键(在您的情况下可能是相反的)。

    • 1.使用EF Core转换。**

如果Employee是从属表,则将该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,像这样配置关系。(注意如何使用ab分隔两个导航属性,在您的实现中,您使用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核心公约。**

如果你不想在依赖表中添加一个属性,使用数据库中现有的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方法来标识的,HasForeignKey方法接受一个lambda,该lambda指定依赖类型中的哪个属性是外键。
因此,如果您希望Employee Id充当CompanyVehicle表的foriegn键,请将Fluent API修改为:在指定lambda时再次注意ab

modelBuilder.Entity<CompanyVehicle>()
        .HasOne(a => a.Employee)
        .WithOne(b => b.CompanyVehicle)
        .HasForeignKey<Employee>(b => b.Id);
elcex8rz

elcex8rz3#

我和A·莫雷尔有同样的问题。
当手动插入ManyToMany的自定义连接表,并且外键为0时,我得到了这个错误。
通过将父表的种子值更改为从2:

DBCC CHECKIDENT ('program_contact', RESEED, 1);

因为这个问题
DBCC CHECKIDENT Sets Identity to 0

jfgube3f

jfgube3f4#

对我来说,这似乎是由于创建了“空白”实体,我没有添加到上下文中。在EF6中,这些实体被忽略,因为它们没有添加,但在EF Core中,它们似乎是自动添加的。
我纠正了这个问题,将“可写”上下文的范围缩小到只有进行更改的那一行,并对其他所有内容使用单独的“只读”上下文。
我可以通过不在视图中直接使用实体类型来进一步纠正这一点,这样我就可以创建不是实体的空白条目。

相关问题