Подстановочные знаки Entity Framework и Linq

Можно ли создать действительный запрос Linq, содержащий подстановочные знаки?

Я видел различные ответы на этот вопрос, в которых предлагается использовать:

.Where(entity => entity.Name.Contains("FooBar"))
.Where(entity => entity.Name.EndsWith("Bar")) 
.Where(entity => entity.Name.StartsWith("Foo"))

ИЛИ создание RawSql:

var commandText =
    @"SELECT field
    FROM     table
    WHERE    field LIKE @search";

var query = new ObjectQuery<Profile>(commandText, context);
query.Parameters.Add(new ObjectParameter("search", wildcardSearch));

Первое решение не сработало бы, если бы подстановочный знак не был в начале или конце строки, например searchTerm = "Foo%Bar".

Второе решение, использующее RawSql, меня не устраивает и кажется дешевым выходом. Но это действительно работает.

Третий вариант, который мне еще предстоит опробовать, - это создать что-то, что может анализировать поисковый запрос и создавать действительный запрос Linq, что @Slauma предпринял в ссылке 2 ниже. Но это все равно не сработало бы, если бы подстановочный знак не был в начале или в конце поискового запроса.

Итак, вопрос: можно ли создать действительный запрос Linq, содержащий символы подстановки?

РЕДАКТИРОВАТЬ: Стоит упомянуть, что в этом случае я использую компоненты доступа к данным Oracle (ODAC / ODP), но я не думаю, что в этом случае это имеет большое значение.

ссылки:

1. похожие запросы в Entity Framework

2. точный поиск и поиск с подстановочными знаками в зависимости от поискового запроса

3. Использование RawSql


person philreed    schedule 18.07.2013    source источник
comment
Я знаю, что это старый вопрос, но вы можете использовать идею из ответа здесь: stackoverflow.com/questions/1040380/wildcard-search-for-linq/ Нет возможности добавить подстановочные знаки в строку, но вы можете создать запрос LIKE, который хотите.   -  person Ruard van Elburg    schedule 18.02.2017


Ответы (3)


Если вы используете файл EDMX в качестве основы для своей модели сущности, возможно, вы могли бы попробовать создать Функция концептуальной модели, которая затем выполняет LIKE в SQL. Я не уверен, сработает ли это для Oracle. После этого вы сможете сделать что-то вроде следующего:

.Where(entity => Like(entity.Name, "Foo%Bar"))
person Adam Gritt    schedule 18.07.2013
comment
Я использую Code First без EDMX для существующей базы данных, но я изучу его, спасибо. - person philreed; 18.07.2013
comment
Я изо всех сил пытаюсь найти способ сделать это без EDMX, но хорошая идея. - person philreed; 18.07.2013
comment
@philreed Я попытался найти информацию о том, как сделать то же самое с Code First, но тоже ничего не нашел. Единственная ссылка, которую я нашел, относится к 2011 году на форуме Microsoft, где это была запрошенная функция, но, как известно, в то время она не поддерживалась. - person Adam Gritt; 18.07.2013

используйте SqlFunctions.PatIndex, это будет выглядеть так:

.Where(entity => SqlFunctions.PatIndex("Foo%Bar", entity.Name) > 0)
person BlackICE    schedule 18.07.2013
comment
извините, только что увидел, что вы используете Oracle, это может иметь значение, не уверен, что patindex сработает для этого. - person BlackICE; 18.07.2013
comment
Да, я только что обновил вопрос, чтобы показать, что использую Oracle, буду удивлен, если не будет эквивалента. Я сейчас занимаюсь этим, спасибо. - person philreed; 18.07.2013
comment
Я не могу найти версию PatIndex для Oracle, но она заставила меня задуматься о потенциале string.IndexOfAny(searchTerm.Split('%')). Потребуется немного изменить, чтобы убедиться, что условия разделения находятся в правильной последовательности. - person philreed; 18.07.2013
comment
Я очень сомневаюсь, что поставщик EF собирается это реализовать. - person BlackICE; 18.07.2013

