Использование IQueryable в join вызывает исключение StackOverflow

#linq-to-sql

#linq-to-sql

Вопрос:

Кто-нибудь знает, почему это вызывает исключение stackoverflow:

 public IQueryable<Category> LoadCategories(bool onlyCatsWithProducts, ...)
{
    var db = new DbDataContext();
    var res = db.Categories.AsQueryable();

    if (onlyCatsWithProducts)
        res = from p in db.Products
              from c in res
              where p.CategoryID == c.ID
              select c;
    ...
    return res;
}
  

Обновить

Изменен пример кода, чтобы было понятно, почему я присваиваю переменной, а затем переназначаю ее позже. В основном это потому, что я пишу функцию для возврата категорий из базы данных, и я принимаю несколько параметров (например, onlyCatsWithProducts), где каждый фильтр результирующего набора только в том случае, если у них есть значение. Обратите внимание, что это все еще не мой фактический код, потому что мои запросы более сложные, и я просто хочу показать простейший запрос, необходимый для воспроизведения ошибки.

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

1. Это фактический код? Если это так, я предполагаю, что это опечатка… что такое v?

2. Спасибо, исправлена опечатка. Я просто сильно упростил свой код для примера, поэтому не мой фактический код.

3. Вам не нужно .AsQueryable() . Возможно, вы захотите попробовать явно задать тип вывода для базового интерфейса, а не использовать var или AsQueryable() . Я не уверен, решит ли это вашу проблему с StackOverflow при рекурсивном назначении дерева выражений здесь.

4. @JimWooley Цель .AsQueryable() заключается в том, что db.Categories имеет тип System.Data.Linq.Table<T> , а позже я присваиваю запрос типа System.Data.Linq.DataQuery<T> этой переменной. Я мог бы, конечно, просто объявить переменную как IQueryable<Category> res = db.Categories; , но это не помогает исправить ошибку.

Ответ №1:

Майкл, отвечая на свой собственный вопрос, сказал, что понятия не имеет, почему замена порядка его from исправила его проблему.

Например, это вызвало переполнение стека:

 var res = db.Categories.AsQueryable();
res = from p in db.Products
      from c in res
      where p.CategoryID == c.ID
      select c;
  

Как это не:

 var res = db.Categories.AsQueryable();
res = from c in res
      from p in db.Products
      where p.CategoryID == c.ID
      select c;
  

Вот почему. Эти два вышеуказанных запроса переводятся компилятором в этот код:

 var res = db.Categories.AsQueryable();
var q = db.Products
    .SelectMany(p => res, (p, c) => new { p, c })
    .Where(x => x.p.CategoryID == x.c.ID)
    .Select(x => x.c);
  

и это соответственно:

 var res = db.Categories.AsQueryable();
var q = res
    .SelectMany(c => db.Products, (c, p) => new { c, p })
    .Where(x => x.p.CategoryID == x.c.ID)
    .Select(x => x.c);
  

Первый содержит лямбда p => res -выражение, которое по сути захватывает ссылку на res переменную. Поскольку res переназначается каждый раз, когда выполняется запрос, ссылается на переназначенную версию самого себя и bang — переполнение стека!

Во втором res случае он находится за пределами каких-либо lambda, поэтому ссылка не фиксируется и используется только исходная ссылка — т. е. res = db.Categories.AsQueryable() И это не меняется при выполнении запроса.

Вероятно, это было бы так же просто в использовании:

 var res = from c in db.Categories
          from p in db.Products
          where p.CategoryID == c.ID
          select c;
  

Я надеюсь, что это поможет прояснить, что происходит.

Ответ №2:

Извиняюсь, после публикации я обнаружил, что замена порядка, похоже, устраняет проблему. Понятия не имею, почему, хотя:

 var db = new DbDataContext();
var res = db.Categories.AsQueryable();
res = from c in res
      from p in db.Products
      where p.CategoryID == c.ID
      select c;
  

Ответ №3:

Зачем создавать переменную, а затем сразу же переназначать переменную, используя переменную в назначении? Ваш запрос, вероятно, выполняет какую-то бесконечную рекурсию, вызывая StackOverFlowException . Попробуйте что-то вроде:

 var res = 
  from c in DB.Instance.Categories
  from p in DB.Instance.Products
  where p.CategoryID == c.ID
  select c;
  

Обновить:

Попробуйте что-то вроде приведенного ниже. Я думаю, вам нужно избегать использования res при назначении res .

 IQueryable<Category> res;

if (onlyCatsWithProducts)
    res = from p in db.Products
          from c in db.Categories.AsQueryable()
          where p.CategoryID == c.ID
          select c;
  

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

1. Изменен вопрос, чтобы отразить назначение переменной. Ваш код работает, но моя функция принимает ряд параметров (а не только один, как в моем уточненном примере), которые изменяют запрос в зависимости от того, что передано, и в результате выполнение всего этого в одном запросе сделает его намного сложнее. Я мог бы сделать это, если бы это был первый примененный фильтр, но три из моих фильтров имеют сложные соединения, которые требуют такого синтаксиса, и поэтому для всех перестановок требуется несколько операторов if else, что не так приятно.