Entity Framework - 添加带有相关实体的实体时出错
Entity Framework - error when adding entity with related entity
我有两个实体:
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
的现有记录。我想参考 EntityB
条记录之一添加新的 EntityA
。
var entityB = _dbContext.EntityB.FirstOrDefault(e => e.Id == 1);
var entityA = new EntityA { Name = "Test", EntityB = entityB };
_dbContext.Add(entityA);
_dbContext.SaveChanges();
当上面的代码运行时出现以下错误:
System.InvalidOperationException: The property 'Id' on entity type 'EntityB' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.
在我看来,保存也试图添加 EntityB
,而不仅仅是对它的引用。我确实在数据库和 Entity Framework 中指定了关系,例如在查询 EntityA
时,如果我在 select 中包含 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
,仅引用 selected EntityB
,而不是同时保存两个实体?
这会出错,因为您使用实体 A 的主键作为实体 B 的外键,强制执行一对一的直接关系。这方面的一个例子是有类似 Order 和 OrderDetails 的东西,其中包含有关特定订单的其他详细信息。两者都将使用“OrderId”作为他们的 PK,而 OrderDetails 使用它的 PK 来关联回其订单。
相反,如果 EntityB 更像是一个 OrderType 引用,则您不会使用 HasOne / WithOne 关系,因为这将要求 Order #1 仅与 OrderType #1 相关联。如果您尝试将 OrderType #2 链接到 Order #1,EF 将尝试替换 OrderType 上的 PK,这是非法的。
通常,EntityA 和 EntityB 之间的关系需要 EntityA table 上的 EntityBId 列作为 FK。这可以是 EntityA 实体中的 属性,或者作为影子 属性(推荐在 EntityA 将具有 EntityB 导航 属性 的地方使用)记录将具有与其关联的订单类型的 OrderId (PK) 和 OrderTypeId (FK)。
此映射为:(Shadow 属性)
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id);
e.HasOne(p => p.EntityB)
.WithMany()
.HasForeignKey("EntityBId");
}
可以将一个 OrderType 分配给许多订单,但我们没有关于 OrderType 的订单集合。我们使用 .HasForeignKey("EntityBId")
在我们的 EntityA table 上设置“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 更好的条件。
看起来您正在尝试使用对新 table EntityA
行的可选 1:1 引用来扩展 EntityB
。您希望两条记录具有相同的 Id 值。
This 1:1 link is sometimes referred to as Table Splitting.
Logically in your application the record from EntityB
and EntityA
represent the same business domain object.
如果您只是想创建一个常规 1 : many
关系,那么您应该删除 HasOne().WithOne(),因为这会创建一个 1:1
,您也不会尝试将 FK 变回 Id 属性.
以下建议仅适用于配置1:1关系
you might use Table Splitting for performance reasons (usually middle tier performance) or security reasons. But it also comes up when we need to extend a legacy schema with new metadata and there is code that we cannot control that would have broken if we just added the extra fields to the existing table.
除了 EntityA.Id
不能为 null 之外,您对此的设置基本正确,因为它必须有一个值作为主键。
public class EntityA
{
public int Id { get; set; }
public string Name { get; set; }
public EntityB { get; set; }
}
If you want records to exist in EntityA
that DO NOT have a corresponding record in EntityB
then you need to use another Id column as either the primary key for EntityA
or the foreign key to EntityB
然后您需要通过禁用自动生成的行为来缩小与 EntityA.Id
字段的差距,以便它采用来自 EntityB
:
的 Id
值
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id).ValueGeneratedNever();
e.HasOne(p => p.EntityB)
.WithOne()
.HasForeignKey<EntityB>(p => p.Id);
}
我可能会更进一步,将 Reciprocating 或 Inverse navigation 属性 添加到 EntityB
这将使我们能够使用更多 fluent 样式分配,而不是使用 _dbContext.Add()
将记录添加到数据库:
public class EntityB
{
public int Id { get; set; }
public string Version { get; set; }
public virtual EntityA { get; set; }
}
使用配置:
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id).ValueGeneratedNever();
e.HasOne(p => p.EntityB)
.WithOne(p => p.EntityA)
.HasForeignKey<EntityB>(p => p.Id);
}
这允许您添加更流畅的风格:
var entityB = _dbContext.EntityB.FirstOrDefault(e => e.Id == 1);
entityB.EntityA = new EntityA { Name = "Test" };
_dbContext.SaveChanges();
我有两个实体:
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
的现有记录。我想参考 EntityB
条记录之一添加新的 EntityA
。
var entityB = _dbContext.EntityB.FirstOrDefault(e => e.Id == 1);
var entityA = new EntityA { Name = "Test", EntityB = entityB };
_dbContext.Add(entityA);
_dbContext.SaveChanges();
当上面的代码运行时出现以下错误:
System.InvalidOperationException: The property 'Id' on entity type 'EntityB' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.
在我看来,保存也试图添加 EntityB
,而不仅仅是对它的引用。我确实在数据库和 Entity Framework 中指定了关系,例如在查询 EntityA
时,如果我在 select 中包含 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
,仅引用 selected EntityB
,而不是同时保存两个实体?
这会出错,因为您使用实体 A 的主键作为实体 B 的外键,强制执行一对一的直接关系。这方面的一个例子是有类似 Order 和 OrderDetails 的东西,其中包含有关特定订单的其他详细信息。两者都将使用“OrderId”作为他们的 PK,而 OrderDetails 使用它的 PK 来关联回其订单。
相反,如果 EntityB 更像是一个 OrderType 引用,则您不会使用 HasOne / WithOne 关系,因为这将要求 Order #1 仅与 OrderType #1 相关联。如果您尝试将 OrderType #2 链接到 Order #1,EF 将尝试替换 OrderType 上的 PK,这是非法的。
通常,EntityA 和 EntityB 之间的关系需要 EntityA table 上的 EntityBId 列作为 FK。这可以是 EntityA 实体中的 属性,或者作为影子 属性(推荐在 EntityA 将具有 EntityB 导航 属性 的地方使用)记录将具有与其关联的订单类型的 OrderId (PK) 和 OrderTypeId (FK)。
此映射为:(Shadow 属性)
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id);
e.HasOne(p => p.EntityB)
.WithMany()
.HasForeignKey("EntityBId");
}
可以将一个 OrderType 分配给许多订单,但我们没有关于 OrderType 的订单集合。我们使用 .HasForeignKey("EntityBId")
在我们的 EntityA table 上设置“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 更好的条件。
看起来您正在尝试使用对新 table EntityA
行的可选 1:1 引用来扩展 EntityB
。您希望两条记录具有相同的 Id 值。
This 1:1 link is sometimes referred to as Table Splitting.
Logically in your application the record fromEntityB
andEntityA
represent the same business domain object.
如果您只是想创建一个常规 1 : many
关系,那么您应该删除 HasOne().WithOne(),因为这会创建一个 1:1
,您也不会尝试将 FK 变回 Id 属性.
以下建议仅适用于配置1:1关系
you might use Table Splitting for performance reasons (usually middle tier performance) or security reasons. But it also comes up when we need to extend a legacy schema with new metadata and there is code that we cannot control that would have broken if we just added the extra fields to the existing table.
除了 EntityA.Id
不能为 null 之外,您对此的设置基本正确,因为它必须有一个值作为主键。
public class EntityA
{
public int Id { get; set; }
public string Name { get; set; }
public EntityB { get; set; }
}
If you want records to exist in
EntityA
that DO NOT have a corresponding record inEntityB
then you need to use another Id column as either the primary key forEntityA
or the foreign key toEntityB
然后您需要通过禁用自动生成的行为来缩小与 EntityA.Id
字段的差距,以便它采用来自 EntityB
:
Id
值
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id).ValueGeneratedNever();
e.HasOne(p => p.EntityB)
.WithOne()
.HasForeignKey<EntityB>(p => p.Id);
}
我可能会更进一步,将 Reciprocating 或 Inverse navigation 属性 添加到 EntityB
这将使我们能够使用更多 fluent 样式分配,而不是使用 _dbContext.Add()
将记录添加到数据库:
public class EntityB
{
public int Id { get; set; }
public string Version { get; set; }
public virtual EntityA { get; set; }
}
使用配置:
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id).ValueGeneratedNever();
e.HasOne(p => p.EntityB)
.WithOne(p => p.EntityA)
.HasForeignKey<EntityB>(p => p.Id);
}
这允许您添加更流畅的风格:
var entityB = _dbContext.EntityB.FirstOrDefault(e => e.Id == 1);
entityB.EntityA = new EntityA { Name = "Test" };
_dbContext.SaveChanges();