Я нашел отличное решение для Oracle. Это часть другого ответа здесь и часть, написанная мной.

    public static class LinqExtensions
{
    public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, String Name, String value)
    {
        Type model = typeof(T);
        ParameterExpression param = Expression.Parameter(typeof(T), "m");
        PropertyInfo key = model.GetProperty(Name);
        MemberExpression lhs = Expression.MakeMemberAccess(param, key);
        Expression<Func<T, String>> lambda = Expression.Lambda<Func<T, String>>(lhs, param);

        return source.Where(BuildLikeExpression(lambda, value));
    }
    public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, Expression<Func<T, String>> valueSelector, String value)
    {
        return source.Where(BuildLikeExpression(valueSelector, value));
    }
    public static Expression<Func<T, Boolean>> BuildLikeExpression<T>(Expression<Func<T, String>> valueSelector, String value)
    {
        if (valueSelector == null)
            throw new ArgumentNullException("valueSelector");
        value = value.Replace("*", "%");        // this allows us to use '%' or '*' for our wildcard
        if (value.Trim('%').Contains("%"))
        {
            Expression myBody = null;
            ParsedLike myParse = Parse(value);
            Type stringType = typeof(String);
            if(myParse.startwith!= null)
            {
                myBody = Expression.Call(valueSelector.Body, stringType.GetMethod("StartsWith", new Type[] { stringType }), Expression.Constant(myParse.startwith));
            }
            foreach (String contains in myParse.contains)
            {
                if (myBody == null)
                {
                    myBody = Expression.Call(valueSelector.Body, stringType.GetMethod("Contains", new Type[] { stringType }), Expression.Constant(contains));
                }
                else
                {
                    Expression myInner = Expression.Call(valueSelector.Body, stringType.GetMethod("Contains", new Type[] { stringType }), Expression.Constant(contains));
                    myBody = Expression.And(myBody, myInner);
                }
            }
            if (myParse.endwith != null)
            {
                if (myBody == null)
                {
                    myBody = Expression.Call(valueSelector.Body, stringType.GetMethod("EndsWith", new Type[] { stringType }), Expression.Constant(myParse.endwith));
                }
                else
                {
                    Expression myInner = Expression.Call(valueSelector.Body, stringType.GetMethod("EndsWith", new Type[] { stringType }), Expression.Constant(myParse.endwith));
                    myBody = Expression.And(myBody, myInner);
                }
            }
            return Expression.Lambda<Func<T, Boolean>>(myBody, valueSelector.Parameters.Single());
        }
        else
        {
            Expression myBody = Expression.Call(valueSelector.Body, GetLikeMethod(value), Expression.Constant(value.Trim('%')));
            return Expression.Lambda<Func<T, Boolean>>(myBody, valueSelector.Parameters.Single());
        }
    }
    private static MethodInfo GetLikeMethod(String value)
    {
        Type stringType = typeof(String);

        if (value.EndsWith("%") && value.StartsWith("%"))
        {
            return stringType.GetMethod("Contains", new Type[] { stringType });
        }
        else if (value.EndsWith("%"))
        {
            return stringType.GetMethod("StartsWith", new Type[] { stringType });
        }
        else
        {
            return stringType.GetMethod("EndsWith", new Type[] { stringType });
        }
    }
    private class ParsedLike
    {
        public String startwith { get; set; }
        public String endwith { get; set; }
        public String[] contains { get; set; }
    }
    private static ParsedLike Parse(String inValue)
    {
        ParsedLike myParse = new ParsedLike();
        String work = inValue;
        Int32 loc;
        if (!work.StartsWith("%"))
        {
            work = work.TrimStart('%');
            loc = work.IndexOf("%");
            myParse.startwith = work.Substring(0, loc);
            work = work.Substring(loc + 1);
        }
        if (!work.EndsWith("%"))
        {
            loc = work.LastIndexOf('%');
            myParse.endwith = work.Substring(loc + 1);
            if (loc == -1)
                work = String.Empty;
            else
                work = work.Substring(0, loc);
        }
        myParse.contains = work.Split(new[] { '%' }, StringSplitOptions.RemoveEmptyEntries);
        return myParse;
    }
}
person Brian Kitt    schedule 26.11.2014