【问题标题】:Exception The value of 'X' is unknown when attempting to save changes异常尝试保存更改时“X”的值未知
【发布时间】:2021-10-01 02:19:45
【问题描述】:

有这两个实体:

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.IdX 的PK 和Y.IdY 的PK 和FK 到X,在EF Core 中有一条记录的X 无法插入。

【问题讨论】:

  • 出于某种神秘的原因,在我的情况下,当我启动主键自动递增为 0 时,Entity Framework 出现问题。我还有一个既是主键又是外键的字段。这不是我第一次遇到这个问题,我通过从 1 开始索引来解决它。IDENTITY(1,1) 我不知道为什么...
  • @A.Morel 在迁移之前尝试在启动时禁用所有 EF 约定,看看它是否适用于从 0 开始的增量。
  • 我先使用数据库,但感谢您的提示。

标签: sql-server entity-framework-core ef-core-5.0


【解决方案1】:

所以我终于找到了问题,将属性配置为PKFK 是可能的并且非常容易。在程序集中从EF6 迁移到EFCore 后,我们有了旧代码。该项目是一个框架,因此在OnModelCreating 中,我们在基础DbContext 中使用modelBuilder.ApplyConfigurationsFromAssembly 在来宾项目中注册配置。项目将自动查找项目引用的所有程序集或应用程序路径中的 DLL 中的所有配置。
关键点是:在EF Core 显式流利FK 配置与EF6 相比顺序相反。所以在EF6Employee 我们曾经这样写:

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();

第一部分使用的参数dCompanyVehicle类型。所以我们的迁移器将旧代码转换为:

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

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

【讨论】:

    【解决方案2】:

    Entity Framework Core 通过检测外键属性来配置一对一的关系,从而识别关系中哪个是主体,哪个是依赖实体。

    首先查看现有数据库,检查依赖表是什么,假设它是Employee,它应该有一个指向CompanyVehicle 表的外键。 (在你的情况下可能是其他方式。)

    1.使用 EF Core 转换。

    如果Employee 是从属表,请将确切的外键属性名称(假设它是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&lt;Employee&gt;(t =&gt; t.Id) 时,您将外键设置为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 约定。

    如果您不喜欢向依赖表添加属性,请使用数据库中现有的外键(假设它是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 方法来识别依赖实体 (Employee),该方法接受一个 lambda,指定依赖类型中的哪个属性是外键。

    因此,如果您希望 Employee Id 充当 CompanyVehicle 表的外键,请修改您的 Fluent API,再次注意 ab指定 lambda 时。

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

    【讨论】:

    • 不应更改数据库设计,所以我选择您的第二个选项,但 db 或实体中没有 Vehicle_Id 列。就像我说的那样,这种确切的方法是使用 EF6 实现的,并且按预期工作。假设Employee是从属表,Employee表中有一个FK_dbo.Employee_dbo.Vehicle_Id键(不是列)。其实我需要Employee.Id既是PK又是FK。
    • @Bamdad 在这种情况下,将最后一行更改为.HasForeignKey&lt;Employee&gt;(b =&gt; b.Id);,确保使用a,b 如上所述的不同参数。也编辑了答案。
    • 这是错误信息。使用ab 或仅使用ts 之间没有区别,因为它们处于不同的上下文中。 (虽然我被误导了,甚至尝试过,但 surly 没用。)我认为我们应该调查插入时发生的异常。
    猜你喜欢
    • 2021-09-10
    • 2016-08-11
    • 1970-01-01
    • 2016-07-17
    • 1970-01-01
    • 2013-02-18
    • 2014-08-04
    • 2021-09-07
    • 1970-01-01
    相关资源
    最近更新 更多