EF6 Core中如何将IEnumerable<T>动态投影到动态对象?
动态生成Linq Select子句的解决方案
问题分析
你遇到的问题主要源于三个核心错误:
- Linq方法误用:
SelectMany是用来扁平化嵌套集合的,而投影(选择指定列)需要用Select方法。 - 字符串无法被Linq解析:
"new({formattedColumns})"是普通字符串,Linq(尤其是Linq to Entities/SQL)需要的是表达式树而非字面量,所以这段代码不会产生任何投影效果,自然返回所有列。 - 泛型约束缺失:硬编码
new { c.Id, c.Name }时,编译器不知道泛型参数T包含Id、Name属性,因此报错。
解决方案
下面提供两种可行方案,按需选择:
方案一:使用第三方库(简单高效,推荐)
借助System.Linq.Dynamic.Core库,它支持用字符串语法构建动态Linq查询,内部会自动将字符串转换为表达式树。
- 安装NuGet包:
Install-Package System.Linq.Dynamic.Core
- 修改你的方法:
using System.Linq.Dynamic.Core; public static IQueryable<dynamic> AddSelectClause<T>(SearchDto criteria, IQueryable<T> queryable, ControllerStateManager stateManager) { if (criteria?.SelectColumns != null && criteria.SelectColumns.Any()) { var columnString = string.Join(", ", criteria.SelectColumns); // 动态生成Select子句,返回动态类型 return queryable.Select($"new({columnString})"); } // 未指定列时返回空结构的动态类型 return queryable.Select("new()"); }
使用示例:
var result = AddSelectClause(criteria, query, stateManager); foreach (dynamic item in result) { Console.WriteLine(item.Id); // 直接访问选择的列属性 }
方案二:手动构建表达式树(无第三方依赖)
如果不想引入外部库,可以手动构建表达式树实现动态投影,返回ExpandoObject(动态对象):
using System.Linq.Expressions; using System.Reflection; using System.Dynamic; public static IQueryable<ExpandoObject> AddSelectClause<T>(SearchDto criteria, IQueryable<T> queryable, ControllerStateManager stateManager) { if (criteria?.SelectColumns == null || !criteria.SelectColumns.Any()) { return queryable.Select(_ => new ExpandoObject()); } var entityType = typeof(T); // 过滤出实体中存在的有效属性 var validProperties = criteria.SelectColumns .Select(col => entityType.GetProperty(col, BindingFlags.Public | BindingFlags.Instance)) .Where(prop => prop != null) .ToList(); if (!validProperties.Any()) { return queryable.Select(_ => new ExpandoObject()); } // 构建参数表达式:x => ... var parameter = Expression.Parameter(entityType, "x"); // 创建ExpandoObject变量并初始化 var expandoVar = Expression.Variable(typeof(ExpandoObject), "expando"); var assignExpando = Expression.Assign(expandoVar, Expression.New(typeof(ExpandoObject))); // 获取IDictionary<string, object>的Add方法 var addMethod = typeof(IDictionary<string, object>).GetMethod("Add")!; // 构建每个属性的Add调用逻辑 var addExpressions = validProperties.Select(prop => { var propertyValue = Expression.MakeMemberAccess(parameter, prop); var boxedValue = Expression.Convert(propertyValue, typeof(object)); return Expression.Call( Expression.Convert(expandoVar, typeof(IDictionary<string, object>)), addMethod, Expression.Constant(prop.Name), boxedValue ); }); // 组合所有表达式:初始化ExpandoObject → 添加属性 → 返回对象 var block = Expression.Block( new[] { expandoVar }, assignExpando, Expression.Block(addExpressions), expandoVar ); // 构建Lambda表达式并应用Select var selector = Expression.Lambda<Func<T, ExpandoObject>>(block, parameter); return queryable.Select(selector); }
使用方式和方案一一致,通过动态类型直接访问属性即可。
内容的提问来源于stack exchange,提问作者Ronald Herhuth
相关产品推荐
相关产品推荐

