如何创buildLINQexpression式树来select一个匿名types
我想使用expression式树dynamic地生成下面的select语句:
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};
但我似乎无法find一个构造函数/重载,让我指定多个属性在我selectlambda。
如上所述,这可以通过Reflection Emit和我在下面包含的帮助类来完成。 下面的代码是一个正在进行的工作,所以把它拿到它的价值……“它在我的盒子上工作”。 SelectDynamic方法类应抛弃在静态扩展方法类中。
正如所料,你不会得到任何Intellisense,因为types不是直到运行时才创build的。 适用于晚期数据控制。
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)); } }
被接受的答案是非常有用的,但是我需要更接近真正的匿名types。
真正的匿名types具有只读属性,用于填充所有值的构造函数,用于比较每个属性值的Equals / GetHashCode实现,以及包含每个属性名称/值的实现ToString。 (有关匿名types的完整说明,请参阅https://msdn.microsoft.com/en-us/library/bb397696.aspx 。)
根据匿名类的定义,我在https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs上放置了一个在github上生成dynamic匿名types的类。; 该项目还包含一些unit testing,以确保假匿名types的行为像真实的。
以下是如何使用它的一个非常基本的例子:
AnonymousTypeUtils.CreateObject(new Dictionary<string, object> { { "a", 1 }, { "b", 2 } });
另外,还有一点需要注意:我发现在Entity Framework中使用dynamic匿名types时,必须使用“members”参数集来调用构造函数。 例如:
Expression.New( constructor: anonymousType.GetConstructors().Single(), arguments: propertyExpressions, members: anonymousType.GetProperties().Cast<MemberInfo>().ToArray() );
如果您使用了不包含“members”参数的Expression.New版本之一,Entity Framework将无法将其识别为匿名types的构造函数。 所以我假设这意味着一个真正的匿名types的构造函数expression式将包含“成员”信息。
我不相信你能做到这一点。 虽然当你select new { c.Name, c.Population }
你似乎没有创build一个类,你实际上是。 如果你看看reflection器或原始IL的编译输出,你将能够看到这一点。
你将有一个类似这样的类:
[CompilerGenerated] private class <>c__Class { public string Name { get; set; } public int Population { get; set; } }
(好吧,我把它清理了一下,因为属性实际上只是一个get_Name()
和set_Name(name)
方法集)
你想要做的是正确的dynamic类创build,直到.NET 4.0出来(甚至在那时我不确定它是否能够实现你想要的)将不可用。
您最好的解决scheme是定义不同的匿名类,然后进行某种逻辑检查以确定要创build哪个类,并创build它,您可以使用System.Linq.Expressions.NewExpression
对象。
但是,如果你真正了解底层的LINQ提供者,那么可能(至less在理论上)可以这么做。 如果您正在编写自己的LINQ提供程序,则可以检测当前parsing的expression式是否为Select,然后确定CompilerGenerated
类,反映其构造函数并进行创build。
当然不是一个简单的任务,但是这将是怎样的LINQ to SQL,LINQ to XML,等等。
您可以使用参数类而不是使用匿名types。 在你的例子中,你可以像这样创build一个参数类:
public struct ParamClass { public string Name { get; set; }; public int Population { get; set; }; }
…并把它放到你的select是这样的:
var v = from c in Countries where c.City == "London" select new ParamClass {c.Name, c.Population};
你得到的是typesIQueryable<ParamClass>
。
这编译,我不知道,如果它的作品,但…
myEnumerable.Select((p) => { return new { Name = p.Name, Description = p.Description }; });
假设p是你的转换,并且select语句返回一个anontypes,使用lambda的函数声明。
编辑:我也不知道你将如何dynamic生成这个。 但至less它会告诉你如何使用select lambda来返回具有多个值的anontypes
EDIT2:
你也必须记住,c#编译器实际上是生成anontypes的静态类。 所以anontypes在编译时确实有一个types。 所以,如果你在运行时产生这些查询(我假设你是这样),你可能必须使用各种reflection方法(我相信你可以用它们来实时创buildtypes)构造一个types,将创build的types加载到执行上下文中在你生成的输出中使用它们。
我想大部分的东西都已经被回答了 – 就像Slace所说的,你需要一些从Select
方法返回的类。 一旦你有了这个类,你可以使用System.Linq.Expressions.NewExpression
方法来创buildexpression式。
如果你真的想这样做,你也可以在运行时生成类。 这是一个更多的工作,因为它不能使用LINQexpression式树来完成,但它是可能的。 你可以使用System.Reflection.Emit
命名空间来做到这一点 – 我只是做了一个快速search,这里有一篇文章解释了这一点:
- 使用Reflection.Emit创builddynamictypes简介
您可以在这里使用IQueryable-Extensions,这是由“Ethan J. Brown”描述的解决scheme的实现:
https://github.com/thiscode/DynamicSelectExtensions
扩展dynamic构build一个匿名types。
那么你可以这样做:
var YourDynamicListOfFields = new List<string>( "field1", "field2", [...] ) var query = query.SelectPartially(YourDynamicListOfFields);
您可以使用dynamicexpression式API,它允许您dynamic地构build您的select语句,如下所示:
Select("new(<property1>,<property2>,...)");
您需要LINQ的Dynamics.cs文件和Visual Studio的语言样本才能工作,两者都链接在此页面的底部。 您也可以在同一个URL中看到一个正在运行的工作示例。
也许有点晚,但可能有助于某人。
您可以通过调用DynamicSelectGenerator
从实体中select生成dynamicselect。
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>());