Utilisation d’expressions pour construire Array.Contains for Entity Framework

c# expression-trees

Question

Je veux avoir un champ variable dans Where-Contains et Select. "field_a" est le type que je veux être variable (parfois, je veux field_b ou _c; ce sont des chaînes). Le code ci-dessous construit correctement le Select as Select(x => x.field_a) . Comment puis-je obtenir la deuxième partie de la clause Where à dire && targetCodes.Contains(x.field_a) ? [ db est un DbContext , itemsArray est un tableau d'objets possédant une propriété de chaîne appelée Code .]

    using (var db = dbFactory.CreateInstance())
    {
        var parameter = Expression.Parameter(typeof(myTable), "x");
        var field = Expression.Property(parameter, "field_a");
        var selector = Expression.Lambda(field, parameter);
        var targetCodes = itemsArray.Select(i => i.Code).ToArray();
        var query = db.myTables
            .Where(x => x.companyId == 1 && targetCodes.Contains(x.field_a))
            .Select((Expression<Func<myTable, string>>)selector);
        return await query.ToArrayAsync();
    }

Réponse acceptée

La partie la plus difficile est MethodInfo de trouver MethodInfo de la méthode .Contains() . Vous pouvez utiliser typeof(IEnumerable<string>).GetMethod(...).Where(...) , mais il est généralement difficile de le faire correctement pour une méthode générique avec plusieurs surcharges. C'est un petit truc qui utilise le compilateur C # pour trouver la surcharge correcte pour vous en créant une expression temporaire:

Expression<Func<IEnumerable<string>, bool>> containsExpr = (IEnumerable<string> q) => q.Contains((string)null);
var containsMethod = (containsExpr.Body as MethodCallExpression).Method;
// containsMethod should resolve to this overload:
// System.Linq.Enumerable.Contains<string>(IEnumerable<string>, string)

Le reste du programme génère simplement une expression en appelant les méthodes Expression.XYZ() appropriées:

var companyIdEquals1 = Expression.Equal(
    Expression.Property(parameter, nameof(myTable.companyId)),
    Expression.Constant(1));

var targetCodesContains = Expression.Call(
    containsMethod,
    Expression.Constant(targetCodes),
    field/*reuses expression you already have*/);

var andExpr = Expression.And(companyIdEquals1, targetCodesContains);
var whereExpr = (Expression<Func<myTable, bool>>)Expression.Lambda(andExpr, parameter);

var query = db//.myTables
    .Where(whereExpr)
    .Select((Expression<Func<myTable, string>>)selector);

Réponse populaire

Il y a plusieurs façons de le faire. Dans ce cas particulier, vous n'avez même pas besoin de traiter d'expressions, car vous pouvez utiliser simplement la chaîne Where après la Select (les conditions chaînées Where sont combinées avec && dans la requête finale):

var query = db.myTables
    .Where(x => x.companyId == 1)
    .Select((Expression<Func<myTable, string>>)selector)
    .Where(v => targetCodes.Contains(v));

Mais pour répondre à votre question, comment construire l'expression représentant targetCodes.Contains({field}) , puisque l'appel réel (sans le sucre de la méthode d'extension) dont vous avez besoin est Enumerable.Contains<string>(targetCodes, {field}) , la plus simple consiste à utiliser la surcharge de méthode Expression.Call suivante spécialement fournie pour les méthodes "appelantes" statiques (génériques et non génériques):

public static MethodCallExpression Call(
    Type type,
    string methodName,
    Type[] typeArguments,
    params Expression[] arguments
);

Dans votre cas, il pourrait être utilisé comme ceci:

var containsCall = Expression.Call(
    typeof(Enumerable), nameof(Enumerable.Contains), new [] { typeof(string) },
    Expression.Constant(targetCodes), field);



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi