Локальная последовательность не может использоваться в реализации 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);
}
Другие вопросы по тегам