#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 Я добавил варианты для списка значений.