[英]How to create LINQ Expression Tree to select an anonymous type
我想使用表達式樹動態生成以下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中指定多個屬性。
如上所述,這可以通過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));
}
}
接受的答案非常有用,但我需要更接近真實匿名類型的東西。
真正的匿名類型具有只讀屬性,用於填充所有值的構造函數,用於比較每個屬性的值的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不會將其識別為匿名類型的構造函數。 所以我認為這意味着真正的匿名類型的構造函數表達式將包含“成員”信息。
你可以在這里使用IQueryable-Extensions,這是“Ethan J. Brown”描述的解決方案的實現:
https://github.com/thiscode/DynamicSelectExtensions
Extension以動態方式構建匿名類型。
然后你可以這樣做:
var YourDynamicListOfFields = new List<string>(
"field1",
"field2",
[...]
)
var query = query.SelectPartially(YourDynamicListOfFields);
也許有點晚,但可能對某人有幫助。
您可以通過從實體中選擇調用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>());
我不相信你能夠做到這一點。 雖然當你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發布之前不可用的東西(即使那時我也不確定它是否能夠實現你想要的)。
您最好的解決方案是定義不同的匿名類,然后進行某種邏輯檢查以確定要創建哪個,並創建它,您可以使用對象System.Linq.Expressions.NewExpression
。
但是,如果您真正了解基礎LINQ提供程序,那么它(可能至少在理論上)可能會這樣做。 如果您正在編寫自己的LINQ提供程序,則可以檢測當前解析的表達式是否為Select,然后確定CompilerGenerated
類,反映其構造函數並創建。
絕對不是一個簡單的任務,但它將是LINQ to SQL,LINQ to XML等所有人都這樣做的。
您可以使用參數類而不是使用匿名類型。 在您的示例中,您可以創建如下的參數類:
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>
類型的東西。
編譯,我不知道它是否有效...
myEnumerable.Select((p) => { return new { Name = p.Name, Description = p.Description }; });
假設p是你的轉換,並且select語句返回anon類型,使用lambda的函數聲明。
編輯:我也不知道你將如何動態生成它。 但至少它告訴你如何使用select lambda返回一個具有多個值的anon類型
EDIT2:
您還必須記住,c#編譯器實際上生成了anon類型的靜態類。 所以anon類型確實在編譯后有一個類型。 因此,如果您在運行時生成這些查詢(我假設您是這樣),您可能必須使用各種反射方法構建一個類型(我相信您可以使用它們來動態創建類型)將創建的類型加載到執行上下文中在生成的輸出中使用它們。
我認為大部分內容已經得到了解答 - 正如Slace所說,你需要一些可以從Select
方法返回的類。 獲得類后,可以使用System.Linq.Expressions.NewExpression
方法創建表達式。
如果你真的想這樣做,你也可以在運行時生成類。 這是一個更多的工作,因為它不能使用LINQ表達式樹完成,但它是可能的。 你可以使用System.Reflection.Emit
命名空間來做到這一點 - 我只是做了一個快速搜索,這是一篇解釋這個的文章:
您可以使用動態表達式API,它允許您動態構建您的select語句,如下所示:
Select("new(<property1>,<property2>,...)");
您需要LINQ中的Dynamics.cs文件和Visual Studio的語言示例才能使用,這兩個文件都鏈接在本頁底部。 您還可以在同一URL上看到一個顯示此操作的工作示例。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.