可以使用反射与LINQ到实体吗?

11

我想通过创建扩展方法来泛化处理过滤器,以使我的代码更加简洁。

以下是我想要清理的代码。

var queryResult = (from r in dc.Retailers select r);
if (!string.IsNullOrEmpty(firstName))
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(firstName.Trim(), ex.FirstName.Trim()) > 0);
if (!string.IsNullOrEmpty(lastName))
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(lastName.Trim(), ex.LastName.Trim()) > 0);
if (!string.IsNullOrEmpty(companyName))
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(companyName.Trim(), ex.CompanyName.Trim()) > 0);
if (!string.IsNullOrEmpty(phone))
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(phone.Trim(), ex.Phone.Trim()) > 0);
if (!string.IsNullOrEmpty(email))
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(email.Trim(), ex.Email.Trim()) > 0);
if (!string.IsNullOrEmpty(city))
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(city.Trim(), ex.City.Trim()) > 0);
if (!string.IsNullOrEmpty(zip))
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(zip.Trim(), ex.Zip.Trim()) > 0);
if (!string.IsNullOrEmpty(country))
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(country.Trim(), ex.Country.Trim()) > 0);
if (!string.IsNullOrEmpty(state))
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(state.Trim(), ex.State.Trim()) > 0);

这段代码显然非常重复。因此我尝试创建一个使用反射过滤属性的扩展方法,以下是该方法。

public static void FilterByValue<T>(this IQueryable<T> obj, string propertyName, string propertyValue)
{
    if (!string.IsNullOrEmpty(propertyValue))
    {
        obj =
            obj.Where(
                ex =>
                    SqlFunctions.PatIndex(propertyValue.Trim(), (string)typeof(T).GetProperty(propertyName,
                        BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase).GetValue(ex)) > 0
                    );
    }
}

它应该被这样称呼:

var queryResult = (from r in dc.Retailers select r);
queryResult.FilterByValue("firstname", firstName);

但是,当linq执行时,我会收到一个错误提示,指出“GetValue”在linq to entity中无法识别。

那么,有没有其他方法可以简化这个问题,或者我必须将其保留为丑陋的形式?

1个回答

17
从技术上讲,是可以的,但您需要自己构建“Expression”并将其传递给“Where”。
话虽如此,与其将属性作为字符串值接受,您应该考虑接受一个“Expression<Func<T,string>>”作为参数,以便在编译时支持验证所选对象是否有效。
我们将从表示通用部分的表达式开始;它将表示具有两个参数(对象和给定属性的值)的函数。然后,我们可以将所有第二个参数的实例替换为我们在实际方法参数中定义的属性选择器。
public static IQueryable<T> FilterByValue<T>(
    this IQueryable<T> obj,
    Expression<Func<T, string>> propertySelector,
    string propertyValue)
{
    if (!string.IsNullOrEmpty(propertyValue))
    {
        Expression<Func<T, string, bool>> expression =
            (ex, value) => SqlFunctions.PatIndex(propertyValue.Trim(),
                value.Trim()) > 0;

        var newSelector = propertySelector.Body.Replace(
            propertySelector.Parameters[0],
            expression.Parameters[0]);

        var body = expression.Body.Replace(expression.Parameters[1], 
            newSelector);
        var lambda = Expression.Lambda<Func<T, bool>>(
            body, expression.Parameters[0]);

        return obj.Where(lambda);
    }
    else
        return obj;
}

这种方法使用一个函数来替换给定表达式中所有出现的一个表达式。其实现如下:

public class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

如果你真的希望将属性名称作为字符串接受,那么只需将newSelector的定义替换为以下内容:
var newSelector = Expression.Property(expression.Parameters[0], propertyName);

1
好的,做到了。谢谢。现在我只需要再多读几遍你的答案才能理解它。 - Smeegs
1
@Smeegs 可以通过使用一些实际值来调试,查看所创建的表达式(甚至是我没有存储在变量中的一些中间值)。 这有助于形象化理解。 表达式通常具有合理的“ToString”实现。 - Servy
你正在使用哪些程序集/命名空间?我看到 SqlFunctions 同时存在于 EntityFramework.SqlServerSystem.Data.Entity 中... 我假设它们是不同的,对吗? - David
@user1467396,我只是在那个部分引用了OP的代码。我不知道他使用的是哪一个。那个表达式的转换是我为这个答案编写的代码。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接