Я использую Entity Framework 4.0 (поэтому я могу использовать его с .NET 3.5) и сгенерировал DAL из существующей базы данных.
В базе данных у меня есть две таблицы со следующими столбцами (ничто не может быть пустым):
- tblWeapon (WeaponId PK, WeaponLabel)
- tblShot (ShotId PK, WeaponId)
И есть внешний ключ в tblShot's WeaponId для tblWeapon.
Тогда сгенерированные сущности выглядят примерно так:
public class Weapon {
public int WeaponId { ... }
public string WeaponLabel { ...}
public EntityCollection Shots { ... }
}
public class Shot {
public int ShotId { ... }
public EntityReference WeaponReference { ... }
public Weapon Weapon { ... }
}
В моем коде у меня есть классы ShotFilter и WeaponFilter, которые содержат критерии для фильтрации отдельных таблиц. Поскольку фильтр для сущностей генерируется динамически, я хотел бы распространить генерацию запросов на соответствующие классы фильтров. Каждый фильтр будет возвращать IQueryable<T>
, и они будут объединяться по мере необходимости для достижения желаемых результатов.
То, что я хочу сделать, - это получить все объекты Shot, которые ссылаются на оружие, на этикетке которого содержится текст 0.5
.
Проблема возникает при попытке выполнить внутреннее соединение на IQueryable<Shot>
с IQueryable<Weapon>
, поскольку Shot
не содержит поля WeaponId
(просто WeaponReference
). Пройдя по сети и не найдя ничего, я нашел сообщение на форуме , где ответом было просто присоединиться к самим объектам. Поэтому я попробовал это и получил ожидаемые результаты:
var oWeaponQuery = from w in oDc.Weapons select w;
oWeaponQuery = oWeaponQuery.Where(w => w.Label.Contains("0.5"));
var oShotQuery = from s in oDc.Shots select s;
oShotQuery = oShotQuery.Join(oWeaponQuery, s => s.Weapon, w => w, (s, w) => s);
Но когда я проверял фактический SQL-запрос, запрашиваемый с помощью SQL Server Profiler, я увидел это ужасное утверждение (и его немного вырвало):
SELECT
1 AS [C1],
[Extent1].[ShotId] AS [ShotId],
[Extent1].[WeaponId] AS [WeaponId]
FROM [dbo].[tblShot] AS [Extent1]
INNER JOIN [dbo].[tblWeapon] AS [Extent2] ON EXISTS (SELECT
cast(1 as bit) AS [C1]
FROM ( SELECT cast(1 as bit) AS X ) AS [SingleRowTable1]
LEFT OUTER JOIN (SELECT
[Extent3].[WeaponId] AS [WeaponId]
FROM [dbo].[tblWeapon] AS [Extent3]
WHERE [Extent1].[WeaponId] = [Extent3].[WeaponId] ) AS [Project1] ON 1 = 1
LEFT OUTER JOIN (SELECT
[Extent4].[WeaponId] AS [WeaponId]
FROM [dbo].[tblWeapon] AS [Extent4]
WHERE [Extent1].[WeaponId] = [Extent4].[WeaponId] ) AS [Project2] ON 1 = 1
WHERE ([Project1].[WeaponId] = [Extent2].[WeaponId]) OR (([Project2].[WeaponId] IS NULL) AND ([Extent2].[WeaponId] IS NULL))
)
WHERE (CAST(CHARINDEX(N'0.5', [Extent2].[Label]) AS int)) > 0
Итак, как бы я сделал это правильно или хотя бы эффективно? Или какие-нибудь другие предложения о том, как организовать генерацию запросов динамически и распределенно?
Спасибо!
Обновление с более подробной информацией
Часть моей проблемы при выполнении соединения связана с EF, в сгенерированном объекте для Shot
нет свойства WeaponId
. Есть только свойство WeaponReference
, которое управляет им. Поэтому в моем соединении я ожидал, что смогу использовать:
oShotQuery = oShotQuery.Join(oWeaponQuery, s => s.WeaponId, w => w.WeaponId, (s, w) => s);
Но это не работает из-за того, что WeaponId
не является свойством Shot
.
Затем я попробовал это (что опять-таки кажется странным):
oShotQuery = oShotQuery.Join(oWeaponQuery, s => s.Weapon.WeaponId, w => w.WeaponId, (s, w) => s);
И это работает, и выдает довольно сжатый SQL (за исключением):
SELECT
1 AS [C1],
[Extent1].[ShotId] AS [ShotId],
[Extent1].[WeaponId] AS [WeaponId]
FROM [dbo].[tblShot] AS [Extent1]
INNER JOIN [dbo].[tblWeapon] AS [Extent2] ON ([Extent1].[WeaponId] = [Extent2].[WeaponId]) OR
(([Extent1].[WeaponId] IS NULL) AND ([Extent2].[WeaponId] IS NULL))
WHERE (CAST(CHARINDEX(N'0.5', [Extent2].[Label]) AS int)) > 0
И это исключение таково: OR (([Extent1].[WeaponId] IS NULL) AND ([Extent2].[WeaponId] IS NULL))
. Я не хочу, где они оба NULL
, я хочу только там, где они равны.