/ / Абстракция на изразяването - c #, ламбда, израз, абстракция

Абстракция на изрази - c #, ламбда, израз, абстракция

Възможно ли е да се избегне дублирането на този метод за всяко стринг поле в модела, който искам да проверя за съвпадение? ако MyModel се абстрахира тогава очевидно 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 заявки.

Отговори:

1 за отговор № 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();
}

1 за отговор № 2

Можете да предоставите функция за избор на свойство като параметър. Например:

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 за отговор № 3

Можете да определите селектор за полето за целта:

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