Вот пример запроса, который я пытаюсь преобразовать в LINQ:
SELECT *
FROM Users
WHERE Users.lastname LIKE '%fra%'
AND Users.Id IN (
SELECT UserId
FROM CompanyRolesToUsers
WHERE CompanyRoleId in (2,3,4) )
Между CompanyRolesToUsers и Users существует отношение FK, но это отношение «многие ко многим», а CompanyRolesToUsers - это таблица соединений.
Мы уже создали большую часть нашего сайта, и у нас уже есть большая часть фильтрации, которая работает путем создания выражений с использованием класса PredicateExtensions.
Код простых фильтров выглядит примерно так:
if (!string.IsNullOrEmpty(TextBoxLastName.Text))
{
predicateAnd = predicateAnd.And(c => c.LastName.Contains(
TextBoxLastName.Text.Trim()));
}
e.Result = context.Users.Where(predicateAnd);
Я пытаюсь добавить предикат для подзапуска в другую таблицу. (CompanyRolesToUsers)
Я бы хотел добавить что-то, что делает следующее:
int[] selectedRoles = GetSelectedRoles();
if ( selectedRoles.Length > 0 )
{
//somehow only select the userid from here ???:
var subquery = from u in CompanyRolesToUsers
where u.RoleID in selectedRoles
select u.UserId;
//somehow transform this into an Expression ???:
var subExpression = Expression.Invoke(subquery);
//and add it on to the existing expressions ???:
predicateAnd = predicateAnd.And(subExpression);
}
Есть какой-либо способ сделать это? Это расстраивает, потому что я могу легко написать хранимую процедуру, но я новичок в этой вещи LINQ, и у меня есть крайний срок. Мне не удалось найти подходящий пример, но я уверен, что он где-то есть.





