У меня есть три таблицы: UserObjects
, UserObjectsRelations
, UserClasses
, которые моделируют отношения M: N между UserObjects и UserClasses.
Теперь мне нужно выбрать это:
All(UserObjects) - Intersect(UserObjectRelations -> UserObjects).Where(UserObjectRelation -> UserClassId IN (some list))
Это означает, что у меня есть список UserClassIds, которые я использую для фильтрации UserClasses (или отношений напрямую), и мне нужно найти все UserObjects, которые не назначены all этим UserClasses.
Пример. Предположим, что у меня есть UserObjectRelations, отфильтрованные по UserClassId IN (1,2):
UserClassId | UserObjectId
--------------------------
1 | 1
2 | 1
2 | 2
У меня также есть много других пользовательских объектов. Результатом моего запроса должны быть все UserObjects, не упомянутые в этом наборе результатов + UserObject с Id = 2, поскольку он не связан со всеми запрошенными UserClasses.
Проблема в том, что SQL-запрос генерируется Entity Framework (у нас нет полного контроля над сгенерированным SQL), поэтому наш первоначальный подход с INTERSECT
не удался - со многими классами UserClass он создает слишком сложный запрос, а иногда SQL Server вызывает ошибку из-за глубокого вложения.
Он создает такой запрос (но очень большой, потому что EF не использует * нотацию и ему действительно нравится много вложенных SELECT):
SELECT Unsused.*
FROM dbo.UserObjects AS Unsused
WHERE Unsused.IsDeleted = 0
EXCEPT (
SELECT U.*
FROM dbo.UserObjects AS U
INNER JOIN dbo.UserObjectRelations AS UR ON UR.UserObjectId = U.Id
WHERE UR.UserClassId = 1
INTERSECT (
SELECT U.*
FROM dbo.UserObjects AS U
INNER JOIN dbo.UserObjectRelations AS UR ON UR.UserObjectId = U.Id
WHERE UR.UserClassId = 2
))
Сейчас я переписываю запрос - сначала в SQL, затем я попытаюсь определить его в Linq-To-Entities. Я придумываю это:
SELECT Unused.*
FROM dbo.UserObjects AS Unused
LEFT JOIN (
SELECT UsageReport.Id
FROM (
SELECT Tmp.Id, COUNT(*) AS Usage
FROM dbo.UserObjects AS Tmp
INNER JOIN dbo.UserObjectRelations AS DefiningRelations ON
Tmp.Id = DefiningRelations.UserObjectId
WHERE DefiningRelations.UserClassId IN (1, 2)
GROUP BY Tmp.Id) AS UsageReport
WHERE UsageReport.Usage = 2
) AS Used ON Used.Id = Unused.Id
WHERE Unused.IsDeleted = 0 AND Used.Id IS NULL
Запрос, вероятно, выглядит не очень хорошо, но я уже пытаюсь избегать конструкций, которые я не знаю, как преобразовать в Linq-To-Entities.
Я все еще не доволен запросом. Мне не нравится эта часть: WHERE UsageReport.Usage = 2
, которая фильтрует внутренний выбор только для пользовательских объектов, которые используются обоими пользовательскими классами. Этот параметр должен быть динамическим и всегда представлять количество идентификаторов, переданных в предложение IN.
У вас есть идея, как лучше написать такой запрос?