SQL LINQ вычисляет, что строка, разделенная запятыми, содержит значение

#c# #linq #entity-framework-core

#c# #linq #сущность-фреймворк-ядро

Вопрос:

Я использую EFCore Linq и сталкиваюсь с проблемой. У меня есть таблица, в которой хранится строка, разделенная запятыми, и мне нужно использовать ее в предложении where для фильтрации только тех, которые содержат определенное значение. Вот мой Linq:

 var volunteers = context.Volunteers
               .Where(x => x.StatusId == 1
                     amp;amp; x.RoleIds.Split(',', StringSplitOptions.None).Contains("1")
                ).ToList();
  

Столбец является идентификатором роли. Ошибка, которую я получил, заключается в том, что запрос Linq не может быть переведен. Что мне нужно здесь сделать? Спасибо!

Комментарии:

1. Проблема в том, что, вероятно, нет функций SQL, которые могли бы разделить строку и / или проверить, содержит ли массив строк строку, и / или EFCore не реализовал сопоставление между этими функциями SQL и функциями C #. Вы могли бы попробовать x.RoleIds.StartsWith("1,") || x.RoleIds.Contains(",1,") || x.RoleIds.EndsWith(",1") || x.RoleIds == "1") , что должно быть таким же, проверив, равен ли первый элемент 1, внутренний элемент 1, последний элемент 1 или есть только один элемент 1.

2. Спасибо, я сделал это, и это работает: (x.RoleIds «,»).Содержит («1,»)

3. Пожалуйста, имейте в виду, что ваша сокращенная форма также будет содержать 11, 21 и так далее.

4. Спасибо за напоминание ckuri. Фактическое число намного больше и уникально. Должно быть в порядке. Но это хороший момент!

5. Также обратите внимание, что "1," это не будет соответствовать, если идентификатор роли является последним в строке, следовательно, необходимы все четыре теста.

Ответ №1:

Если вас устраивает использование LinqKit (или создание собственной ориентированной версии), вы можете создавать расширения для обработки тестирования за вас:

 public static class IQueryableExt { // using LINQKit
    // string fieldExpr(T row) - function returning multiple value string field to test
    // delimiter - string separator between values in test field
    // value - string value to find in values of test field
    // dbq.Where(r => fieldExpr(r).Split(delimiter).Contains(value))
    public static IQueryable<T> WhereSplitContains<T>(this IQueryable<T> dbq, Expression<Func<T, string>> fieldExpr, string delimiter, string value) {
        var pred = PredicateBuilder.New<T>(r => fieldExpr.Invoke(r) == value);
        pred = pred.Or(r => fieldExpr.Invoke(r).StartsWith(value   delimiter));
        pred = pred.Or(r => fieldExpr.Invoke(r).EndsWith(delimiter   value));
        pred = pred.Or(r => fieldExpr.Invoke(r).Contains(delimiter   value   delimiter));

        return dbq.Where((Expression<Func<T, bool>>)pred.Expand());
    }

    // values - string values, one of which to find in values of test field
    // string fieldExpr(T row) - function returning multiple value string field to test
    // delimiter - string separator between values in test field
    // dbq.Where(r => values.Any(value => fieldExpr(r).Split(delimiter).Contains(value)))
    public static IQueryable<T> WhereAnySplitContains<T>(this IQueryable<T> dbq, IEnumerable<string> values, Expression<Func<T, string>> fieldExpr, string delimiter) {
        var pred = PredicateBuilder.New<T>();
        foreach (var value in values) {
            pred = pred.Or(r => fieldExpr.Invoke(r) == value);
            pred = pred.Or(r => fieldExpr.Invoke(r).StartsWith(value   delimiter));
            pred = pred.Or(r => fieldExpr.Invoke(r).EndsWith(delimiter   value));
            pred = pred.Or(r => fieldExpr.Invoke(r).Contains(delimiter   value   delimiter));
        }

        return dbq.Where((Expression<Func<T, bool>>)pred.Expand());
    }
    public static IQueryable<T> WhereSplitContainsAny<T>(this IQueryable<T> dbq, Expression<Func<T, string>> fieldExpr, string delimiter, IEnumerable<string> values) =>
        dbq.WhereAnySplitContains(values, fieldExpr, delimiter);
    public static IQueryable<T> WhereSplitContainsAny<T>(this IQueryable<T> dbq, Expression<Func<T, string>> fieldExpr, string delimiter, params string[] values) =>
        dbq.WhereAnySplitContains(values, fieldExpr, delimiter);

    // values - string values, all of which to find in values of test field
    // string fieldExpr(T row) - function returning multiple value string field to test
    // delimiter - string separator between values in test field
    // dbq.Where(r => values.All(value => fieldExpr(r).Split(delimiter).Contains(value)))
    public static IQueryable<T> WhereAllSplitContains<T>(this IQueryable<T> dbq, IEnumerable<string> values, Expression<Func<T, string>> fieldExpr, string delimiter) {
        var pred = PredicateBuilder.New<T>();
        foreach (var value in values) {
            var subPred = PredicateBuilder.New<T>(r => fieldExpr.Invoke(r) == value);
            subPred = subPred.Or(r => fieldExpr.Invoke(r).StartsWith(value   delimiter));
            subPred = subPred.Or(r => fieldExpr.Invoke(r).EndsWith(delimiter   value));
            subPred = subPred.Or(r => fieldExpr.Invoke(r).Contains(delimiter   value   delimiter));
            pred = pred.And(subPred);
        }

        return dbq.Where((Expression<Func<T, bool>>)pred.Expand());
    }
    public static IQueryable<T> WhereSplitContainsAll<T>(this IQueryable<T> dbq, Expression<Func<T, string>> fieldExpr, string delimiter, IEnumerable<string> values) =>
        dbq.WhereAllSplitContains(values, fieldExpr, delimiter);
    public static IQueryable<T> WhereSplitContainsAll<T>(this IQueryable<T> dbq, Expression<Func<T, string>> fieldExpr, string delimiter, params string[] values) =>
        dbq.WhereAllSplitContains(values, fieldExpr, delimiter);
}
  

С этими расширениями ваш запрос будет:

 var volunteers = context.Volunteers.Where(x => x.StatusId == 1)
                                   .WhereSplitContains(r => r.RoleIds, ",", "1")
                                   .ToList();
  

Если у вас было несколько значений, вы могли бы использовать другие варианты:

 var volunteers = context.Volunteers.Where(x => x.StatusId == 1)
                                   .WhereSplitContainsAny(r => r.RoleIds, ",", "1", "2")
                                   .ToList();
  

Комментарии:

1. Это работает очень хорошо NetMage, большое спасибо! Просто еще одна мысль — как мне изменить расширение, чтобы оно принимало список строковых значений вместо одного значения «1»?

2. @Franky Вы бы хотели, чтобы ИЛИ или И обработка списка значений?

3. @Franky Я добавил варианты для списка значений.