EF Core 5调用SQL Server存储过程报错:已添加相同键EntityID的项
报错原因
- 直接触发报错的核心原因是存储过程返回的结果集中存在大量重名列,第一个重复的就是
EntityID:你的SQL语句同时返回了Items表的[i].[EntityID]和Movements子查询的[t].[EntityID],除此之外CreateByID、CreateOn、Type、IsDeleted、UpdateByID、UpdateOn等字段在两个表中也重名。EF Core做结果映射时会把列名作为Key存入字典匹配实体属性,重复Key会直接抛出该异常。 - 额外的逻辑问题:你用带导航属性(
Brand、Enterprise、List<StockMovement>等集合属性)的ItemSP实体直接接收存储过程返回的扁平左连接结果,FromSqlRaw默认不会自动映射关联导航属性和集合属性,就算解决了重名问题,这些导航属性也都会是Null,无法达到你想要的关联数据填充效果。
解决方法
方案一:使用扁平化DTO接收结果(最适配存储过程返回结构,推荐)
- 首先修改存储过程的SQL语句,给所有重名的列设置别名,避免重复:
SELECT [i].[EntityID], [i].[Alicuota], [i].[AlicuotaType], [i].[BrandID], [i].[Code], [i].[CreateByID] AS ItemCreateByID, [i].[CreateOn] AS ItemCreateOn, [i].[Description], [i].[EnterpriseID], [i].[IsDeleted] AS ItemIsDeleted, [i].[MeasureType], [i].[MinStock], [i].[Name], [i].[Type] AS ItemType, [i].[UpdateByID] AS ItemUpdateByID, [i].[UpdateOn] AS ItemUpdateOn, [t].[EntityID] AS MovementEntityID, -- 移动记录的EntityID改别名 [t].[Balance], [t].[BranchOfficeID], [t].[CreateByID] AS MovementCreateByID, [t].[CreateOn] AS MovementCreateOn, [t].[Date], [t].[EmployeeID], [t].[IsDeleted] AS MovementIsDeleted, [t].[IsEditable], [t].[ItemID], [t].[Notes], [t].[Number], [t].[Quantity], [t].[Type] AS MovementType, [t].[UpdateByID] AS MovementUpdateByID, [t].[UpdateOn] AS MovementUpdateOn, [t].[VoucherID] -- 后面原有SQL逻辑保持不变
- 新建一个扁平化的DTO类,和上面修改后的SQL返回字段一一对应:
// 注意要在DbContext中将该类配置为无键实体 public class ItemMovementDto { public string EntityID { get; set; } public string Name { get; set; } public string Code { get; set; } public string Description { get; set; } public decimal? MinStock { get; set; } public MeasureTypeEnum MeasureType { get; set; } public IVAAlicuotaTypeEnum AlicuotaType { get; set; } public decimal Alicuota { get; set; } public ItemTypeEnum ItemType { get; set; } public string BrandID { get; set; } public string EnterpriseID { get; set; } // 移动记录对应的属性 public string MovementEntityID { get; set; } public decimal? Balance { get; set; } public string BranchOfficeID { get; set; } public DateTime? MovementCreateOn { get; set; } // 剩余所有SQL返回的字段都在此处对应添加属性即可 }
- 直接用该DTO接收存储过程返回结果:
_databaseContext.Set<ItemMovementDto>().FromSqlRaw("SP_NAME {0}", "PARAMETER").ToList();
方案二:保留ItemSP结构,手动填充关联属性
如果你确实需要返回带Movements集合的ItemSP结构,可以拆为两次查询实现:
- 修改存储过程,仅返回Items表的字段,不关联Movements表
- 调用存储过程拿到所有ItemSP的列表,提取所有EntityID作为参数,批量查询对应的StockMovement列表
- 遍历ItemSP列表,把对应移动记录手动赋值到
Movements属性即可。
内容的提问来源于stack exchange,提问作者avechuche
相关产品推荐
相关产品推荐

