我想使用表达式树动态生成以下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};
但我似乎无法找到一个构造函数/重载,它允许我在select lambda中指定多个属性。
答案 0 :(得分:67)
如上所述,这可以通过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));
}
}
答案 1 :(得分:8)
接受的答案非常有用,但我需要更接近真实匿名类型的东西。
真正的匿名类型具有只读属性,用于填充所有值的构造函数,用于比较每个属性的值的Equals / GetHashCode实现,以及包含每个属性的名称/值的实现ToString 。 (有关匿名类型的完整说明,请参阅https://msdn.microsoft.com/en-us/library/bb397696.aspx。)
基于匿名类的定义,我在https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs的github上放了一个生成动态匿名类型的类。该项目还包含一些单元测试,以确保假匿名类型的行为与真实类型相同。
以下是如何使用它的一个非常基本的例子:
AnonymousTypeUtils.CreateObject(new Dictionary<string, object>
{
{ "a", 1 },
{ "b", 2 }
});
另外,另一个注意事项:我发现在使用带有Entity Framework的动态匿名类型时,必须使用“members”参数集调用构造函数。例如:
Expression.New(
constructor: anonymousType.GetConstructors().Single(),
arguments: propertyExpressions,
members: anonymousType.GetProperties().Cast<MemberInfo>().ToArray()
);
如果您使用其中一个不包含“members”参数的Expression.New版本,则Entity Framework不会将其识别为匿名类型的构造函数。所以我认为这意味着一个真正的匿名类型的构造函数表达式将包含“成员”信息。
答案 2 :(得分:2)
你可以在这里使用IQueryable-Extensions,这是“Ethan J. Brown”描述的解决方案的实现:
https://github.com/thiscode/DynamicSelectExtensions
扩展动态构建匿名类型。
然后你可以这样做:
var YourDynamicListOfFields = new List<string>(
"field1",
"field2",
[...]
)
var query = query.SelectPartially(YourDynamicListOfFields);
答案 3 :(得分:2)
也许有点晚,但可能对某人有帮助。
您可以从实体中选择,通过调用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>());
答案 4 :(得分:1)
我不相信你能够实现这一目标。虽然当你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发布之前不可用的东西(即使那时我也不确定它是否能够达到你想要的效果)。
您最好的解决方案是定义不同的匿名类,然后进行某种逻辑检查以确定要创建哪一个,并创建它,您可以使用对象{{1 }}
但是,如果您对基础LINQ提供程序真正变得非常核心,那么它(可能至少在理论上)可能会这样做。如果您 编写自己的LINQ提供程序,则可以检测当前解析的表达式是否为Select,然后确定System.Linq.Expressions.NewExpression
类,反映其构造函数并创建。
绝对不是一个简单的任务,但LINQ to SQL,LINQ to XML等都是如此。
答案 5 :(得分:1)
您可以使用参数类,而不是使用匿名类型。在您的示例中,您可以创建如下参数类:
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>
类型的东西。
答案 6 :(得分:1)
这编译,我不知道它是否有效......
myEnumerable.Select((p) => { return new { Name = p.Name, Description = p.Description }; });
假设p是你的变换,并且select语句返回一个anon类型,使用lambda的函数声明。
编辑:我也不知道你将如何动态生成它。但至少它告诉你如何使用select lambda返回一个带有多个值的anon类型
EDIT2:
您还必须记住,c#编译器实际上生成了anon类型的静态类。所以anon类型确实在编译后有一个类型。因此,如果您在运行时生成这些查询(我假设您是这样),您可能必须使用各种反射方法构建一个类型(我相信您可以使用它们来动态创建类型)将创建的类型加载到执行上下文中在生成的输出中使用它们。
答案 7 :(得分:1)
我认为大部分内容已经得到了解答 - 正如Slace所说,你需要一些可以从Select
方法返回的类。获得课程后,您可以使用System.Linq.Expressions.NewExpression
方法创建表达式。
如果你真的想这样做,你也可以在运行时生成类。这是一个更多的工作,因为它不能使用LINQ表达式树完成,但它是可能的。您可以使用System.Reflection.Emit
命名空间来执行此操作 - 我只是快速搜索,这是一篇解释此内容的文章:
答案 8 :(得分:0)
您可以使用动态表达式API,它允许您动态构建您的选择语句,如下所示:
Select("new(<property1>,<property2>,...)");
您需要LINQ中的Dynamics.cs文件和Visual Studio的语言示例才能使其工作,两者都链接在this page的底部。您还可以在同一个网址上看到一个显示此操作示例的工作示例。