Come creare LINQ Expression Tree per selezionare un tipo anonimo

Vorrei generare la seguente istruzione select usando dynamicmente gli alberi di espressione:

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

Ho elaborato come generare

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

ma non riesco a trovare un costruttore / sovraccarico che mi consenta di specificare più proprietà nel mio lambda selezionato.

Questo può essere fatto, come detto, con l’aiuto di Reflection Emit e una class di supporto che ho incluso di seguito. Il codice sotto è un work in progress, quindi prendilo per quello che vale … ‘funziona sulla mia scatola’. La class del metodo SelectDynamic deve essere sottoposta a una class di metodo di estensione statica.

Come previsto, non si ottiene alcun Intellisense poiché il tipo non viene creato fino al runtime. Funziona bene con i controlli dati ritardati.

 public static IQueryable SelectDynamic(this IQueryable source, IEnumerable fieldNames) { Dictionary sourceProperties = fieldNames.ToDictionary(name => name, name => source.ElementType.GetProperty(name)); Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values); ParameterExpression sourceItem = Expression.Parameter(source.ElementType, "t"); IEnumerable bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType(); 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 builtTypes = new Dictionary(); static LinqRuntimeTypeBuilder() { moduleBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(assemblyName.Name); } private static string GetTypeKey(Dictionary 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 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 fields) { return GetTypeKey(fields.ToDictionary(f => f.Name, f => f.PropertyType)); } public static Type GetDynamicType(IEnumerable fields) { return GetDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType)); } } 

La risposta accettata è molto utile, ma avevo bisogno di qualcosa di un po ‘più vicino ad un vero tipo anonimo.

Un tipo anonimo reale ha proprietà di sola lettura, un costruttore per il riempimento di tutti i valori, un’implementazione di Equals / GetHashCode per confrontare i valori di ogni proprietà e un’implementazione di ToString che include il nome / valore di ciascuna proprietà. (Vedere https://msdn.microsoft.com/en-us/library/bb397696.aspx per una descrizione completa dei tipi anonimi.)

Sulla base di questa definizione di classi anonime, inserisco una class che genera tipi dinamici anonimi su github in https://github.com/dotlatex/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs . Il progetto contiene anche alcuni test unitari per assicurarsi che i tipi anonimi falsi si comportino come quelli veri.

Ecco un esempio molto semplice di come usarlo:

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

Inoltre, un’altra nota: ho scoperto che quando si utilizza un tipo anonimo dinamico con Entity Framework, il costruttore deve essere chiamato con il set di parametri “membri”. Per esempio:

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

Se si è utilizzata una delle versioni di Expression.New che non include il parametro “members”, Entity Framework non lo riconoscerà come costruttore di un tipo anonimo. Suppongo quindi che l’espressione di costruttore di un tipo di anonimo reale includa le informazioni “membri”.

È ansible utilizzare le estensioni IQueryable qui, che è l’implementazione della soluzione descritta da “Ethan J. Brown”:

https://github.com/thiscode/DynamicSelectExtensions

L’estensione crea dynamicmente un tipo anonimo.

Quindi puoi fare questo:

 var YourDynamicListOfFields = new List( "field1", "field2", [...] ) var query = query.SelectPartially(YourDynamicListOfFields); 

Non credo che sarai in grado di raggiungere questo objective. Anche se quando select new { c.Name, c.Population } sembra che tu non stia creando una class in realtà. Se si guarda l’output compilato in Reflector o l’IL grezzo, si sarà in grado di vederlo.

Avrai una class che assomiglierebbe a qualcosa del genere:

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

(Ok, ho pulito un touch, dal momento che una proprietà è in realtà solo un get_Name() e set_Name(name) impostato in ogni caso)

Quello che stai cercando di fare è una corretta creazione di classi dinamiche, qualcosa che non sarà disponibile fino a quando non uscirà .NET 4.0 (e anche allora non sono sicuro che sarà in grado di ottenere ciò che desideri).

La soluzione migliore sarebbe definire le diverse classi anonime e quindi disporre di una sorta di controllo logico per determinare quale creare e per crearlo è ansible utilizzare l’object System.Linq.Expressions.NewExpression .

Ma, potrebbe essere (almeno in teoria) ansible farlo, se stai diventando veramente duro sul provider LINQ sottostante. Se si sta scrivendo il proprio provider LINQ, è ansible rilevare se l’espressione correntemente analizzata è Select, quindi determinare la class CompilerGenerated , riflettere per il suo costruttore e creare.

Sicuramente non è un compito semplice, ma sarebbe come LINQ a SQL, LINQ a XML, ecc. Lo fanno tutti.

Potresti usare una class parametro invece di lavorare con un tipo anonimo. Nel tuo esempio puoi creare una class parametro come questa:

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

… e inseriscilo nella tua selezione in questo modo:

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

Quello che ottieni è qualcosa del tipo IQueryable .

Questo compila, non so se funziona comunque …

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

Supponendo che p è ciò che stai trasformando, e l’istruzione select restituisce un tipo anon, usando la dichiarazione di funzione di lambda.

Modifica: anche io non so come generarlo dynamicmente. Ma almeno ti mostra come usare il lambda di selezione per restituire un tipo anon con più valori

Edit2:

Dovresti anche tenere a mente che il compilatore c # genera effettivamente classi statiche del tipo anon. Quindi il tipo anon ha effettivamente un tipo dopo la compilazione. Quindi, se si generano queste query in fase di esecuzione (che presumo che tu sia) potresti dover build un tipo usando i vari metodi di riflessione (credo che tu possa usarli per creare i tipi al volo) carica i tipi creati nel contesto di esecuzione e usali nel tuo output generato.

Penso che la maggior parte delle cose abbia già una risposta – come ha detto Slace, hai bisogno di una class che verrebbe restituita dal metodo Select . Una volta System.Linq.Expressions.NewExpression la class, è ansible utilizzare il metodo System.Linq.Expressions.NewExpression per creare l’espressione.

Se vuoi davvero farlo, puoi anche generare classi in fase di esecuzione. È un po ‘più di lavoro, perché non può essere fatto usando gli alberi di espressione LINQ, ma è ansible. Puoi utilizzare lo spazio dei nomi System.Reflection.Emit per farlo – Ho appena fatto una rapida ricerca e qui c’è un articolo che spiega questo:

  • Introduzione alla creazione di tipi dinamici con Reflection.Emit

Forse un po ‘tardi ma può aiutare qualcuno.

È ansible generare dinamico selezionare per chiamata DynamicSelectGenerator in selezionare da un’ quadro.

 public static Func DynamicSelectGenerator() { // 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>(xInit, xParameter); // compile to Func return lambda.Compile(); } 

E usa questo codice:

 var result = dbContextInstancs.EntityClass.Select(DynamicSelectGenerator()); 

Puoi utilizzare l’API di Dynamic Expression che ti consente di creare dynamicmente la tua istruzione select in questo modo:

  Select("new(,,...)"); 

È necessario il file Dynamics.cs dal LINQ e gli esempi di lingua per Visual Studio affinché questo funzioni, entrambi sono collegati in fondo a questa pagina . Puoi anche vedere un esempio funzionante che mostra questo in azione sullo stesso URL.