我正忙于在Dapper和DapperExtensions之上创建包装器扩展方法。目前我正在尝试向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.MarketId
或p => p.MarketId == marketId
, expression.Body
的类型为UnaryExpression
。对于后者, expression.Body
实际上包含{Convert((p.MarketId == 2))}
。
尝试
var binaryExpression = expression as BinaryExpression;
返回null
,这是不幸的,因为我可以找到有用的Left
和Right
属性。
那么,有谁知道如何实现我想要的?我希望能够根据传入的lambda表达式选择Operator
枚举。我们非常感谢任何帮助。
我已经想出如何实现我想要的。
综上所述:
GetList<T>
扩展方法的扩展方法。 IFieldPredicate
类型的谓词,我可以使用它来为要执行的SQL查询添加过滤器。我可以通过使用Predicates.Field<T>(Expression<Func<T, object>> expression, Operator op, object value)
来实现这一点。 t => t.Id == id
转换为Predicates.Field<T>
参数。因此,从概念上讲,我需要将lambda表达式拆分为三个部分: t => t.Id
, Operator.Eq
和id
。 在@ 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%的情况,所以我满足于保持原样。
这就是问题:
Expression<Func<T, object>> expression
你的函数必须返回object
。 p.MarketId == marketId
的类型是bool
。因此需要将它装箱到object
,因此Convert
。
如果表达式始终是谓词,则应将其更改为:
Expression<Func<T, bool>> expression
那时,我希望你能看到合适的二进制表达式。另一方面,那对p => p.MarketId
......
说实话,这些参数意味着什么并不是很清楚。感觉就像你想要两个方法 - 一个用于单个参数是一个谓词,一个用于两个参数:一个投影和一个目标值。