如何创建 LINQ 表达式树以选择匿名类型
Posted
技术标签:
【中文标题】如何创建 LINQ 表达式树以选择匿名类型【英文标题】:How to create LINQ Expression Tree to select an anonymous type 【发布时间】:2010-10-11 00:05:38 【问题描述】:我想使用表达式树动态生成以下选择语句:
var v = from c in Countries
where c.City == "London"
select new c.Name, c.Population;
我已经弄清楚了如何生成
var v = from c in Countries
where c.City == "London"
select new c.Name;
但我似乎找不到可以让我在我的选择 lambda 中指定多个属性的构造函数/重载。
【问题讨论】:
【参考方案1】:如前所述,这可以在 Reflection Emit 和我在下面包含的辅助类的帮助下完成。下面的代码是一个正在进行的工作,所以把它当作它的价值......'它适用于我的盒子'。 SelectDynamic 方法类应该扔在一个静态扩展方法类中。
正如预期的那样,您不会获得任何 Intellisense,因为该类型直到运行时才创建。适用于后期绑定数据控件。
public static IQueryable SelectDynamic(this IQueryable source, IEnumerable<string> fieldNames)
Dictionary<string, PropertyInfo> sourceProperties = fieldNames.ToDictionary(name => name, name => source.ElementType.GetProperty(name));
Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);
ParameterExpression sourceItem = Expression.Parameter(source.ElementType, "t");
IEnumerable<MemberBinding> bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>();
Expression selector = Expression.Lambda(Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem);
return source.Provider.CreateQuery(Expression.Call(typeof(Queryable), "Select", new Type[] source.ElementType, dynamicType ,
Expression.Constant(source), selector));
public static class LinqRuntimeTypeBuilder
private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private static AssemblyName assemblyName = new AssemblyName() Name = "DynamicLinqTypes" ;
private static ModuleBuilder moduleBuilder = null;
private static Dictionary<string, Type> builtTypes = new Dictionary<string, Type>();
static LinqRuntimeTypeBuilder()
moduleBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(assemblyName.Name);
private static string GetTypeKey(Dictionary<string, Type> fields)
//TODO: optimize the type caching -- if fields are simply reordered, that doesn't mean that they're actually different types, so this needs to be smarter
string key = string.Empty;
foreach (var field in fields)
key += field.Key + ";" + field.Value.Name + ";";
return key;
public static Type GetDynamicType(Dictionary<string, Type> fields)
if (null == fields)
throw new ArgumentNullException("fields");
if (0 == fields.Count)
throw new ArgumentOutOfRangeException("fields", "fields must have at least 1 field definition");
try
Monitor.Enter(builtTypes);
string className = GetTypeKey(fields);
if (builtTypes.ContainsKey(className))
return builtTypes[className];
TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);
foreach (var field in fields)
typeBuilder.DefineField(field.Key, field.Value, FieldAttributes.Public);
builtTypes[className] = typeBuilder.CreateType();
return builtTypes[className];
catch (Exception ex)
log.Error(ex);
finally
Monitor.Exit(builtTypes);
return null;
private static string GetTypeKey(IEnumerable<PropertyInfo> fields)
return GetTypeKey(fields.ToDictionary(f => f.Name, f => f.PropertyType));
public static Type GetDynamicType(IEnumerable<PropertyInfo> fields)
return GetDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType));
【讨论】:
太棒了,不知道在运行时创建类型这么简单!谢谢! 很好,但“无法序列化接口 System.Linq.IQueryable” 您可以将 OrderBy 放入您的 //TODO 进行优化并完成。 @Ethan J. Brown,如果源是 IEnumerable 而不是 IQueryable,你能告诉我如何修改你的代码吗?谢谢! 我一直在使用这个(嗯,类似的)并且收到了Unable to create a constant value of type
错误。我已通过在最后一行将Expression.Constant(source)
替换为source.Expression
来解决此问题。希望这对某人有帮助:)【参考方案2】:
接受的答案非常有用,但我需要一些更接近真正匿名类型的东西。
真正的匿名类型具有只读属性、用于填充所有值的构造函数、用于比较每个属性的值的 Equals/GetHashCode 实现以及包含每个属性的名称/值的 ToString 实现. (有关匿名类型的完整描述,请参阅 https://msdn.microsoft.com/en-us/library/bb397696.aspx。)
基于匿名类的定义,我在github上https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs放了一个生成动态匿名类型的类。该项目还包含一些单元测试,以确保假匿名类型的行为与真实匿名类型一样。
这是一个非常基本的使用示例:
AnonymousTypeUtils.CreateObject(new Dictionary<string, object>
"a", 1 ,
"b", 2
);
另外,另一个注意事项:我发现当使用实体框架的动态匿名类型时,必须使用“members”参数集调用构造函数。例如:
Expression.New(
constructor: anonymousType.GetConstructors().Single(),
arguments: propertyExpressions,
members: anonymousType.GetProperties().Cast<MemberInfo>().ToArray()
);
如果您使用不包含“members”参数的 Expression.New 版本之一,Entity Framework 不会将其识别为匿名类型的构造函数。所以我假设这意味着一个真正的匿名类型的构造函数表达式将包含“成员”信息。
【讨论】:
【参考方案3】:可能有点晚,但可能对某人有所帮助。
您可以通过调用DynamicSelectGenerator
从实体中选择来生成动态选择。
public static Func<T, T> DynamicSelectGenerator<T>()
// get Properties of the T
var fields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray();
// input parameter "o"
var xParameter = Expression.Parameter(typeof(T), "o");
// new statement "new Data()"
var xNew = Expression.New(typeof(T));
// create initializers
var bindings = fields.Select(o => o.Trim())
.Select(o =>
// property "Field1"
var mi = typeof(T).GetProperty(o);
// original value "o.Field1"
var xOriginal = Expression.Property(xParameter, mi);
// set value "Field1 = o.Field1"
return Expression.Bind(mi, xOriginal);
);
// initialization "new Data Field1 = o.Field1, Field2 = o.Field2 "
var xInit = Expression.MemberInit(xNew, bindings);
// expression "o => new Data Field1 = o.Field1, Field2 = o.Field2 "
var lambda = Expression.Lambda<Func<T, T>>(xInit, xParameter);
// compile to Func<Data, Data>
return lambda.Compile();
并由此代码使用:
var result = dbContextInstancs.EntityClass.Select(DynamicSelectGenerator<EntityClass>());
【讨论】:
Expression.New(typeof(T)) 如果 T 是实体映射类型之一,它将不起作用。【参考方案4】:我不相信你能做到这一点。虽然当你做select new c.Name, c.Population
时,你似乎并没有创建一个你实际上是的类。如果您查看 Reflector 或原始 IL 中的编译输出,您将能够看到这一点。
你会有一个看起来像这样的类:
[CompilerGenerated]
private class <>c__Class
public string Name get; set;
public int Population get; set;
(好吧,我清理了一下,因为属性实际上只是 get_Name()
和 set_Name(name)
方法集)
您正在尝试做的是正确的动态类创建,在 .NET 4.0 出现之前将不可用(即使那样我也不确定它是否能够实现您想要的)。
最好的解决方案是定义不同的 anonymous 类,然后进行某种逻辑检查以确定要创建哪个类,并使用对象System.Linq.Expressions.NewExpression
创建它.
但是,如果您对底层 LINQ 提供程序非常了解,那么它可能(至少在理论上)是可能的。如果您正在编写自己的 LINQ 提供程序,则可以检测当前解析的表达式是否为 Select,然后确定 CompilerGenerated
类,反映其构造函数并创建。
这绝对不是一项简单的任务,但它将是 LINQ to SQL、LINQ to XML 等如何完成的。
【讨论】:
很好的总结。可惜没有办法生成表达式来生成新类型。虽然,我可以想象这会打开一大罐蠕虫。 :) 我会检查 System.Linq.Dynamic 中的扩展是如何工作的,我猜如果这个类可以做到的话,一定有办法做到这一点。【参考方案5】:您可以在此处使用 IQueryable-Extensions,它是“Ethan J. Brown”所描述的解决方案的实现:
https://github.com/thiscode/DynamicSelectExtensions
扩展动态构建匿名类型。
那么你可以这样做:
var YourDynamicListOfFields = new List<string>(
"field1",
"field2",
[...]
)
var query = query.SelectPartially(YourDynamicListOfFields);
【讨论】:
【参考方案6】:您可以使用参数类而不是使用匿名类型。在您的示例中,您可以像这样创建一个参数类:
public struct ParamClass
public string Name get; set; ;
public int Population get; set; ;
...然后像这样将其放入您的选择中:
var v = from c in Countries
where c.City == "London"
select new ParamClass c.Name, c.Population;
你得到的是IQueryable<ParamClass>
类型的东西。
【讨论】:
【参考方案7】:这可以编译,但我不知道它是否有效......
myEnumerable.Select((p) => return new Name = p.Name, Description = p.Description ; );
假设 p 是您的转换对象,并且 select 语句使用 lambda 的函数声明返回匿名类型。
编辑:我也不知道您将如何动态生成它。但至少它向您展示了如何使用 select lambda 返回具有多个值的匿名类型
编辑2:
您还必须记住,c# 编译器实际上会生成匿名类型的静态类。所以匿名类型在编译后确实有一个类型。因此,如果您在运行时生成这些查询(我假设您是),您可能必须使用各种反射方法构造一个类型(我相信您可以使用它们来动态创建类型)将创建的类型加载到执行上下文中并在生成的输出中使用它们。
【讨论】:
【参考方案8】:我认为大多数事情都已经得到解答 - 正如 Slace 所说,您需要一些可以从 Select
方法返回的类。一旦有了类,就可以使用System.Linq.Expressions.NewExpression
方法来创建表达式。
如果你真的想这样做,你也可以在运行时生成类。这需要更多的工作,因为它不能使用 LINQ 表达式树来完成,但它是可能的。您可以使用 System.Reflection.Emit
命名空间来执行此操作 - 我刚刚进行了快速搜索,这里有一篇文章对此进行了解释:
【讨论】:
【参考方案9】:您可以使用动态表达式 API,它允许您像这样动态构建您的选择语句:
Select("new(<property1>,<property2>,...)");
您需要 LINQ 中的 Dynamics.cs 文件和 Visual Studio 的语言示例,这两者都链接在 this page 的底部。您还可以在同一 URL 上看到一个显示此操作的工作示例。
【讨论】:
我相信它只适用于 LINQ to SQL,但不适用于其他 LINQ 提供程序 我相信该框架仅适用于 IQueryable,不适用于 IEnumerable。 我尝试了你的代码,它给出了错误如何使用 datacontext 在实体框架中实现上述代码?以上是关于如何创建 LINQ 表达式树以选择匿名类型的主要内容,如果未能解决你的问题,请参考以下文章