Локальная последовательность не может использоваться в реализации LINQ to SQL
Я получаю сообщение об ошибке, см. Ниже, когда я пытаюсь сгенерировать список класса MappedItem. Вкратце приведенный ниже пример кода пытается найти товары по категориям, диапазону дат и артикулу. У меня есть требование, чтобы пользователь мог вводить список SKU, разделенных запятыми, и поиск должен найти любой продукт, чей SKU начинается с одного из SKU, введенных пользователем. Когда я запускаю код, я получаю.
Локальная последовательность не может использоваться в реализации операторов запросов LINQ to SQL, кроме оператора Contains().
Сокращенная последовательность такова:
Преобразуйте разделенные запятыми строки SKU в список строк.
string sku = TextSKU.Text;
List<string> skuList = sku.Split(new char[] { ',' }).ToList();
Определите в другом месте кода класс, который будет принимать результаты поиска.
public class MappedItem
{
public string ItemDescription { get; set; }
public int ItemCount { get; set; }
public MappedItem()
{
}
public MappedItem(string itemDescription, int itemCount)
{
ItemDescription = itemDescription;
ItemCount = itemCount;
}
}
Вот запрос, по которому я генерирую свои результаты
List<MappedItem> widgetItems = (from c1 in db.CCRCodes
join pac in db.widgetAssignedCodes on c1.code_id equals pac.code_id
join ph in db.widgetHistories on pac.history_id equals ph.history_id
where ph.contact_dt.Value.Date >= startDate && ph.contact_dt.Value.Date <= endDate &&
(string.IsNullOrEmpty(baanCatFam) || ph.baan_cat_family_code == baanCatFam) &&
(string.IsNullOrEmpty(baanCat) || ph.baan_cat_code == baanCat) &&
(string.IsNullOrEmpty(baanSubCat) || (ph.baan_sub_cat_code == baanSubCat)) &&
(string.IsNullOrEmpty(sku) || skuList.All(sl => ph.product_mod.StartsWith(sl)))
group c1 by c1.code_desc into ct
select new MappedItem
{
ItemDescription = ct.Key.ToUpper(),
ItemCount = ct.Count()
}).OrderByDescending(m => m.ItemCount)
.ToList();
Я считаю, что виновником является строка кода, которую я извлек и показал ниже.
skuList.All(sl => ph.product_mod.StartsWith(sl))
Это идентифицирует все skus, которые начинаются с элемента из skuList, который получен из разделенных запятыми списков skus, введенных пользователем. У меня вопрос, что является причиной этой ошибки, и, учитывая примеры кода, что мне делать, чтобы обойти их.
1 ответ
Во-первых - по логике вы хотите любой, а не все.
Во-вторых, это плохой способ создания фильтра запросов. Все эти операции отправляются в базу данных, а информация о том, какие фильтры следует применять, уже локальна. Явные объединения также плохие (вместо них можно использовать свойства ассоциации).
IQueryable<WidgetHistory> query = db.widgetHistories
.Where(ph => ph.contact_dt.Value.Date >= startDate
&& ph.contact_dt.Value.Date <= endDate);
if (!string.IsNullOrEmpty(baanCatFam))
{
query = query.Where(ph => ph.baan_cat_family_code == baanCatFam);
}
if (!string.IsNullOrEmpty(baanCat))
{
query = query.Where(ph => ph.baan_cat_code == baanCat);
}
if (!string.IsNullOrEmpty(baanSubCat))
{
query = query.Where(ph => ph.baan_sub_cat_code == baanSubCat);
}
//TODO sku filtering here.
List<MappedItem> widgetItems =
from ph in query
let c1 = ph.widgetAssignedCode.CCRCode
group c1 by c1.code_desc into g
select new MappedItem
{
ItemDescription = g.Key.ToUpper(),
ItemCount = g.Count()
}).OrderByDescending(m => m.ItemCount)
.ToList();
Третье: ответ на ваш вопрос.
что вызывает эту ошибку
Поставщик запросов LinqToSql не может перевести вашу локальную коллекцию в s ql. Есть только ограниченный набор сценариев, где он может переводиться... .Where(ph => idList.Contains(ph.Id))
переводится в предложение IN с 1 параметром на int в idList.
Чтобы обойти это ограничение, вам нужно преобразовать локальную коллекцию в выражение. Начнем с преобразования каждого элемента в коллекции в фильтрующее выражение:
List<Expression<Func<WidgetHistory, bool>>> skuFilters =
skuList.Select<string, Expression<Func<WidgetHistory, bool>>>(skuItem =>
ph => ph.ProductMod.StartsWith(skuItem)
).ToList();
Далее вспомогательный метод:
public static Expression<Func<T, bool>> OrTheseFiltersTogether<T>(
this IEnumerable<Expression<Func<T, bool>>> filters)
{
Expression<Func<T, bool>> firstFilter = filters.FirstOrDefault();
if (firstFilter == null)
{
Expression<Func<T, bool>> alwaysTrue = x => true;
return alwaysTrue;
}
var body = firstFilter.Body;
var param = firstFilter.Parameters.ToArray();
foreach (var nextFilter in filters.Skip(1))
{
var nextBody = Expression.Invoke(nextFilter, param);
body = Expression.OrElse(body, nextBody);
}
Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(body, param);
return result;
}
А теперь собираем все вместе:
if (skuFilters.Any()) //this part goes into where it says "TODO"
{
Expression<Func<WidgetHistory, bool>> theSkuFilter = skuFilters.OrTheseFiltersTogether()
query = query.Where(theSkuFilter);
}