多个添加的实体可能在数据库种子上具有相同的主键

Posted

技术标签:

【中文标题】多个添加的实体可能在数据库种子上具有相同的主键【英文标题】:multiple added entities may have the same primary key on database seed 【发布时间】:2015-01-03 05:21:11 【问题描述】:

假设我有以下使用实体框架 6 的 asp.net mvc 5 应用程序的模型结构

class Athlete 
 int AthleteID get; set;
 List<YearsAsAthlete> YearsAsAthlete get;set;


class YearsAsAthlete 
 int YearsAsAthleteID get;set;
 int AthleteID get;set;
 [ForeignKey("AthleteID")]
 Athlete Athlete get;set;
 List<ContractRevenue> ContractRevenue get;set;
 List<AdvertisementRevenue> AdvertisementRevenue get;set;


class ContractRevenue 
 int ContractRevenueID get;set;
 int YearsAsAthleteID get;set;
 [ForeignKey("YearsAsAthleteID")]
 YearsAsAthlete YearsAsAthlete get;set;

 List<RevenueAmounts> RevenueAmounts get;set;


class AdvertisementRevenue get;set;
 int AdvertisementRevenueID get;set;
 int YearsAsAthleteID get;set;
 [ForeignKey("YearsAsAthleteID")]
 YearsAsAthlete YearsAsAthlete get;set;

 List<RevenueAmounts> RevenueAmounts get;set;


class RevenueAmounts 
 int RevenueAmountsID get;set;
 int AmountPaid get;set;
 date DateOfPayment get;set;

当我拥有这样的模型时,这些模型工作得很好,他们有关系,而且一切都像热软糖圣代一样美味。当我运行它时,数据库会创建这些表,并且 RevenueAmounts 表会为 ContracRevenue 和 AdvertisementRevenue 获得 2 个自动生成的外键列。

但是,我不想要这些,因为它们的名字很奇怪 (ContractRevenue_ContractRevenueID),我需要一些方法来访问我的后控制器方法中的foreginkey id 属性,以添加与正确类型的收入相关的新值。

当我将 RevenueAmounts 模型更改为以下内容时:

class RevenueAmounts 
 int RevenueAmountsID get;set;
 int AmountPaid get;set;
 date DateOfPayment get;set;

 // ***NOTE adding foreign keys here

 int ContractRevenueID get;set;
 [ForeginKey("ContractRevenueID")]
 ContractRevenue ContractRevenue get;set;

 int AdvertisementRevenueID get;set;
 [ForeignKey("AdvertisementRevenueID")]
 AdvertisementRevenue AdvertisementRevenue get;set;

我开始遇到异常:

