2013-08-20 18 views
5

Quando si lavora con IQuerayble<TItem> possiamo chiamare Select come questo:Come istanziare e inizializzare un oggetto dinamico nell'albero delle espressioni?

query.Select(item => new { A=item.Prop1, B=item.Prop2}); 

E Select metodo si aspetta Expression<Func<TItem,TResult>>

ho bisogno di usare ExpandoObject posto di classe anonima, ma staticamente tipizzato.

Se fosse possibile sarebbe assomigliare:

query.Select(item => dynamic new ExpandoBoject { A=item.Prop1, B=item.Prop2}); 

quindi voglio costruire albero di espressione Expression<Func<TItem,ExpandoObject>> dove le proprietà dell'oggetto vengono inizializzati in modo simile a quello con tipo anonimo.
La funzionalità dinamica è necessaria solo per l'inizializzazione, quindi è corretto che Func restituisca ExpandoObject anziché dynamic.

Non riesco a trovare molta documentazione su Expression.Dynamic e sui relativi raccoglitori che dovrei usare.


Update 1

Perchè ho bisogno di tutta questa roba?
Perché voglio get primary keys.
Voglio farlo per qualsiasi tipo di entità.

So come ottenere l'elenco delle proprietà che compongono PK, ma ora ho bisogno di fare una proiezione difficile dell'entità a EntityKey. Bene, potrebbe essere lo stesso equivalente di questa classe.

var keys = context.Set<TEntity>().Where(Expression<Func<TEntity,bool>).Select(Expression<Func<TEntity,EntityKey>>); 

Come ho osservato nelle lambda commenti contenenti blocchi non possono essere convertiti in strutture di espressione in modo che io possa non è semplice creare il dizionario e riempirlo. Ora sto giocando con l'albero di espressione semanticamente vicino a questo codice:

var dict = new Dictionary<string,object>(); 
dict.Add("Prop1",value1); 
dict.Add("Prop2",value2); 
return dict 

Ma dubito EF in grado di analizzare l'espressione contenenti blocchi. Devo verificare.
E sono curioso di sapere se funzionerà con oggetti dinamici ed Expression.MemberInit come funziona con oggetti statici.


Update 2

Entity Framework non supporta la sintassi di inizializzazione dizionario.
Genera NotSupportedException con il messaggio: Solo gli elementi di inizializzazione dell'elenco con un singolo elemento sono supportati in LINQ su Entità.


Update 3

EF non supporta le espressioni di blocco pure.
NotSupportedException con messaggio: Espressione LINQ sconosciuta di tipo "Blocco".

+0

Non è possibile fare qualcosa di simile a questo: 'query.Select (item => dynamic new ExpandoBoject {A = item.Prop1, B = item.Prop2});' Lippert ha detto: http: // stackoverflow. it/questions/7478048/why-cant-i-do-this-dynamic-x-new-expandoobject-foo-12-bar-twelve. – xanatos

+0

Lo so, non posso. Era un esempio di intenzione di costruire l'albero di espressione con la dinamica. –

+0

Ciò che si può fare è creare un tipo anonimo in LINQ e quindi, dopo che IQueryable è terminato, nella parte IEnumerable copia il tipo anonimo in un oggetto Expando. Chiaramente entrambe le espressioni possono essere autogenerate in qualche modo. – xanatos

risposta

3

Ora sto giocando con l'albero di espressione semanticamente vicino a questo codice:

var dict = new Dictionary<string,object>(); 
dict.Add("Prop1",value1); 
dict.Add("Prop2",value2); 
return dict; 

È possibile farlo, perché si può scrivere che codice come come una singola espressione come this:

new Dictionary<string, object> 
{ 
    { "Prop1", value1 }, 
    { "Prop2", value2 } 
}; 

Ed è possibile creare un albero di espressioni che contiene questa espressione (che EF s hould essere in grado di gestire) in questo modo:

var addMethod = typeof(Dictionary<string, object>).GetMethod("Add"); 

var expression = Expression.Lambda<Func<Dictionary<string, object>>>(
    Expression.ListInit(
     Expression.New(typeof(Dictionary<string, object>)), 
     Expression.ElementInit(
      addMethod, 
      Expression.Constant("Prop1"), 
      value1Expression), 
     Expression.ElementInit(
      addMethod, 
      Expression.Constant("Prop2"), 
      value2Expression)), 
    itemParameterExpression); 
+0

Ci proverò oggi, ma EF si è lamentato della sintassi di inizializzazione del dizionario dicendo che comprende solo liste semplici. –

+0

No, EF genera un'eccezione. –

+0

@voroninp In questo caso, puoi provare qualcosa come 'new [] {Tuple.Create (" Prop1 ", value1), Tuple.Create (" Prop2 ", value2)}'. – svick

1

La cosa descritta è difficile soprattutto perché non siamo in grado di creare tipo anonimo in modo dinamico in fase di esecuzione - hanno bisogno di essere conosciuto già al momento della compilazione. Quindi la mia proposizione è di creare una classe che possa contenere diverse proprietà di tipo arbitrario scelto (simile a Tuple), tuttavia caricheremo dai valori db solo per le proprietà importanti per noi. Quindi abbiamo bisogno di una classe come questa:

public class CustomTuple<T1, T2> 
{ 
    public T1 Item1 { get; set; } 
    public T2 Item2 { get; set; } 
} 

Possiamo aggiungere altre proprietà se abbiamo bisogno di più. Se avessimo una tale classe con 5 proprietà che con l'uso di essa potremmo caricare al massimo 5 proprietà. Ora la logica di proiezione:

Type[] parameterTypes = new Type[] { typeof(int), typeof(object) }; 
Type tupleType = typeof(CustomTuple<,>).MakeGenericType(parameterTypes); 
ParameterExpression x = Expression.Parameter(typeof(Entity)); 
NewExpression body = Expression.New(tupleType.GetConstructor(new Type[0]), new Expression[0]); 
MemberBinding binding1 = Expression.Bind(
    typeof(CustomTuple<,>).MakeGenericType(parameterTypes).GetProperty("Item1"), 
    Expression.Property(x, "Value")); 
MemberInitExpression memberInitExpression = 
    Expression.MemberInit(
     body, 
     binding1); 

Expression<Func<Entity, object>> exp = Expression.Lambda<Func<Entity, object>>(memberInitExpression, x); 
using (MyDbContext context = new MyDbContext()) 
{ 
    var list = context.Entities.Select(exp).ToList(); 
} 

Il codice precedente seleziona dai valori di raccolta Entità della proprietà Valore classe Entity. parameterTypes definisce i tipi di parametri restituiti da Seleziona proiezione. Se non intendiamo usare la proprietà data, la lasciamo come tipo di oggetto. Quindi dobbiamo creare un'espressione di inizializzazione. Lo facciamo con il metodo Nuovo, assegniamo i valori delle proprietà con l'espressione creata da Expression.Bind e li combiniamo con Expression.MemberInit. Possiamo creare dinamicamente in fase di esecuzione tutte le espressioni MemberBinding di cui abbiamo bisogno.

+0

In realtà, è simile a quello che ho fatto alla fine. Ma ho iniziato da zero proprietà. Creato in modo analogo a ExpandoObject con ereditarietà consentita e quindi dinamicamente con CodeEmit ha generato la classe antenato con le proprietà richieste. Ciò mi ha permesso di interrogare genericamente i PK delle entità da EF. –

+0

Buona idea per generare dinamicamente tali classi. Cercherò di modificare la mia risposta per includerla. – mr100

Problemi correlati