2009-03-03 19 views
40

Chciałbym wygenerować następującą instrukcję SELECT dynamicznie za pomocą wyrażenia drzew:Jak utworzyć LINQ Expression drzewo, aby wybrać typ anonimowy

var v = from c in Countries 
     where c.City == "London" 
     select new {c.Name, c.Population}; 

Ja pracowałem się, jak generować

var v = from c in Countries 
     where c.City == "London" 
     select new {c.Name}; 

ale Nie mogę znaleźć konstruktora/przeciążenia, które pozwoli mi określić wiele właściwości w wybranej przeze mnie metodzie lambda.

+3

mogłyby księgowania kod, który wpadliśmy do tej pory? – Andy

Odpowiedz

62

Można to zrobić, jak już wspomniano, za pomocą refleksji Emit i klasa pomocnika, które uwzględniłem poniżej. Poniższy kod jest w toku, więc weź go za to, co jest warte ... "działa na moim pudełku". Klasa metody SelectDynamic powinna być rzucana w klasę metod statycznego rozszerzenia.

Zgodnie z oczekiwaniami, nie otrzymasz żadnych IntelliSense, ponieważ typ nie zostanie utworzony przed uruchomieniem. Działa dobrze na późnych kontrolach danych.

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

cudowny, nie wiedziałem, że tworzenie typu w czasie wykonywania było takie proste! – mCasamento

+0

Dobrze, ale "Nie można serializować interfejsu System.Linq.IQueryable" –

+2

Możesz umieścić OrderBy w swoim // TODO dla optymalizacji i gotowe. –

0

Można użyć dynamicznej Expression API, które pozwala dynamicznie zbudować select tak:

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

Musisz się Dynamics.cs plik z LINQ i próbek językowych dla Visual Studio to zadziałało , oba są połączone na dole this page. Możesz zobaczyć działający przykład pokazujący to w akcji pod tym samym adresem URL.

+0

Wierzę, że będzie działać tylko z LINQ do SQL, a nie innego dostawcy LINQ przez –

+0

Wierzę, że framework działa tylko z IQueryable, a nie z IEnumerable. –

+0

próbowałem kodu u przez daje błąd, jak zaimplementować powyższy kod w strukturze podmiotu przy użyciu datacontext? – Thulasiram

1

Nie wierzę, że będziesz w stanie to osiągnąć. Chociaż po wykonaniu select new { c.Name, c.Population } wydaje się, że nie tworzysz klasy, którą faktycznie jesteś. Jeśli spojrzysz na skompilowane wyjście w Reflectorze lub surowym IL, będziesz w stanie to zobaczyć.

Będziesz mieć klasę, która będzie wyglądać następująco:

[CompilerGenerated] 
private class <>c__Class { 
    public string Name { get; set; } 
    public int Population { get; set; } 
} 

(Ok, wyczyściłem go dotknąć, ponieważ nieruchomość jest naprawdę tylko get_Name() i set_Name(name) sposób ustawić tak)

To, co próbujesz zrobić, to odpowiednie tworzenie klas dynamicznych, coś, co będzie dostępne, dopóki nie pojawi się .NET 4.0 (a nawet wtedy nie jestem pewien, czy będzie w stanie osiągnąć to, co chcesz).

Jesteś najlepszym rozwiązaniem byłoby zdefiniowanie różnych anonimowych klas, a następnie mieć jakiś logiczny sprawdza, które z nich tworzyć, i utworzyć go można użyć obiektu System.Linq.Expressions.NewExpression.

Ale może być (przynajmniej teoretycznie) możliwe do zrobienia, jeśli robisz się naprawdę twardy na temat podstawowego dostawcy LINQ. Jeśli jest, pisząc własnego dostawcę LINQ, możesz wykryć, czy aktualnie analizowane wyrażenie jest Select, to musisz określić klasę CompilerGenerated, zastanowić się nad jej konstruktorem i utworzyć.

Zdecydowanie nie jest to proste zadanie, ale tak samo jak LINQ do SQL, LINQ do XML, itp. To wszystko.

+0

Dobre podsumowanie. Szkoda, że ​​nie ma możliwości wygenerowania wyrażenia w celu wygenerowania nowego typu. Chociaż mogę sobie wyobrazić, że otwiera to wielką puszkę robaków. :) – Inferis

+0

