En tirant parti de l&#39;expression <Func<T, object> &gt;

c# dapper extension-methods

Question

Je suis en train de créer des méthodes d'extension de wrapper sur Dapper et DapperExtensions . En ce moment, j'essaie d'ajouter un filtrage à la méthode d'extension GetList<T> , similaire à la méthode d'extension Where<T> de LINQ. J'ai vu cette question mais il semble que je ne puisse pas implémenter ce que Marc Gravell a suggéré car il n'y a pas de type EqualsExpression dans .NET 4.5. Voici un code de démonstration pour vous aider à expliquer mon problème:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq.Expressions;
using DapperExtensions;

namespace Dapper.Extensions.Demo
{
    public class Program
    {
        private static readonly string ConnectionString = ConfigurationManager.ConnectionStrings["DapperDbContext"].ConnectionString;
        public static IDbConnection Connection { get { return new SqlConnection(ConnectionString); } }

        public static void Main(string[] args)
        {
            const int marketId = 2;
            var matchingPeople = Connection.Get<Person>(p => p.MarketId, marketId); // This works

            // Below is a LambdaExpression. expression.Body is, bizarrely, a UnaryExpression with a Convert
            //var matchingPeople = Connection.Get<Person>(p => p.MarketId == marketId); // Does not work

            foreach (var person in matchingPeople)
            {
                Console.WriteLine(person);
            }

            if (Debugger.IsAttached)
                Console.ReadLine();
        }
    }

    public static class SqlConnectionExtensions
    {
        public static IEnumerable<T> Get<T>(this IDbConnection connection, Expression<Func<T, object>> expression, object value = null) where T : class
        {
            using (connection)
            {
                connection.Open();

                // I want to be able to pass in: t => t.Id == id then:
                // Expression<Func<T, object>> expressionOnLeftOfFilterClause = t => t.Id;
                // string operator = "==";
                // object valueFromLambda = id;
                // and call Predicates.Field(expressionOnLeftOfFilterClause, Operator.Eq, valueFromLambda)

                var predicate = Predicates.Field(expression, Operator.Eq, value);
                var entities = connection.GetList<T>(predicate, commandTimeout: 30);
                connection.Close();
                return entities;
            }
        }
    }

    public class Person
    {
        public int Id { get; set; }

        public string FirstName { get; set; }

        public string Surname { get; set; }

        public int MarketId { get; set; }

        public override string ToString()
        {
            return string.Format("{0}: {1}, {2} - MarketId: {3}", Id, Surname, FirstName, MarketId);
        }
    }
}

Prêter une attention particulière à ma méthode d’extension Get<T> : lorsque je passe soit p => p.MarketId ou p => p.MarketId == marketId , expression.Body est de type UnaryExpression . Pour ce dernier, expression.Body contient en fait {Convert((p.MarketId == 2))} .

Essayant

var binaryExpression = expression as BinaryExpression;

renvoie null , ce qui est dommage car il y a des propriétés Left et Right que j'aurais pu trouver utiles.

Alors, est-ce que quelqu'un sait comment atteindre ce que je veux? Plus loin dans la ligne, j'aimerais pouvoir sélectionner l'énumération de l' Operator basée sur l'expression lambda transmise. Toute aide serait très appréciée.

Réponse acceptée

J'ai compris comment réaliser ce que je veux.

En résumé:

  1. J'ai besoin d'une méthode d'extension qui englobe la méthode d'extension GetList<T> de GetList<T> .
  2. Ce dernier peut prendre un prédicat de type IFieldPredicate que je peux utiliser pour ajouter un filtre à la requête SQL à exécuter. Je peux y parvenir en utilisant Predicates.Field<T>(Expression<Func<T, object>> expression, Operator op, object value) .
  3. Le problème réside dans la transformation d'une expression lambda simple t => t.Id == id en paramètres pour Predicates.Field<T> . Donc, conceptuellement, je dois séparer l'expression lambda en trois parties: t => t.Id , Operator.Eq et id .

Avec l'aide de @Iridium, @Eduard et @Jon, ma solution finale est la suivante:

public static class SqlConnectionExtensions
{
    public static IEnumerable<T> Get<T>(this IDbConnection connection, Expression<Func<T, object>> expression) where T : class
    {
        using (connection)
        {
            connection.Open();

            var binaryExpression = (BinaryExpression)((UnaryExpression) expression.Body).Operand;

            var left = Expression.Lambda<Func<T, object>>(Expression.Convert(binaryExpression.Left, typeof(object)), expression.Parameters[0]);
            var right = binaryExpression.Right.GetType().GetProperty("Value").GetValue(binaryExpression.Right);
            var theOperator = DetermineOperator(binaryExpression);

            var predicate = Predicates.Field(left, theOperator, right);
            var entities = connection.GetList<T>(predicate, commandTimeout: 30);

            connection.Close();
            return entities;
        }
    }

    private static Operator DetermineOperator(Expression binaryExpression)
    {
        switch (binaryExpression.NodeType)
        {
            case ExpressionType.Equal:
                return Operator.Eq;
            case ExpressionType.GreaterThan:
                return Operator.Gt;
            case ExpressionType.GreaterThanOrEqual:
                return Operator.Ge;
            case ExpressionType.LessThan:
                return Operator.Lt;
            case ExpressionType.LessThanOrEqual:
                return Operator.Le;
            default:
                return Operator.Eq;
        }
    }
}

Je peux maintenant faire ceci:

var matchingPeople = Connection.Get<Person>(p => p.MarketId == marketId);

Je sais à quel point c'est fragile - ça va se casser si je passe quelque chose de plus complexe, ou même quelque chose qui semble équivalent, comme var matchingPeople = Connection.Get<Person>(p => p.MarketId.Equals(marketId)); . Il résout 90% de mes cas, alors je me contente de le laisser tel quel.


Réponse populaire

C'est le problème:

Expression<Func<T, object>> expression

Votre fonction doit renvoyer un object . Le type de p.MarketId == marketId est bool . Il faut donc l'encadrer pour object , d'où le Convert .

Si l'expression est toujours destinée à être un prédicat, vous devez la changer en:

Expression<Func<T, bool>> expression

À ce stade, je m'attendrais à ce que vous voyiez l'expression binaire appropriée. Par contre, cela ne fonctionnera pas pour p => p.MarketId ...

Pour être honnête, la signification des paramètres n’est pas très claire. On a l'impression que vous voulez peut-être deux méthodes - une pour un seul paramètre qui est un prédicat et une pour deux paramètres: une projection et une valeur cible.




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