在实体框架中保存新实体后尝试插入多对多时出错

问题描述 投票:0回答:1

我遇到的问题包括以下步骤。我犹豫哪种方法是正确的 - 我想保存一个新实体,它是多对多关系的一部分。保存后,我想更新表,其中该实体是形成复合键的外键。

我认为这可以通过两种方式发生 - 通过直接在多对多表中保存记录,或者将其添加到导航属性 - 最前面提到的新保存的实体中的集合。

无论我尝试什么,我都会遇到以下异常:

无法跟踪实体类型“PartCar”的实例,因为已跟踪具有相同键值 {'CarId', 'PartId'} 的另一个实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。

我希望插入的汽车数量等于 dto 数组长度,并且我希望使用汽车的 id 和 partsId 数组的每个 id 来更新联结表。显然我做错了什么,但我不知道是什么。

public static string ImportCars(CarDealerContext context, string inputJson)
{
     CarDto[] dtos = JsonConvert.DeserializeObject<CarDto[]>(inputJson);
     var parts = context.Parts.AsNoTracking().ToList();

     foreach(var  carDto in dtos) 
     {
         var newCar = new Car { Make = carDto.Make, Model = carDto.Model, TravelledDistance = carDto.TravelledDistance };

         context.Cars.Add(newCar);
         context.SaveChanges();

         foreach(var partId in carDto.PartCars)
         {
             if (parts.Any(p => p.Id == partId))
             {
                 var part = parts.FirstOrDefault(p => p.Id == partId);

                 newCar.PartCars.Add(new PartCar
                 {
                     PartId = partId,
                     CarId = newCar.Id
                 });
             }
             
             context.SaveChanges();
         }
     }

     return $"Successfully imported {dtos.Length}";
}
[
  {
    "make": "Opel",
    "model": "Omega",
    "travelledDistance": 176664996,
    "partsId": [
      38,
      102,
      23,
      116,
      46,
      68,
      88,
      104,
      71,
      32,
      114
    ]
  },
]
entity-framework many-to-many dbcontext
1个回答
0
投票

没有什么明显的问题,所以你的映射可能有问题,尽管引用的键是汽车 ID 和零件 ID 的组合,所以我要检查的最明显的事情是汽车 ID 在之后被设置为新值保存更改()。 (例如,被识别为身份列)

但是,我要指出,EF 旨在处理对象图并处理实体之间的关系。如果关系设置正确,您无需明确保存汽车然后添加其零件。理想情况下,在使用 DbContext 的操作中,您应该只调用

SaveChanges()
一次。

public static string ImportCars(CarDealerContext context, string inputJson)
{
     CarDto[] dtos = JsonConvert.DeserializeObject<CarDto[]>(inputJson);
     var parts = context.Parts.ToList();

    foreach(var  carDto in dtos) 
    {
        var newCar = new Car 
        { 
            Make = carDto.Make, 
            Model = carDto.Model, 
            TravelledDistance = carDto.TravelledDistance 
        };

        foreach(var partId in carDto.PartCars)
        {
            var part = parts.FirstOrDefault(p => p.Id == partId);
            if(part == null)
                continue;
            newCar.PartCars.Add(new PartCar
            {
                Part = part,
            });
        }
    }

    context.SaveChanges();
    return $"Successfully imported {dtos.Length}";
}

注意,在加载零件时,我们让 EF 跟踪实例,然后在创建 PartCar 时,我们只需要设置 Part 导航属性并将其添加到 Car 的 PartCars 集合中。 EF 将处理其余部分,包括填充 Car FK 和 Part FK 以构建复合密钥。根据关系,EF 将自动填充 FK,并确保相关实体按正确的顺序插入。通常建议这样做,而不是直接使用 FK,因为在处理多个相似的引用时,很容易意外地在 FK 中使用不正确的值,这可能会导致错误,或者更糟糕的是,导致不正确的记录分配,从而导致静默。 (匹配的ID恰好存在于不正确的表中,导致保存了错误的关系)

在处理零件引用时,如果整个零件表非常小,请执行以下操作:

var parts = context.Parts.ToList();

... 没问题,但如果您的目录中有数千个零件,并且您要插入几辆汽车,每辆汽车可能有十几个通用零件,那么加载整个零件目录的成本可能会很高。在这种情况下,您可以简化它:

var partIds = dtos.SelectMany(x => x.PartCars).Distinct.ToList();
var parts = context.Parts.Where(x => partIds.Contains(x.Id)).ToList();

这将加载您传入的汽车 DTO 中引用的所有部件。因此,如果您的汽车仅使用 3000 个零件目录中的 20 个零件,则加载的零件实体将只是这 20 个,而不是全部 3000 个。

如果在进行这些更改后,您仍然在 SaveChanges 上遇到有关 PartCar 中唯一约束违规的错误,那么您可能会在传入的 Car DTO 中遇到问题,其中集合中存在重复的零件 ID。这可能是您想要在这样的方法中防范的事情,您可以使用 Distinct 过滤掉重复的部件 ID,或者检查重复项并记录在过滤掉之前传入的问题。

© www.soinside.com 2019 - 2024. All rights reserved.