我遇到的问题包括以下步骤。我犹豫哪种方法是正确的 - 我想保存一个新实体,它是多对多关系的一部分。保存后,我想更新表,其中该实体是形成复合键的外键。
我认为这可以通过两种方式发生 - 通过直接在多对多表中保存记录,或者将其添加到导航属性 - 最前面提到的新保存的实体中的集合。
无论我尝试什么,我都会遇到以下异常:
无法跟踪实体类型“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
]
},
]
没有什么明显的问题,所以你的映射可能有问题,尽管引用的键是汽车 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,或者检查重复项并记录在过滤掉之前传入的问题。