我正在尝试编写LINQ / Entity Framework投影,该投影将从远距离关系中拉回两个字段。出于这个问题的所有目的,我有这些结构:
PlayedGame
name="testCollection"
(PK)int Id
播放器
List<PlayerGameResult> PlayerGameResults
(PK)int Id
PlayerGameResult
string Name
(FK to PlayedGame)PlayedGame PlayedGame
(FK to Player)对于给定的游戏,我想获取该游戏中任意玩家的名字和玩家ID。实际上我的例子有点复杂,但我忽略了细节,因为它们分散了注意力。
以下预测是我能想到的最好的:
Player Player
...但是生成的底层SQL对我来说是非常可怕的:
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();
是否有更好的&#34;写这个投影的方式?换句话说,如何在LINQ / Entity Framework中编写此查询,只能以一种方式从Player表中拉回这两个字段,从而产生一个体面/合理的查询?我不会假装知道上面的查询很糟糕 - 但它对我来说似乎并不像。
期待您的想法!
答案 0 :(得分:1)
整个LINQ语句被翻译成SQL,这意味着您不必担心空检查。 SQL没有null引用的概念(我们可能会说,它从一开始就具有null传播!)。所以你的陈述可以简化为:
var result = dataContext.GetQueryable<PlayedGame>()
.Where(player => player.Id == testPlayerWithNoPlayedGames.Id)
.Select(x => new
{
Name = x.PlayerGameResults.FirstOrDefault().Player.Name,
Id = (int?)x.PlayerGameResults.FirstOrDefault().Player.Id
}).FirstOrDefault();
查询语法可以使它看起来不那么复杂:
var result = (from playerGame in dataContext.GetQueryable<PlayedGame>()
where playerGame.Id == testPlayerWithNoPlayedGames.Id
let player = x.PlayerGameResults.FirstOrDefault().Player
select new
{
Name = player.Name,
Id = (int?)player.Id
}).FirstOrDefault();
答案 1 :(得分:1)
如果我理解正确,那么Player
和PlayedGame
通过PlayerGameResult
表格之间存在多对多关系。
您要实现的目标是从PlayerGameResults
表中获取任何关系,以获取特定播放器。
为什么不这样做呢?为此,您必须通过其播放器查询PlayerGameResults
表过滤,如下所示:
var result = dataContext.GetQueryable<PlayerGameResults>()
.Where(player => player.Player.Id == testPlayerWithNoPlayedGames.Id)
.Select(x => new {
Name = x.Player.Name,
Id = x.Player.Id,
})
.FirstOrDefault();
根据评论讨论更新
由于我们基本上使用数据库,而不是使用C#(虽然仍在编写,但我会说“带有C#令牌的SQL”),我们必须考虑数据库术语。
这就是为什么必须理解你是从表中选择特定行,并且基本上根据你描述的内容从多对多关系表PlayerGameResult
中选择行的逻辑{/ 1}
接受答案的样本是一个很好的“坏”样本,所以我将基于它来指代gist you shared(稍微格式化):
.Select(x => new AchievementRelatedPlayedGameSummary
{
//--only pull records where the Player had rank -42 (i.e. none of the PlayerGameResults)
WinningPlayerName = x.PlayerGameResults.FirstOrDefault(y => y.GameRank == -42).Player.Name,
WinningPlayerId = x.PlayerGameResults.FirstOrDefault(y => y.GameRank == -42).Player.Id
})
这里我们两次引用(我们可能认为)同一行x.PlayerGameResults.FirstOrDefault(y => y.GameRank == -42).Player
,但实际情况是,将为每行生成单独的SQL 。从你的要点再次抽样。
这个为Player.Name(结果为[Project2].[Name]
):
OUTER APPLY (SELECT TOP (1)
[Extent2].[PlayerId] AS [PlayerId]
FROM [dbo].[PlayerGameResult] AS [Extent2]
WHERE ([Filter1].[Id] = [Extent2].[PlayedGameId]) AND (-42 = [Extent2].[GameRank]) ) AS [Limit1]
LEFT OUTER JOIN [dbo].[Player] AS [Extent3] ON [Limit1].[PlayerId] = [Extent3].[Id]) AS [Project2]
这个为Player.Id(结果为[Limit2].[PlayerId]
- 看看EF如何优化不加入表格):
OUTER APPLY (SELECT TOP (1)
[Extent4].[PlayerId] AS [PlayerId]
FROM [dbo].[PlayerGameResult] AS [Extent4]
WHERE ([Project2].[Id] = [Extent4].[PlayedGameId]) AND (-42 = [Extent4].[GameRank]) ) AS [Limit2]
这里有恶魔 - 因为我们有无序的SQL,SELECT TOP(1)
将返回任何记录。 完全没有保证这两个结果将基于同一行(感谢EF - 这里我们甚至可以从不同的表中获得结果!),因此您可能会从一个表中收到Id
来自其他Player
的{{1}}和Name
。结果取决于索引的存在和统计累积,db load,SQL server版本及其整体情绪。你不能确定 - 就是这样。
当然我不知道你的特定数据模型,也许Player
的过滤总是会返回单行 - 不管怎么说 - 但总的来说,这不应该基于你的假设。< / p>
所以我仍然建议你根据你选择的实体进行查询,就像这个
GameRank