Локальная последовательность не может использоваться в реализации LINQ to SQL - PullRequest
4 голосов
/ 15 февраля 2011

Я получаю сообщение об ошибке, см. Ниже, когда я пытаюсь сгенерировать список класса 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 Ответ

5 голосов
/ 16 февраля 2011

Первый - по логике вы хотите Любой, а не Все.

Во-вторых, это плохой способ создания фильтра запросов. Все эти операции отправляются в базу данных, а информация о том, какие фильтры следует применять, уже локальна. Явные объединения также плохие (вместо них можно использовать свойства ассоциации).

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 не может перевести вашу локальную коллекцию в sql. Существует только ограниченный набор сценариев, в которые он может переводиться ... .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);
}
...