2012-01-25 1 views
6

J'ai demandé un very similar question hier, mais ce n'est qu'aujourd'hui que j'ai réalisé que la réponse que j'ai acceptée ne résout pas tous mes problèmes. J'ai le code suivant:Comment puis-je créer une multi-propriété dynamique Sélectionnez sur un IEnumerable <T> lors de l'exécution?

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

qui est utilisé comme suit:

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

Cela me permet de retirer les clés primaires d'un IQueryable<TUnknown>. Le problème est que ce code ne fonctionne qu'avec une seule clé primaire et j'ai besoin d'ajouter le support pour plusieurs PK.

Donc, est-il possible d'adapter la méthode SelectExpression ci-dessus pour prendre un IEnumerable<string> (qui est ma liste de noms de propriétés de clé primaire) et la méthode retourne-t-elle une expression qui sélectionne ces clés?

I.e. Compte tenu de ce qui suit:

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

My Select doit effectuer les opérations suivantes (à l'exécution):

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

Le problème est que les types anonymes qui sont les résultats de la sélection statemet sont créés au moment de la compilation .. – m0sa

+1

Y at-il une alternative à l'utilisation d'un type anonyme et encore réaliser ce dont j'ai besoin? Ou est-ce de nouveau à la planche à dessin? – GenericTypeTea

+0

@GenericTypeTea, un Tuple serait-il une option acceptable pour vous? –

Répondre

2

Vous pouvez utiliser Tuple<> car les types anonymes doivent être connus au moment de la compilation:

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

puis:

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

va générer l'expression suivante:

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

Malheureusement, cela ne semble pas fonctionner avec un IQueryable de Entity Framework. "Seuls les constructeurs et les initialiseurs sans paramètre sont pris en charge dans LINQ to Entities." – GenericTypeTea

+0

@GenericTypeTea, oh, eh bien, c'est triste. Dans ce cas, vous pouvez utiliser Reflection.Emit pour générer un type dynamique à l'exécution avec le bon nombre de propriétés, puis générer une expression attribuant ces propriétés. On dirait beaucoup de travail cependant. –

+0

Je commence à me demander s'il vaut mieux forcer l'application consommatrice à implémenter une interface que je spécifie sur tous leurs objets EF POCO. Ensuite, je pourrais juste les faire implémenter une méthode GetPrimaryKeySelectExpression ... mais alors cela conduirait à toutes sortes de problèmes possibles (non compris le fait qu'il casse le principe entier d'une classe POCO) ainsi que d'être une douleur totale dans le b ' hind pour l'utilisateur à mettre en œuvre. Balançoires et ronds-points! – GenericTypeTea

1

Comme quelqu'un l'a déjà dit, vous êtes essentiellement essayer d'obtenir un type anonyme construit à l'exécution, ce qui ne va pas travailler.

Existe-t-il une alternative à l'utilisation d'un type anonyme et toujours réaliser ce dont j'ai besoin?

Cela dépend vraiment de ce que vous voulez dire. Les réponses évidentes sont d'utiliser des constructions d'exécution, comme un dictionnaire, un tuple, etc. Mais vous êtes probablement tout à fait conscient de cela, donc je suppose que vous voulez un résultat à la compilation avec des noms de champs réels, de sorte que mauvaise utilisation de primaryKeys se coince lors de la compilation.

Si c'est le cas, je crains que votre seule option soit de générer le code approprié avant la compilation. Ce n'est pas aussi grave que cela puisse paraître, mais ce n'est pas complètement transparent: lorsque vous modifiez le schéma, vous devez réexécuter la génération de code d'une manière ou d'une autre.

Dans notre entreprise, nous avons fait exactement cela, en s'inspirant de SubSonic mais en trouvant que SubSonic lui-même n'était pas tout à fait ce que nous voulions. Cela a plutôt bien marché à mon avis.

+0

Je n'ai aucune connaissance des clés primaires à l'exécution. Le code ci-dessus est utilisé par une application consommatrice sur laquelle je n'ai aucun contrôle. Voir la question précédente pour une meilleure explication. – GenericTypeTea

3

Il n'existe pas de moyen facile de faire exactement ce que vous voulez, car il vous faudrait créer dynamiquement un nouveau type (les types anonymes sont créés par le compilateur lorsqu'ils sont connus de façon statique). Bien que ce soit faisable, ce n'est probablement pas l'option la plus facile ...

Vous pouvez obtenir un résultat similaire en utilisant 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

Malheureusement, cela ne semble pas fonctionner avec un IQueryable de Entity Framework. "Seuls les constructeurs et les initialiseurs sans paramètre sont pris en charge dans LINQ to Entities." – GenericTypeTea

+1

@GenericTypeTea Peut-être que c'est évident, mais j'ai résolu ce message d'erreur en compilant l'expression avant utilisation, c'est-à-dire: var primaryKeyResults = query.Select (primaryKeyExpression.Compile()). ToList(); –

+2

@ Jorr.it Votre commentaire peut être vrai, mais la requête ne sera pas exécutée sur la base de données – Tokk

Questions connexes