表达抽象

时间:2014-01-11 15:47:39

标签: c# lambda expression abstraction

是否可以避免为我想检查匹配的模型中的每个字符串字段重复此方法?如果MyModel被抽象,那么lambda表达式中的MyModelField显然不再被识别,所以我想也许某种反射可以按名称访问该字段?

private Expression<Func<MyModel, bool>> MatchMyModelFieldByStrategy(SearchItem searchItem)
{
    var searchItemKey = searchItem.Value.ToLower();
    Expression<Func<MyModel, bool>> defaultExp = s => s.MyModelField.ToLower().Contains(searchItemKey);
    switch (searchItem.SearchStrategy)
    {
        case StrStrategy.Contains:
            return defaultExp;
        case StrStrategy.StartsWith:
            return s => s.MyModelField.ToLower().StartsWith(searchItemKey);
        case StrStrategy.EndsWith:
            return s => s.MyModelField.ToLower().EndsWith(searchItemKey);
        case StrStrategy.Equals:
            return s => s.MyModelField.ToLower().Equals(searchItemKey);
    }
    return defaultStrat;
}

修改

我需要调用动态构建谓词的方法来与Entity Framework查询一起使用。

3 个答案:

答案 0 :(得分:1)

您可以提供属性选择器功能作为参数。例如:

private Expression<Func<MyModel, bool>> MatchMyModelFieldByStrategy(SearchItem searchItem, Func<MyModel, string> propertySelector)
{
    var searchItemKey = searchItem.Value.ToLower();
    Expression<Func<MyModel, bool>> defaultExp = s => propertySelector.Invoke(s).ToLower().Contains(searchItemKey);
    switch (searchItem.SearchStrategy)
    {
        case StrStrategy.Contains:
            return defaultExp;
        case StrStrategy.StartsWith:
            return s => propertySelector.Invoke(s).ToLower().StartsWith(searchItemKey);
        case StrStrategy.EndsWith:
            return s => propertySelector.Invoke(s).ToLower().EndsWith(searchItemKey);
        case StrStrategy.Equals:
            return s => propertySelector.Invoke(s).ToLower().Equals(searchItemKey);
    }
    return defaultStrat;
}

可以这样使用:

var matchExpression = MatchMyModelFieldByStrategy(someSearchItem, model => model.MyModelField);

答案 1 :(得分:1)

您可以为目标字段定义选择器:

private Expression<Func<MyModel, bool>> MatchMyModelFieldByStrategy(SearchItem searchItem, Func<MyModel, String> selector)
{
    var searchItemKey = searchItem.Value.ToLower();
    Expression<Func<MyModel, bool>> defaultExp = s =>  selector(s).ToLower().Contains(searchItemKey);
    switch (searchItem.SearchStrategy)
    {
        case StrStrategy.Contains:
            return defaultExp;
        case StrStrategy.StartsWith:
            return s =>  selector(s).ToLower().StartsWith(searchItemKey);
        case StrStrategy.EndsWith:
            return s =>  selector(s).ToLower().EndsWith(searchItemKey);
        case StrStrategy.Equals:
            return s =>  selector(s).ToLower().Equals(searchItemKey);
    }
    return defaultStrat;
}

并以这种方式使用它:

MatchMyModelFieldByStrategy(searchItem, x=>x.MyModelField);

答案 2 :(得分:1)

如果您计划将MatchMyModelFieldByStrategy的结果与Entity Framework或LINQ2SQL一起使用,则selector必须是表达式而不是委托,因为基础LINQ提供程序在构建实体命令文本期间将无法识别委托。

因此,你必须自己构建表达式,如下所示:

(假设您有类似的类型:)

enum SearchStrategy
{
    Contains,
    StartsWith,
    EndsWith,
    Equals
}

class SearchItem
{
    public SearchStrategy SearchStrategy { get; set; }
    public string Value { get; set; }
}

以下是构建过滤表达式的代码:

static class QueryBuilder
{
    private static readonly Lazy<MethodInfo> toLowerMethodInfo;
    private static readonly Dictionary<SearchStrategy, Lazy<MethodInfo>> searchStrategyToMethodInfoMap;

    static QueryBuilder()
    {
        toLowerMethodInfo = new Lazy<MethodInfo>(() => typeof(string).GetMethod("ToLower", new Type[0]));

        searchStrategyToMethodInfoMap = new Dictionary<SearchStrategy, Lazy<MethodInfo>>
        {
            { 
                SearchStrategy.Contains, 
                new Lazy<MethodInfo>(() => typeof(string).GetMethod("Contains", new[] { typeof(string) })) 
            },
            { 
                SearchStrategy.StartsWith, 
                new Lazy<MethodInfo>(() => typeof(string).GetMethod("StartsWith", new[] { typeof(string) })) 
            },
            { 
                SearchStrategy.EndsWith, 
                new Lazy<MethodInfo>(() => typeof(string).GetMethod("EndsWith", new[] { typeof(string) })) 
            },
            { 
                SearchStrategy.Equals, 
                new Lazy<MethodInfo>(() => typeof(string).GetMethod("Equals", new[] { typeof(string) })) 
            },
        };
    }

    public static Expression<Func<T, bool>> MatchMyModelFieldByStrategy<T>(SearchItem searchItem, Expression<Func<T, string>> selector)
    {
        // "doe"
        var searchItemKey = searchItem.Value.ToLower();
        // _.Name.ToLower()
        var toLowerCallExpr = Expression.Call(selector.Body, toLowerMethodInfo.Value);
        // a method we shall use for searching
        var searchMethodInfo = searchStrategyToMethodInfoMap[searchItem.SearchStrategy].Value;

        // _ => _.Name.ToLower().SomeSearchMethod("doe")
        return Expression.Lambda<Func<T, bool>>(
            Expression.Call(toLowerCallExpr, searchMethodInfo, Expression.Constant(searchItemKey)), 
            selector.Parameters);
    }
}

我添加了一点懒惰来缓存反射结果,因为对于每个MatchMyModelFieldByStrategy调用它们都是相同的。

现在测试实体类型:

class MyEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

...和示例代码:

    static void Main(string[] args)
    {
        Console.WriteLine(QueryBuilder.MatchMyModelFieldByStrategy<MyEntity>(
            new SearchItem { SearchStrategy = SearchStrategy.Contains, Value = "doe" }, _ => _.Name));
        Console.WriteLine(QueryBuilder.MatchMyModelFieldByStrategy<MyEntity>(
            new SearchItem { SearchStrategy = SearchStrategy.StartsWith, Value = "doe" }, _ => _.Name));
        Console.WriteLine(QueryBuilder.MatchMyModelFieldByStrategy<MyEntity>(
            new SearchItem { SearchStrategy = SearchStrategy.EndsWith, Value = "doe" }, _ => _.Name));
        Console.WriteLine(QueryBuilder.MatchMyModelFieldByStrategy<MyEntity>(
            new SearchItem { SearchStrategy = SearchStrategy.Equals, Value = "doe" }, _ => _.Name));

        Console.ReadLine();
    }