I'm trying to write a LINQ/Entity Framework projection that will pull back two fields from a distant relation. For all the purposes of this question, I have these structure:
PlayedGame
int Id
(PK)List<PlayerGameResult> PlayerGameResults
Player
int Id
(PK)string Name
PlayerGameResult
PlayedGame PlayedGame
(FK to PlayedGame)Player Player
(FK to Player)
For a given played game, I want to fetch the Name and Player Id of one arbitrary Player that was in that played game. In reality my example is a little bit more complicated, but I'm leaving out the details since they are a distraction.
The following projection is the best I could come up with:
var result = dataContext.GetQueryable<PlayedGame>()
.Where(playedGame => playedGame.Id == somePlayedGameId)
.Select(x => new
{
Name = x.PlayerGameResults.FirstOrDefault() != null ? x.PlayerGameResults.FirstOrDefault().Player.Name : null,
Id = x.PlayerGameResults.FirstOrDefault() != null ? x.PlayerGameResults.FirstOrDefault().Player.Id : 0
})
.FirstOrDefault();
... but the underlying SQL that is generated is moderately terrifying to me:
SELECT
[Limit5].[Id] AS [Id],
[Limit5].[C1] AS [C1],
[Limit5].[C2] AS [C2]
FROM
(SELECT TOP (1)
[Project11].[Id] AS [Id],
CASE WHEN ([Project11].[C1] IS NOT NULL) THEN [Project11].[Name] END AS [C1],
CASE WHEN ([Project11].[C2] IS NOT NULL) THEN [Project11].[C3] ELSE 0 END AS [C2]
FROM
(SELECT
[Project9].[Id] AS [Id],
[Project9].[Name] AS [Name],
[Project9].[C1] AS [C1],
[Project9].[C2] AS [C2],
(SELECT TOP (1)
[Extent6].[PlayerId] AS [PlayerId]
FROM [dbo].[PlayerGameResult] AS [Extent6]
WHERE [Project9].[Id] = [Extent6].[PlayedGameId]) AS [C3]
FROM ( SELECT
[Project8].[Id] AS [Id],
[Project8].[Name] AS [Name],
[Project8].[C1] AS [C1],
[Project8].[C2] AS [C2]
FROM ( SELECT
[Project6].[Id] AS [Id],
[Project6].[Name] AS [Name],
[Project6].[C1] AS [C1],
(SELECT TOP (1)
[Extent5].[Id] AS [Id]
FROM [dbo].[PlayerGameResult] AS [Extent5]
WHERE [Project6].[Id] = [Extent5].[PlayedGameId]) AS [C2]
FROM ( SELECT
[Project5].[Id] AS [Id],
[Extent4].[Name] AS [Name],
[Project5].[C1] AS [C1]
FROM (SELECT
[Project3].[Id] AS [Id],
[Project3].[C1] AS [C1],
(SELECT TOP (1)
[Extent3].[PlayerId] AS [PlayerId]
FROM [dbo].[PlayerGameResult] AS [Extent3]
WHERE [Project3].[Id] = [Extent3].[PlayedGameId]) AS [C2]
FROM ( SELECT
[Project2].[Id] AS [Id],
[Project2].[C1] AS [C1]
FROM ( SELECT
[Extent1].[Id] AS [Id],
(SELECT TOP (1)
[Extent2].[Id] AS [Id]
FROM [dbo].[PlayerGameResult] AS [Extent2]
WHERE [Extent1].[Id] = [Extent2].[PlayedGameId]) AS [C1]
FROM [dbo].[PlayedGame] AS [Extent1]
WHERE [Extent1].[Id] = @p__linq__0
) AS [Project2]
) AS [Project3] ) AS [Project5]
LEFT OUTER JOIN [dbo].[Player] AS [Extent4] ON [Project5].[C2] = [Extent4].[Id]
) AS [Project6]
) AS [Project8]
) AS [Project9]
) AS [Project11]
) AS [Limit5]
Is there a "better" way to write this projection? In other words, how can I write this query in LINQ / Entity Framework to only pull back these two fields from the Player table in a fashion that will produce a decent/sane query? I won't pretend to know for certain that the above query is terrible -- but it just doesn't seem in the ballpark to me.
Looking forward to your thoughts!