2012-01-25 14 views
6

Ho chiesto un very similar question ieri, ma non è stato fino a oggi ho capito che la risposta che ho accettato non risolve tutti i miei problemi. Ho il seguente codice:Come posso creare una proprietà multi-selezione dinamica su un oggetto IEnumerable <T> in fase di runtime?

public Expression<Func<TItem, object>> SelectExpression<TItem>(string fieldName) 
{ 
    var param = Expression.Parameter(typeof(TItem), "item"); 
    var field = Expression.Property(param, fieldName); 
    return Expression.Lambda<Func<TItem, object>>(field, 
     new ParameterExpression[] { param }); 
} 

che viene utilizzato come segue:

string primaryKey = _map.GetPrimaryKeys(typeof(TOriginator)).Single(); 
var primaryKeyExpression = SelectExpression<TOriginator>(primaryKey); 
var primaryKeyResults = query.Select(primaryKeyExpression).ToList(); 

Questo mi permette di tirare fuori le chiavi primarie da un IQueryable<TUnknown>. Il problema è che questo codice funziona solo con una singola chiave primaria e ho bisogno di aggiungere il supporto per più PK.

Quindi, c'è un modo per adattare il metodo SelectExpression in precedenza per prendere un IEnumerable<string> (che è il mio elenco di nomi di proprietà di chiave primaria) e il metodo restituire un'espressione che seleziona quelle chiavi?

I.e. Dato il seguente:

var knownRuntimePrimaryKeys = new string[] { "CustomerId", "OrderId" }` 

mio Select ha bisogno di fare quanto segue (in fase di esecuzione):

var primaryKeys = query.Select(x=> new { x.CustomerId, x.OrderId }); 
+1

Il problema è che i tipi anonimi che sono i risultati del select statemet sono creati al momento della compilazione .. – m0sa

+1

Esiste un'alternativa all'utilizzo di un tipo anonimo e ancora ottenere quello che ho bisogno? O è tornato al tavolo da disegno? – GenericTypeTea

+0

@GenericTypeTea, una tupla sarebbe un'opzione accettabile per te? –

risposta

2

si potrebbe usare Tuple<> perché i tipi anonimi devono essere noti al momento della compilazione:

public Expression<Func<TItem, object>> SelectExpression<TItem>(params string[] fieldNames) 
{ 
    var param = Expression.Parameter(typeof(TItem), "item"); 
    var fields = fieldNames.Select(x => Expression.Property(param, x)).ToArray(); 
    var types = fields.Select(x => x.Type).ToArray(); 
    var type = Type.GetType("System.Tuple`" + fields.Count() + ", mscorlib", true); 
    var tuple = type.MakeGenericType(types); 
    var ctor = tuple.GetConstructor(types); 
    return Expression.Lambda<Func<TItem, object>>(
     Expression.New(ctor, fields), 
     param 
    ); 
} 

e quindi:

var primaryKeyExpression = SelectExpression<TOriginator>("CustomerId", "OrderId"); 

genererà la seguente espressione:

item => new Tuple<string, string>(item.CustomerId, item.OrderId) 
+1

Sfortunatamente questo non sembra funzionare con un IQueryable da Entity Framework. "Solo i costruttori e gli inizializzatori senza parametri sono supportati in LINQ alle entità." – GenericTypeTea

+0

@GenericTypeTea, oh, bene, questo è triste. In questo caso è possibile utilizzare Reflection.Emit per generare un tipo dinamico in fase di esecuzione con il giusto numero di proprietà e quindi creare un'espressione che assegna tali proprietà. Sembra un sacco di lavoro però. –

+0

Sto iniziando a chiedermi se è meglio forzare l'applicazione che consuma per implementare un'interfaccia che specifichi su tutti gli oggetti EF POCO. Quindi potrei semplicemente farli implementare un metodo GetPrimaryKeySelectExpression ... ma questo porterebbe a tutti i tipi di possibili problemi (non includendo il fatto che infrange l'intero principio di una classe POCO) oltre ad essere un dolore totale nella b ' hind per l'utente da implementare. Altalene e rotatorie! – GenericTypeTea

1

Come qualcuno ha già fatto notare, si sono essenzialmente cercando di ottenere un tipo anonimo costruito a runtime, che non sta andando a lavorare.

Esiste un'alternativa all'utilizzo di un tipo Anonimo e ottenere comunque ciò che richiedo?

Questo dipende molto da cosa intendi. Le risposte ovvie sono usare costrutti di runtime, come un dizionario, una tupla, ecc. Ma probabilmente ne sei pienamente consapevole, quindi presumo che tu voglia un risultato tipizzato in fase di compilazione con nomi di campi reali, così che qualsiasi l'errato utilizzo di primaryKeys viene rilevato durante la compilazione.

Se è così, allora temo che la tua unica opzione sia generare il codice rilevante prima della compilazione. Non è così male come potrebbe sembrare, ma non è completamente trasparente: quando si modifica lo schema, è necessario rieseguire la generazione del codice in qualche modo.

Nella nostra azienda abbiamo fatto esattamente questo, ispirato allo SubSonic ma trovando che la stessa SubSonic non era esattamente quello che volevamo. Ha funzionato piuttosto bene secondo me.

+0

Non ho alcuna conoscenza delle chiavi primarie in fase di runtime. Il codice sopra è usato da un'applicazione consumante su cui non ho alcun controllo. Vedi la domanda precedente per una spiegazione migliore. – GenericTypeTea

3

Non esiste un modo semplice per eseguire esattamente ciò che si desidera, poiché richiederebbe la creazione di un nuovo tipo in modo dinamico (i tipi anonimi vengono creati dal compilatore quando sono noti staticamente). Anche se è certamente fattibile, probabilmente non è l'opzione più semplice ...

È possibile ottenere un risultato simile usando tuples:

public Expression<Func<TItem, object>> SelectExpression<TItem>(string[] propertyNames) 
{ 
    var properties = propertyNames.Select(name => typeof(TItem).GetProperty(name)).ToArray(); 
    var propertyTypes = properties.Select(p => p.PropertyType).ToArray(); 
    var tupleTypeDefinition = typeof(Tuple).Assembly.GetType("System.Tuple`" + properties.Length); 
    var tupleType = tupleTypeDefinition.MakeGenericType(propertyTypes); 
    var constructor = tupleType.GetConstructor(propertyTypes); 
    var param = Expression.Parameter(typeof(TItem), "item"); 
    var body = Expression.New(constructor, properties.Select(p => Expression.Property(param, p))); 
    var expr = Expression.Lambda<Func<TItem, object>>(body, param); 
    return expr; 
} 
+2

Sfortunatamente questo non sembra funzionare con un IQueryable da Entity Framework. "Solo i costruttori e gli inizializzatori senza parametri sono supportati in LINQ alle entità." – GenericTypeTea

+1

@GenericTypeTea Forse è ovvio, ma ho risolto questo messaggio di errore con la compilazione dell'espressione prima dell'uso, cioè .: var primaryKeyResults = query.Select (primaryKeyExpression.Compile()). ToList(); –

+2

@ Jorr.it Il tuo commento potrebbe essere vero ma la query non verrà eseguita sul database – Tokk

Problemi correlati