Вот как я делал подзапросы в LINQ, я думаю, это должно получить то, что вы хотите. Вы можете заменить явный CompanyRoleId == 2 ... другим подзапросом для различных ролей, которые вам нужны, или присоединиться к нему.
from u in Users
join c in (
from crt in CompanyRolesToUsers
where CompanyRoleId == 2
|| CompanyRoleId == 3
|| CompanyRoleId == 4) on u.UserId equals c.UserId
where u.lastname.Contains("fra")
select u;
Мне пришлось поставить «select crt» после «== 4», чтобы избежать ошибки «тело запроса должно заканчиваться предложением select или предложением group»
@ Нет, я бы не был так уверен. По моему опыту, производительность запросов LINQ сильно зависит от того, как вы пишете запрос (как и следовало ожидать от любого запроса SQL).
Для этого оператора не требуется подзапрос, его лучше записать как
select u.*
from Users u, CompanyRolesToUsers c
where u.Id = c.UserId --join just specified here, perfectly fine
and u.lastname like '%fra%'
and c.CompanyRoleId in (2,3,4)
или же
select u.*
from Users u inner join CompanyRolesToUsers c
on u.Id = c.UserId --explicit "join" statement, no diff from above, just preference
where u.lastname like '%fra%'
and c.CompanyRoleId in (2,3,4)
При этом в LINQ это будет
from u in Users
from c in CompanyRolesToUsers
where u.Id == c.UserId &&
u.LastName.Contains("fra") &&
selectedRoles.Contains(c.CompanyRoleId)
select u
или же
from u in Users
join c in CompanyRolesToUsers
on u.Id equals c.UserId
where u.LastName.Contains("fra") &&
selectedRoles.Contains(c.CompanyRoleId)
select u
Что опять же, оба респектабельных способа представить это. Я лично предпочитаю явный синтаксис "соединения" в обоих случаях, но вот он ...
Очень хороший ответ, но все эти методы ведут себя по-разному и обычно создают очень разные sql, sql, которые могут работать хорошо или плохо, обычно использование прямого соединения дает лучший sql, но не всегда ...
не "очень разные sql", а просто "разные sql". Я не буду спорить с различиями, но, по моему опыту, их нет в отношении производительности. Любой оптимизатор может определить соединения из предложения where.
По моему опыту работы с достаточно сложными запросами, способ написания запросов linq имеет значение. Sql server 2005 иногда не создает оптимальный план запроса для запросов с глубокими уровнями вложенности подзапросов ... Мне пришлось оптимизировать некоторые сложные запросы linq более чем несколько раз ...
Спасибо за помощь. Хотя это действительно помогает мне понять, как переводить SQL в запросы LINQ, ваши начальные запросы возвращают разные результаты -> если у пользователя несколько ролей, их запись будет возвращаться несколько раз, поэтому я в первую очередь избегал соединения.
На самом деле, если я добавлю к SQL «отдельный», то получу правильные записи. Добавлю еще пост с рабочим стыком.
Хм, зачем «фильтровать», когда можно «объединять и разделять». Это потому, что «соединить и разделить» - это естественный и захватывающий sql, в то время как «фильтр» недостаточно захватывающий?
Что ж, раз уж меня пригласили ... Факт: ни один оптимизатор не может превратить Distinct (n ^ 2) в фильтр (n).
В 99% случаев запрос, написанный с использованием "отличного", написан неправильно. Если вы хотите присоединиться к таблице для фильтрации, но не хотите дублировать записи в левой части соединения, используйте полусоединение (т. Е. Подзапрос).
Вы можете сделать что-то подобное для своего случая - (синтаксис может быть немного неправильным). Также посмотрите этот ссылка на сайт
subQuery = (from crtu in CompanyRolesToUsers where crtu.RoleId==2 || crtu.RoleId==3 select crtu.UserId).ToArrayList();
finalQuery = from u in Users where u.LastName.Contains('fra') && subQuery.Contains(u.Id) select u;
Я просто хотел показать, что он хочет это сделать .. не навязывать свое мнение
@TheSoftwareJedi Я вижу, что вы спамили этот комментарий по нескольким ответам здесь, но имейте в виду, что эту ветку, вероятно, найдут люди, которые ищут помощь при выполнении подзапросов в linq, чьи обстоятельства, вероятно, не совсем такие же, как у OP.
Вот версия SQL, которая возвращает правильные записи:
select distinct u.*
from Users u, CompanyRolesToUsers c
where u.Id = c.UserId --join just specified here, perfectly fine
and u.firstname like '%amy%'
and c.CompanyRoleId in (2,3,4)
Также обратите внимание, что (2,3,4) - это список, выбранный из списка флажков пользователем веб-приложения, и я забыл упомянуть, что я просто жестко запрограммировал его для простоты. На самом деле это массив значений CompanyRoleId, поэтому он может быть (1), (2,5) или (1,2,3,4,6,7,99).
Еще одна вещь, которую я должен указать более четко, - это то, что PredicateExtensions используются для динамического добавления предложений предиката в Where для запроса, в зависимости от того, какие поля формы заполнил пользователь веб-приложения. Таким образом, сложная часть для меня заключается в том, как чтобы преобразовать рабочий запрос в выражение LINQ, которое я могу присоединить к динамическому списку выражений.
Я попробую несколько примеров запросов LINQ и посмотрю, смогу ли я интегрировать их с нашим кодом, а затем опубликую свои результаты. Спасибо!
Марсель
Вот вам подзапрос!
List<int> IdsToFind = new List<int>() {2, 3, 4};
db.Users
.Where(u => SqlMethods.Like(u.LastName, "%fra%"))
.Where(u =>
db.CompanyRolesToUsers
.Where(crtu => IdsToFind.Contains(crtu.CompanyRoleId))
.Select(crtu => crtu.UserId)
.Contains(u.Id)
)
Относительно этой части вопроса:
predicateAnd = predicateAnd.And(c => c.LastName.Contains(
TextBoxLastName.Text.Trim()));
Я настоятельно рекомендую извлечь строку из текстового поля перед созданием запроса.
string searchString = TextBoxLastName.Text.Trim();
predicateAnd = predicateAnd.And(c => c.LastName.Contains( searchString));
Вы хотите сохранить хороший контроль над тем, что отправляется в базу данных. В исходном коде одним из возможных вариантов считывания является то, что необрезанная строка отправляется в базу данных для обрезки, что не является хорошей работой для базы данных.
Спасибо за полезный код. Это, безусловно, показывает, как вещи могут быть связаны друг с другом. Вы также, вероятно, правы, выполняя строковую операцию перед тем, как передать ее Linq, конечно, не помешало бы держать вещи разделенными.
Хорошо, вот базовый запрос соединения, который получает правильные записи:
int[] selectedRolesArr = GetSelectedRoles();
if ( selectedRolesArr != null && selectedRolesArr.Length > 0 )
{
//this join version requires the use of distinct to prevent muliple records
//being returned for users with more than one company role.
IQueryable retVal = (from u in context.Users
join c in context.CompanyRolesToUsers
on u.Id equals c.UserId
where u.LastName.Contains( "fra" ) &&
selectedRolesArr.Contains( c.CompanyRoleId )
select u).Distinct();
}
Но вот код, который легче всего интегрируется с уже существующим алгоритмом:
int[] selectedRolesArr = GetSelectedRoles();
if ( useAnd )
{
predicateAnd = predicateAnd.And( u => (from c in context.CompanyRolesToUsers
where selectedRolesArr.Contains(c.CompanyRoleId)
select c.UserId).Contains(u.Id));
}
else
{
predicateOr = predicateOr.Or( u => (from c in context.CompanyRolesToUsers
where selectedRolesArr.Contains(c.CompanyRoleId)
select c.UserId).Contains(u.Id) );
}
что благодаря плакату на Форум LINQtoSQL
Он просил подзапрос! Но я уверен, что LINQ все равно меняет все на тот же SQL ...