[SqlException (0x80131904):在表“AdvertisementRevenue”上引入 FOREIGN KEY 约束“FK_dbo.AdvertisementRevenue_dbo.YearsAsAthlete_YearsAsAthleteID”可能会导致循环或多个级联路径。指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。

** 编辑 **

我已经使用 fluent API 关闭了级联删除功能,但是现在我遇到了一个不同的异常:

无法确定“GIP.DAL.ContractRevenue_RevenueAmounts”关系的本金端。多个添加的实体可能具有相同的主键。

顺便说一句,当我尝试将一堆信息植入数据库然后在底部执行context.SaveChanges()(最后只执行一次)时抛出异常

【问题讨论】:

Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths的可能重复 【参考方案1】:

在您的编辑中,您提到“多个添加的实体可能具有相同的主键”。错误。在不了解您在此处所做操作的所有细节的情况下,听起来您正在与一个实体建立关系——其中两个在上下文中具有相同的 ID。这些可能是尚未保存的新实体,它们从数据库中获取自动生成的 ID。如果关系基于 ID,则存在一些歧义,因为 Entity Framework 无法确定关系实际指向哪个新实体 - 它们都具有关系指向的 ID。

有两个潜在的修复方法。

    为在上下文中创建的实体生成一个临时的唯一标识符。实体框架将在保存实体时丢弃它,但在此之前,它可以使用它来区分一个新实体与另一个实体。我过去曾为此使用负整数。

    不要使用 ID 而是在实体引用上创建关系。如果Entity Framework直接引用了实体,那么它就不需要经过基于非唯一标识符识别实体的过程,应该不会有这个问题。

【讨论】:

这实际上是数据库的初始种子方法,我添加了一堆“东西”,然后在最后一直执行 context.saveChanges。你能详细说明你提到的第一个修复吗?我怎样才能拥有“临时”唯一标识符?那会是实体类中的属性吗? 临时标识符应分配给指定为实体键的属性。寻找一个用 KeyAttribute 装饰的。 msdn.microsoft.com/en-us/library/… 我很抱歉给你带来了痛苦,但我的理解是,我创建了一个新属性 (int UniqueIDget;set;) 并用 [keyattribute] 装饰它?但是,如果我们正在查看我的实体类,这是在 Contract/advertisementRevenue 类上还是在 RevenueAmounts 类上? 哦。抱歉,我看到了 [Key] 属性.. 好吧,所以我只是把它放在我的主键之上,但它会为我的主键创建 2 个值,一个作为主键,另一个作为唯一 ID?跨度> 第一种方法对我很有效。我的 EF 类“学生”有一个 Guid 作为 ID(实体键),由数据库在 context.SaveChanges() 上分配。使用代码中的 ID = Guid.NewGuid() 设置它解决了这个问题。这个 ID 不会被使用,但它会解决这个问题。我喜欢这个解决方案。【参考方案2】:

这是因为 Entity Framework 在启用级联删除时无法确定关系中的哪个对象是父对象。

您是否使用代码优先?生成迁移后,您将在表声明中看到级联删除选项。如果将其设置为 false,则应解决此问题。

但是更大的问题是您正在创建的对象关系没有实现聚合根,而聚合根通常可以避免这个问题。

【讨论】:

您能否详细说明如何使我的模型实现聚合根?在 Visual Studio 服务器资源管理器中,我在哪里可以看到级联删除选项? 让我试着为你找到一些链接。生成迁移时,可以在表级别打开和关闭级联删除。您也可以在 Fluent API 中进行配置 感谢您的跟进,我发现了如何将其关闭,但现在我遇到了一个不同的异常:无法确定“GIP.DAL.ContractRevenue_RevenueAmounts”关系的主体端。多个添加的实体可能具有相同的主键。 您需要删除 YearsAsAthlete 中的运动员声明或将其设为虚拟。如果您声明与关系双方的对象的关系,EF 会抱怨,因为它无法确定父对象。 我明白了,我正在研究这个,我测试后会更新【参考方案3】:

我遇到了同样的问题,但就我而言,还有另一种解决方案。 在一个循环中,我正在添加父母 对于每个父母,我都在添加孩子。父母和孩子之间是有关系的。当然我用了FK作为虚拟参考列,但问题还是出现了。

foreach (var parent in parents)

   var id = parent.Id;
   var parentEntity = new Parent();
   this.Context.Set<Parent>().Add(parentEntity);
   parentEntity.CreatedOn = DateTime.UtcNow;
   ...

   foreach (var child in parents[id].Children)
   
       var childEntity = new Child();
       //this.Context.Set<Child>().Add(childEntity);
       parent.Children.Add(childEntity);
       childEntity.CreatedOn = DateTime.UtcNow;
       ...
   

我不得不注释掉将子实体添加到 EF 上下文的行,它解决了问题。

【讨论】:

以上是关于多个添加的实体可能在数据库种子上具有相同的主键的主要内容,如果未能解决你的问题,请参考以下文章

附加类型为“”的实体失败,因为另一个实体具有相同的主键值

附加类型实体失败,因为相同类型的另一个实体已经具有相同的主键值。

使用在多个列上具有主键的实体框架更新数据库

ASP.NET MVC - 附加类型为“MODELNAME”的实体失败,因为同一类型的另一个实体已经具有相同的主键值

附加类型的实体失败,因为相同类型的其他实体已具有相同的主键值。在使用 "Attach" 方法或者将实体的状态设置为 "Unchanged" 或 "Mo

EF报错 附加类型model失败