7

我正忙于在DapperDapperExtensions之上创建包装扩展方法。目前我正在尝试将过滤添加到GetList<T>扩展方法中,类似于 LINQ 的Where<T>扩展方法。我已经看到了这个问题,但似乎我无法实现Marc Gravell的建议,因为 .NET 4.5 中没有类型EqualsExpression。这是一些演示代码,可帮助解释我的问题:

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

特别注意我的Get<T>扩展方法:当我传入p => p.MarketIdorp => p.MarketId == marketId时,expression.Body类型为UnaryExpression. 对于后者,expression.Body实际上包含{Convert((p.MarketId == 2))}.

尝试

var binaryExpression = expression as BinaryExpression;

Returns null,这很不幸,因为有一些我可能会发现有用的属性LeftRight

那么,有谁知道如何实现我想要的?更进一步,我希望能够Operator根据传入的 lambda 表达式选择枚举。任何帮助将不胜感激。

4

2 回答 2

5

我已经想出了如何实现我想要的。

总之:

  1. 我需要一个扩展方法来包装 DapperExtension 的GetList<T>扩展方法。
  2. 后者可能会接受一个类型的谓词IFieldPredicate,我可以使用它来将过滤器添加到要执行的 SQL 查询中。我可以通过使用Predicates.Field<T>(Expression<Func<T, object>> expression, Operator op, object value).
  3. 问题在于将简单的 lambda 表达式t => t.Id == id转换为Predicates.Field<T>. 因此,从概念上讲,我需要将 lambda 表达式分成三个部分:t => t.IdOperator.Eqid

在@Iridium、@Eduard 和@Jon 的帮助下,我的最终解决方案是:

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

我现在可以这样做:

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

我知道这有多脆弱——如果我传入更复杂的东西,甚至是看起来等价的东西,比如var matchingPeople = Connection.Get<Person>(p => p.MarketId.Equals(marketId));. 它确实解决了我 90% 的问题,所以我很乐意让它保持原样。

于 2013-03-01T13:56:41.423 回答
3

这就是问题:

Expression<Func<T, object>> expression

你的函数必须返回object。的类型p.MarketId == marketIdbool。因此它需要被装箱到object,因此Convert.

如果表达式始终是谓词,则应将其更改为:

Expression<Func<T, bool>> expression

那时,我希望您能看到适当的二进制表达式。另一方面,那将不适用于p => p.MarketId...

老实说,这些参数的含义并不是很清楚。感觉就像您可能需要两种方法 - 一种用于作为谓词的单个参数,另一种用于两个参数:投影和目标值。

于 2013-03-01T09:55:03.893 回答