Sprawdzę, jak rozszerzenia w pracy System.Linq.Dynamic, zgaduję, że musi być sposób, aby to zrobić, jeśli klasa może to zrobić. –

1

Można użyć klasy parametrów zamiast pracować z typem anonimowym. W przykładzie można utworzyć klasę parametru tak:

public struct ParamClass { 
    public string Name { get; set; }; 
    public int Population { get; set; }; 
} 

... i umieścić go w select tak:

var v = from c in Countries 
     where c.City == "London" 
     select new ParamClass {c.Name, c.Population}; 

Co masz na to coś w rodzaju IQueryable<ParamClass>.

1

To kompiluje, nie wiem, czy to działa ...

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

Zakładając p jest to, co transformującego oraz SELECT zwraca typ anon, używając deklaracji funkcji lambda.

Edycja: Nie wiem również, w jaki sposób wygenerowałbyś to dynamicznie. Ale przynajmniej pokazuje, jak używać wybierz lambda wrócić typ anon z wielu wartości

Edit2:

będzie też trzeba mieć na uwadze, że kompilator C# faktycznie generuje statyczne klasy w anon rodzaj. Tak więc typ anon ma typ po kompilacji. Więc jeśli twoja generacja tych zapytań w czasie wykonywania (co przypuszczam, że jesteś), być może będziesz musiał skonstruować typ za pomocą różnych metod refleksji (uważam, że możesz ich użyć do tworzenia typów w locie) załaduj utworzone typy do kontekstu wykonania i używaj ich w wygenerowanych wynikach.

1

Myślę, że większość rzeczy już została odebrana - jak powiedział Slace, potrzebujesz pewnej klasy, która zostanie zwrócona z metody Select. Po utworzeniu klasy można użyć metody System.Linq.Expressions.NewExpression do utworzenia wyrażenia.

Jeśli naprawdę chcesz to zrobić, możesz również wygenerować klasę w czasie wykonywania. To trochę więcej pracy, ponieważ nie można tego zrobić przy użyciu drzew wyrażeń LINQ, ale jest to możliwe. Można użyć System.Reflection.Emit nazw to zrobić - po prostu tak szybkiego wyszukiwania i tu jest artykuł, który wyjaśnia to:

2

można użyć IQueryable-extensions tutaj, co jest wdrożeniowymi rozwiązania opisanego przez "Ethan J. Brown":

https://github.com/thiscode/DynamicSelectExtensions

Extension buduje dynamicznie typ anonimowy.

Następnie można to zrobić:

var YourDynamicListOfFields = new List<string>(

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

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

Przyjęte rozwiązanie jest bardzo przydatne, ale ja potrzebowałem czegoś trochę bliżej do prawdziwego anonimowego typu.

Prawdziwy anonimowy typ ma właściwości tylko do odczytu, konstruktor do wypełniania wszystkich wartości, implementacja równań/GetHashCode do porównywania wartości każdej właściwości oraz implementacja ToString, która zawiera nazwę/wartość każdego z nich. własność. (Aby uzyskać pełny opis typów anonimowych, zobacz https://msdn.microsoft.com/en-us/library/bb397696.aspx).

Na podstawie tej definicji anonimowych klas, umieściłem klasę, która generuje dynamiczne anonimowe typy na github pod numerem https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs. Projekt zawiera również testy jednostkowe, aby upewnić się, że fałszywe typy anonimowe zachowują się jak prawdziwe.

Oto bardzo prosty przykład, jak z niego korzystać:

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

Również inna uwaga: Okazało się, że w przypadku korzystania z dynamicznego typ anonimowy z Entity Framework, konstruktor musi być wywołana z „członków” zestaw parametrów. Na przykład:

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

Jeśli użyto jednego z wersjami Expression.New że nie zawiera parametr „członkowie”, Entity Framework nie rozpozna go jako konstruktora typu anonimowego. Zakładam więc, że oznacza to, że wyrażenie konstruktora typu anonimowego typu zawierałoby informację "członków".

0

Może trochę za późno, ale może komuś pomóc.

Możesz wygenerować dynamiczny wybór przez wywołanie DynamicSelectGenerator w wybranej jednostce.

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(); 
     } 

i wykorzystywanie przez ten kod:

var result = dbContextInstancs.EntityClass.Select(DynamicSelectGenerator<EntityClass>()); 
Powiązane problemy