#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, что не так приятно.