2009-03-03 141 views
40

我想生成以下select語句動態使用表達式樹:如何創建LINQ表達式樹選擇一個匿名類型

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中指定多個屬性。

+3

你能發佈自己拿出這麼遠的代碼? – Andy 2009-03-03 12:11:56

回答

62

這是可以做到,如前所述,與反射的幫助發射和我已經包括在下面的助手類。下面的代碼是一個正在進行的工作,所以把它看作是值得的......'它在我的盒子上工作'。 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)); 
    } 
} 
0

您可以使用動態表達API,它允許你動態地構建你的SELECT語句是這樣的:

Select("new(<property1>,<property2>,...)"); 

您需要Dynamics.cs從LINQ和Visual Studio的語言樣本文件中查找該工作,兩者都鏈接在this page的底部。您還可以在同一個URL中看到一個正在運行的工作示例。

+0

我相信只能用於LINQ to SQL,而不是另一個LINQ提供者,儘管 – 2009-03-03 12:26:23

+0

我相信該框架只適用於IQueryable,而不適用於IEnumerable。 – 2009-03-03 12:41:49

+0

我嘗試了你的代碼,它給錯誤如何在實體框架中使用datacontext實現上面的代碼? – Thulasiram 2012-06-20 06:35:22

1

我不相信你能夠做到這一點。雖然當你做select new { c.Name, c.Population }看起來你並沒有創建一個真正的班級。如果你看看反射器或原始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到SQL,LINQ到XML等等。

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>的東西。

1

這個編譯,我不知道它是否工作,但是...

myEnumerable.Select((p) => { return new { Name = p.Name, Description = p.Description }; }); 

假設p是你的轉換,並且select語句返回一個anon類型,使用lambda的函數聲明。

編輯:我也不知道你會如何動態生成這個。但至少它表明你如何使用選擇拉姆達與多個值

EDIT2返回一個匿名類型:

你也必須考慮到裸,那C#編譯器實際生成不久的靜態類類型。所以anon類型在編譯時確實有一個類型。所以如果你在運行時產生這些查詢(我認爲你是這樣),你可能必須使用各種反射方法構建一個類型(我相信你可以使用它們來實時創建類型)將創建的類型加載到執行上下文中,在你生成的輸出中使用它們。

1

我認爲大部分事情已經得到解答 - 正如Slace所說,你需要一些從Select方法中返回的類。一旦你有了課程,你可以使用System.Linq.Expressions.NewExpression方法來創建表達式。

如果你真的想這樣做,你也可以在運行時生成類。這是一個更多的工作,因爲它不能使用LINQ表達式樹來完成,但它是可能的。您可以使用System.Reflection.Emit命名空間來做到這一點 - 我只是做了快速搜索,這裏是說明這個問題的文章:

2

您可以在這裏使用的IQueryable的擴展,它是由「伊森J.布朗」中描述的解決方案的implemantation:

https://github.com/thiscode/DynamicSelectExtensions

的擴展動態創建一個匿名類型。

然後,你可以這樣做:

var YourDynamicListOfFields = new List<string>(

    "field1", 
    "field2", 
    [...] 

) 
var query = query.SelectPartially(YourDynamicListOfFields); 
6

接受的答案是非常有用的,但我需要的東西更接近真正的匿名類型一點。

真正的匿名類型具有隻讀屬性,用於填充所有值的構造函數,用於比較每個屬性值的Equals/GetHashCode實現以及包含每個屬性值的實現ToString屬性。 (有關匿名類型的完整說明,請參閱https://msdn.microsoft.com/en-us/library/bb397696.aspx)。

基於匿名類的定義,我將一個類生成動態匿名類型的類https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs。該項目還包含一些單元測試,以確保假匿名類型的行爲與真實匿名類型相似。

下面是如何使用它的一個非常簡單的例子:

AnonymousTypeUtils.CreateObject(new Dictionary<string, object> 
{ 
    { "a", 1 }, 
    { "b", 2 } 
}); 

此外,另注:我發現,使用動態匿名類型與實體框架時,構造函數必須用「成員」叫參數集。例如:

Expression.New(
    constructor: anonymousType.GetConstructors().Single(), 
    arguments: propertyExpressions, 
    members: anonymousType.GetProperties().Cast<MemberInfo>().ToArray() 
); 

如果您使用的是不包括「成員」參數Expression.New的版本之一,實體框架將不會承認它是一個匿名類型的構造函數。所以我認爲這意味着一個真正的匿名類型的構造函數表達式會包含「成員」信息。

0

也許有點晚,但可能有助於某人。

您可以通過致電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>());