EF Core 8中SqlQueryRaw映射多类型列至自定义类的构造器重载问题
EF Core 8 中解决多类型列分组映射构造器重载失效问题
环境
使用 EntityFrameworkCore v8.0.1 开发物品管理应用
需求
统计数据库表中各列的分组信息:显示列的不同取值及对应记录数,例如Manufacturer列返回HP (15)、Dell (13)这类结果。
问题
表中列数量多且数据类型多样,尝试通过自定义模型类构造器重载处理类型转换,再用SqlQueryRaw映射结果时,抛出异常:
System.InvalidCastException: Unable to cast object of type 'System.DateTime' to type 'System.String'
原因是 EF Core 的SqlQueryRaw会直接根据目标类的属性类型匹配数据库返回值,而非自动选择构造器重载。ItemGroup的Name属性为string?,导致所有非字符串类型的返回值被强制转换,触发异常。
现有代码
模型类
public class ItemGroup { public string? Name { get; set; } public int Count { get; set; } public ItemGroup(int name, int count) : this(name.ToString(), count) { } public ItemGroup(DateTime name, int count) : this(name.ToShortDateString(), count) { } public ItemGroup(string name, int count) { Name = name ?? "(Blank)"; Count = count; } }
查询代码
foreach (var result in _context.Database.SqlQueryRaw<ItemGroup>("select " + column.Column + " as 'Name', count(*) as 'Count' from items group by " + column.Column).ToList()) { column.Groups.Add(result); }
预期效果
构造器重载自动生效,完成不同类型到字符串的转换:
Manufacturers: HP (15), Dell (13), ASUS (6)(调用string参数构造器)Manufacture Date: 4/14/2019 (10), 6/18/2020 (9)(调用DateTime参数构造器)Lifespan: 365 (10), 438 (10), 512 (5)(调用int参数构造器)
要求仅在构造器内处理转换,不修改SQL查询逻辑。
解决方案
核心思路
让 EF Core 优先使用构造器实例化对象,而非直接赋值属性,同时统一处理多类型的转换逻辑。
方案1:使用object统一接收值,在构造器内做类型判断
修改模型类,添加私有无参构造器供EF Core使用,同时新增接收object类型的构造器,内部处理不同类型的转换:
public class ItemGroup { public string? Name { get; set; } public int Count { get; set; } // 私有无参构造器,仅EF Core可调用 private ItemGroup() {} public ItemGroup(object name, int count) { if (name == DBNull.Value) { Name = "(Blank)"; } else { Name = name switch { DateTime date => date.ToShortDateString(), int num => num.ToString(), string str => str ?? "(Blank)", _ => name.ToString() ?? "(Blank)" }; } Count = count; } }
然后在DbContext的OnModelCreating中配置构造器映射:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<ItemGroup>() .HasConstructor<object, int>(); }
方案2:为不同列类型动态匹配构造器(适用于固定列类型场景)
如果能提前确定每列的数据类型,可以在查询时根据列类型指定对应的构造器映射。例如:
- 先为
ItemGroup配置所有需要的构造器:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<ItemGroup>() .HasConstructor<int, int>() .HasConstructor<DateTime, int>() .HasConstructor<string, int>(); }
- 查询时根据列类型动态调整(需提前维护列类型映射表):
// 假设column.Type存储了列的CLR类型 var entityType = modelBuilder.Entity<ItemGroup>().Metadata; var constructor = entityType.GetConstructors() .First(c => c.Parameters[0].ParameterType == column.Type); // 这里需要结合EF Core的内部API或动态查询来匹配构造器,不过实际使用中方案1更通用
额外注意:避免SQL注入风险
当前查询直接拼接column.Column到SQL语句中,存在注入风险。如果column.Column来自用户输入,建议改用LINQ动态构建查询:
// 示例:用LINQ动态构建分组查询 var itemType = typeof(Item); var parameter = Expression.Parameter(itemType, "i"); var columnExpr = Expression.Property(parameter, column.Column); var groupByExpr = Expression.Lambda(columnExpr, parameter); var query = _context.Items .GroupBy(groupByExpr) .Select(g => new ItemGroup(g.Key, g.Count())); var results = query.ToList();
内容的提问来源于stack exchange,提问作者CreativeGuy888
相关产品推荐
相关产品推荐

