首页 > 解决方案 > 实体框架 - 添加具有相关实体的实体时出错

问题描述

我有两个实体:

public class EntityA
{
    public int? Id { get; set; }
    public string Name { get; set; }
    public EntityB { get; set; }
}

public class EntityB
{
    public int? Id { get; set; }
    public string Version { get; set; }
}

EntityB我已经在数据库中有现有记录。我想EntityA参考其中一条EntityB记录添加一个新的。

var entityB = _dbContext.EntityB.FirstOrDefault(e => e.Id == 1);

var entityA = new EntityA { Name = "Test", EntityB = entityB };

_dbContext.Add(entityA);
_dbContext.SaveChanges();

当上面的代码运行时,我收到以下错误:

System.InvalidOperationException:实体类型“EntityB”上的属性“Id”是键的一部分,因此无法修改或标记为已修改。要使用标识外键更改现有实体的主体,首先删除依赖项并调用“SaveChanges”,然后将依赖项与新主体关联。

在我看来,保存也试图添加EntityB,而不仅仅是对它的引用。我确实在数据库和实体框架中指定了关系,例如,当查询EntityA我是否包含EntityB在选择中时,我也得到了引用的实体(因此关系有效)。

modelBuilder.Entity<EntityA>(e =>
{
  e.HasKey(p => p.Id);
  e.HasOne(p => p.EntityB)
    .WithOne()
    .HasForeignKey<EntityB>(p => p.Id);
}

modelBuilder.Entity<EntityB>(e =>
{
  e.HasKey(p => p.Id);
}

如何保存一个新EntityA的,只引用选定的EntityB,而不是保存两个实体?

标签: entity-framework.net-coreentity-framework-coreasp.net-core-3.1

解决方案


这会出错,因为您使用 EntityA 的 PK 作为 Entity B 的 FK,它强制执行 1 对 1 的直接关系。这方面的一个例子是有类似 Order 和 OrderDetails 的东西,其中包含有关特定订单的其他详细信息。两者都将使用“OrderId”作为他们的 PK,而 OrderDetails 使用它的 PK 与它的订单相关联。

相反,如果 EntityB 更像是 OrderType 引用,则不会使用 HasOne / WithOne 关系,因为这将要求 Order #1 仅与 OrderType #1 关联。如果您尝试将 OrderType #2 链接到 Order #1,EF 将尝试替换 OrderType 上的 PK,这是非法的。

通常,EntityA 和 EntityB 之间的关系需要 EntityA 表上的 EntityBId 列作为 FK。这可以是 EntityA 实体中的一个属性,或者保留为影子属性(推荐在 EntityA 将具有 EntityB 导航属性的情况下)使用上面带有 Order 和 OrderType 的示例,Order 记录将具有一个 OrderId (PK) 和一个 OrderTypeId ( FK) 到与其关联的订单类型。

对此的映射将是:(阴影属性)

modelBuilder.Entity<EntityA>(e =>
{
  e.HasKey(p => p.Id);
  e.HasOne(p => p.EntityB)
    .WithMany()
    .HasForeignKey("EntityBId");
}

一个 OrderType 可以分配给许多 Orders,但我们没有 OrderType 上的 Orders 集合。我们使用.HasForeignKey("EntityBId")来在 EntityA 表上设置“EntityBId”的影子属性。或者,如果我们在 EntityA 上声明 EntityBId 属性:

modelBuilder.Entity<EntityA>(e =>
{
  e.HasKey(p => p.Id);
  e.HasOne(p => p.EntityB)
    .WithMany()
    .HasForeignKey(p => p.EntityBId);
}

附带说明一下,导航属性应该被声明virtual。即使您不想依赖延迟加载(推荐),它也有助于确保完全支持用于更改跟踪的 EF 代理,并且延迟加载通常是在运行时比抛出 NullReferenceExceptions 更好的条件。


推荐阅读