Здравствуйте,
помогите разобраться. У меня на сайте пользователь может выбрать некоторые критерии, в соответствии с которыми производится выборка. Поскольку критериев много, решено было отказаться от ХП и использовать только LINQ. Но тут возник затык — оказалось, я не знаю, как же сделать запрос динамическим. Предположим, надо выбрать покупателей, относящихся к определенным категориям или категории. Т.е. sql запросы могут быть такими:
select * from customers where category = 4;
select * from customers where category = 4 and category = 8;
select * from customers where category = 4 and category = 8 and category = 9;
select * from customers;
В последнем случае пользователю надо выбрать всех покупателей безотносительно категории.
Предполагалось, что запрос на LINQ мог бы выглядеть примерно следующим образом (это ПСЕВДОКОД!)
Категории, для которых надо производить выборку, содержатся в List<int>
List<int> categories = new List<int>(){4,8}; //на самом деле массив передается в качестве параметра и содержит произвольный набор категорий и может быть пустым
//дальше идет псевдокодvar result;
foreach(int cat in categories)
{
result = customers.Where(r => r == cat);
}
result = result.Select();
Т.е. суть в том, чтобы динамически производить операцию where в зависимости от выбора пользователя.
Данный код, естественно, не работает.
Как заставить его работать? Может, вообще как-то по-другому это делается?
Спасибо
Life is very short and there's no time
for fussing and fighting... (C) Paul McCartney & John Lennon
Здравствуйте, J_K, Вы писали:
J_K>Как заставить его работать? Может, вообще как-то по-другому это делается?
Поставьте себя на место LINQ — как можно из вашего foreach сгенерировать хотя бы минимально эффективный SQL запрос?
Попробуйте сделать Join для таблиц customers и categories
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
select * from customers where category = 4 and category = 8;
select * from customers where category = 4 and category = 8 and category = 9;
Если честно, я в БД, СУБД и SQL плохо разбираюсь. Но что-то мне подсказывает, что эти два запроса всегда возвращают пустое множество. Возможно, это «что-то» ошибается.
J_K>//дальше идет псевдокод...
Что-то я не смекнул, каким образом этот псевдокод иллюстрирует проблему... Ну, неважно.
J_K>Т.е. суть в том, чтобы динамически производить операцию where в зависимости от выбора пользователя.
Исходя из того, как я понял проблему (а быть может, я её понял неправильно), могу предложить два пути решения.
1) Если у пользователя есть лишь небольшое число вариантов выбора, то можно просто в switch'e или из таблицы выбрать соответствующую функцию или передать непосредственно лямбду.
2) Если выбор пользователя может быть произвольным, то можно сконструировать прямо в рантайме соответствующее expression tree, а затем передать его в запрос. Для каждой LINQ-функции, принимающей лямбду, вроде есть аналогичная функция, принимающая expression tree. Так что этот путь по сути предполагает построение интерпретатора выражений. Возможно, будет проще сгенерировать интерпретатор программой типа GOLD.
Здравствуйте, J_K, Вы писали:
J_K>помогите разобраться. У меня на сайте пользователь может выбрать некоторые критерии, в соответствии с которыми производится выборка. Поскольку критериев много, решено было отказаться от ХП и использовать только LINQ.
Linq тут вряд ли поможет, я бы сгенерировал все нужные варианты ХП автоматом, назвал бы и по каким-то правилом и при выборке генерировал бы имя ХП по тем же правилам, чтобы найти нужную.
Связность, конечно, высокая, но альтернативой является, пожалуй, только навороченная O/R M
J_K>select * from customers where category = 4;
J_K>select * from customers where category = 4 and category = 8;
J_K>select * from customers where category = 4 and category = 8 and category = 9;
J_K>select * from customers;
J_K>
Может быть там имеется ввиду or, а не and? Может тогда in поможет?
J_K>
J_K>List<int> categories = new List<int>(){4,8}; //на самом деле массив передается в качестве параметра и содержит произвольный набор категорий и может быть пустым
J_K>//дальше идет псевдокод
J_K>var result;
J_K>foreach(int cat in categories)
J_K>{
J_K> result = customers.Where(r => r == cat);
J_K>}
J_K>result = result.Select();
J_K>
Здесь в цикле result переприсвается. Смысла в этом не очень много.
J_K>select * from customers where category = 4;
J_K>select * from customers where category = 4 and category = 8;
J_K>select * from customers where category = 4 and category = 8 and category = 9;
J_K>select * from customers;
J_K>
Вы перепутали or с and.
J_K>
J_K>List<int> categories = new List<int>(){4,8}; //на самом деле массив передается в качестве параметра и содержит произвольный набор категорий и может быть пустым
J_K>//дальше идет псевдокод
J_K>var result = customers;
J_K>foreach(int cat in categories)
J_K>{
J_K> result = result.Where(r => r == cat);
J_K>}
J_K>//result = result.Select();
J_K>
J_K>Т.е. суть в том, чтобы динамически производить операцию where в зависимости от выбора пользователя. J_K>Данный код, естественно, не работает. J_K>Как заставить его работать? Может, вообще как-то по-другому это делается? J_K>Спасибо
опять же вы перепутали условие, правильно будет:
result = customers.Where(c => categories.Contains(c.category));
Здравствуйте, J_K, Вы писали:
J_K>помогите разобраться.
Вижу несколько ошибок и проблем.
1. Во-первых, приведенные SQL запросы явно делают не то, чего вы хотите.
Вот правильные варианты:
select * from customers where (category = 4);
select * from customers where (category = 4 or category = 8);
select * from customers where (category = 4 or category = 8 or category = 9);
2. Если я прав, то цикл не нужен. Достаточно проверить одним предикатом:
var result = from c in customers where categories.Contains(c.category)
3. Linq идеально подходит для динамических решений в духе твоего цикла. Только фильтры нужно применять не к одному и тому же источнику, а цепочкой:
var result = from c in customers select c;
if (categories.Length > 0)
{
result = from r in result where categories.Contains(r.category) select r;
}
if (!name.IsNullOrEmpty)
{
result = from r in result where r.name == name select r;
}
...
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Спасибо всем за интересные сообщения.
Да, конечно, там в запросе вместо and должен быть or. Сообщение писалось в час ночи, уже голова плоховато работала.
Буду исследовать предложенные вами варианты.
Вопрос к последнему оратору.
Вы писали:
var result = from c in customers select c;
if (categories.Length > 0)
{
result = from r in result where categories.Contains(r.category) select r;
}
if (!name.IsNullOrEmpty)
{
result = from r in result where r.name == name select r;
}
Эта конструкция и была мне нужна, но! Разве select не должен идти последним? Или это неважно?
Life is very short and there's no time
for fussing and fighting... (C) Paul McCartney & John Lennon
Здравствуйте, J_K, Вы писали:
J_K>Здравствуйте, adontz, Вы писали:
J_K>Мне тоже интересно. Но сейчас у меня SQL 2005, а там нет профайлера, чтобы посмотреть... или можно как-то по-другому?
Как так нет? Есть. В Performance tools.
Можно и по-другому. Есть плагин к VS 2008 — SqlServerQueryVisualizer: http://weblogs.asp.net/scottgu/archive/2007/07/31/linq-to-sql-debug-visualizer.aspx
Кстати в SP1 beta он уже включен.
Здравствуйте, <Аноним>, Вы писали:
А>Здравствуйте, Аноним, Вы писали:
А>>посмотрите Dynamic Linq А>Sinclair и Ziaw, напишите, пожалуйста, чем вам это решение не нравится.
1. Linq создан как средство избавления от динамической генерации текстовых запросов.
2. В данном случае нет необходимости в их применении.
3. Linq предоставляет досточно механизмов для генерации запросов проходящих проверку на уровне компиляции.
Здравствуйте, J_K, Вы писали:
J_K>Попытка использовать Contains успехом не увенчалась. Возник эксепшн:
J_K>LINQ to Entities does not recognize the method 'Boolean Contains(Int32)' method, and this method cannot be translated into a store expression.
Здравствуйте, J_K, Вы писали:
J_K>Мне тоже интересно. Но сейчас у меня SQL 2005, а там нет профайлера, чтобы посмотреть... или можно как-то по-другому?
В отладчике смотрим на result.ToString().
... << RSDN@Home 1.2.0 alpha rev. 771>>
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, J_K, Вы писали:
J_K>Судя по всему, это еще баг:
Не баг, конечно, просто реализовать не успели.
Сейчас попробовал заюзать Contains на VS 2008 SP1 beta (в составе с SP1 beta идет EF beta 4) — тоже не работает. Что-то затянули они... а возможность-то полезная.