В LinqToEntities, Как передать имя столбца динамического c в DbFunctions.Like - PullRequest
2 голосов
/ 18 апреля 2020

У меня есть IQueryable<T> из моего DbSet в Entity Framework. Мне предоставляется «Нечеткая строка поиска» с именем searchText, например:

public List<T> Search<T>(string searchText)
{
    using (var context = ...)
    {
        var baseQuery = context.Set<T>().AsQueryable();
        baseQuery = baseQuery.Where(x =>
            DbFunctions.Like(x.PropertyName, searchText)
            || DbFunctions.Like(x.PropertyTwo, searchText)
            || DbFunctions.Like(x.PropertyThree, searchText)
            || DbFunctio..... etc
        );
        return baseQuery.ToList();
    }
}

Но, учитывая характер c generi, я не знаю, какие свойства имеются в типе. Я могу предоставить абстрактный метод для тех, кто реализует это, что позволяет ему предоставить мне список свойств (или даже PropertyInfo или что-то еще, я могу это выяснить). Но я не знаю, как динамически создать выражение. Это то, что у меня есть до сих пор:

var baseQuery = context.Set<T>().AsQueryable();
var expression = baseQuery.Expression;
var colName = "colName"; // Or names, I can iterate.

var parameter = Expression.Parameter(typeof(T), "x");
var selector = Expression.PropertyOrField(parameter, colName);
expression = Expression.Call(typeof(DbFunctions), nameof(DbFunctions.Like),
    new Type[] { baseQuery.ElementType, selector.Type },
    expression, Expression.Quote(Expression.Lambda(selector, parameter)));

Проблема в том, что ... ну, с самого начала это не работает. Но в основном из-за того, что я не использую searchText где-либо в нем, и не знаю, как его подключить. Я ДУМАЮ, что я близко ... но потратил на него слишком много времени.

1 Ответ

2 голосов
/ 25 апреля 2020

Надеюсь, я правильно понимаю ваш запрос c: если вы хотите построить набор условий LIKE на основе известного типа и списка имен столбцов, вы можете попробовать что-то вроде этого:

static private MethodInfo dbLikeMethod = typeof(DbFunctions).GetMethod(nameof(DbFunctions.Like), BindingFlags.Public | BindingFlags.Static, null, new Type[] {typeof(string), typeof(string)}, null); // I am targeting DbFunctions.Like(string, string). You might want another overload (or even mix them up depending on your inputs)

public List<T> Search<T>(string searchText) where T: class
{

    using (var context = new ...)
    {
        var baseQuery = context.Set<T>().AsQueryable().Where(CreateExpression<T>(searchText));// you could probably find a more elegant way of plugging it into your query
        return baseQuery.ToList();
    }
}

Expression<Func<T, bool>> CreateExpression<T>(string searchText) where T : class
{   
    var cols = new List<string> {
        "PropertyName",
        "PropertyTwo" // i understand you've got a way to figure out which strings you need here
    };

    var parameter = Expression.Parameter(typeof(T), "x");   
    var dbLikeCalls = cols.Select(colName => Expression.Call(dbLikeMethod, Expression.PropertyOrField(parameter, colName), Expression.Constant(searchText))); // for convenience, generate list of DbFunctions.Like(x.<Property>, searchText) expressions here
    var aggregatedCalls = dbLikeCalls.Skip(1).Aggregate((Expression)dbLikeCalls.First(), (accumulate, call) => Expression.OrElse(accumulate, call)); // aggregate the list using || operators: use first item as a seed and keep adding onto it

    return Expression.Lambda<Func<T, bool>>(aggregatedCalls, parameter);
}
...