Pulling Apart Expression >

c# dapper extension-methods

Pregunta

Estoy ocupado creando métodos de extensión de envoltura sobre Dapper y DapperExtensions . Por el momento estoy intentando agregar el filtrado al método de extensión GetList<T> , similar al método de extensión Where<T> LINQ. He visto esta pregunta, pero parece que no puedo implementar lo que sugirió Marc Gravell porque no hay un tipo EqualsExpression en .NET 4.5. Aquí hay un código de demostración para ayudar con la explicación de mi problema:

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

Prestar especial atención a mi método de extensión Get<T> : cuando paso en p => p.MarketId o p => p.MarketId == marketId , expression.Body es del tipo UnaryExpression . Para este último, expression.Body contiene {Convert((p.MarketId == 2))} .

Intentando

var binaryExpression = expression as BinaryExpression;

devuelve null , lo cual es desafortunado porque hay propiedades Left y Right que podría haber encontrado útiles.

Entonces, ¿alguien sabe cómo lograr lo que quiero? Más adelante en la línea, me gustaría poder elegir el enum del Operator función de la expresión lambda pasada. Cualquier ayuda sería muy apreciada.

Respuesta aceptada

He descubierto cómo lograr lo que quiero.

En resumen:

  1. Necesito un método de extensión que ajuste el método de extensión GetList<T> .
  2. Este último puede tomar un predicado de tipo IFieldPredicate que puedo usar para agregar un filtro a la consulta SQL que se ejecutará. Puedo lograr esto usando Predicates.Field<T>(Expression<Func<T, object>> expression, Operator op, object value) .
  3. El problema radica en transformar una expresión lambda simple t => t.Id == id en parámetros para Predicates.Field<T> . Entonces, conceptualmente, necesito separar la expresión lambda en tres partes: t => t.Id , Operator.Eq e id .

Con la ayuda de @Iridium, @Eduard y @Jon, mi solución final es:

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

Ahora puedo hacer esto:

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

Sé lo frágil que es esto: se romperá si paso algo más complejo, o incluso algo que parezca equivalente, como var matchingPeople = Connection.Get<Person>(p => p.MarketId.Equals(marketId)); . Sin embargo, resuelve el 90% de mis casos, así que estoy contento de dejarlo tal como está.


Respuesta popular

Este es el problema:

Expression<Func<T, object>> expression

Tu función tiene que devolver el object . El tipo de p.MarketId == marketId es bool . Por lo tanto, debe estar encasillado a object , de ahí el Convert .

Si la expresión siempre debe ser un predicado, debe cambiarla a:

Expression<Func<T, bool>> expression

En ese punto, espero que veas la expresión binaria apropiada. Por otro lado, eso no funcionará para p => p.MarketId ...

Para ser sincero, no está muy claro qué significan los parámetros. Parece que quieres dos métodos: uno para un parámetro único que es un predicado y uno para dos parámetros: una proyección y un valor objetivo.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué