如何在Entity Framework投影中有效地获取远程关系

时间:2017-03-15 16:46:02

标签: c# sql-server entity-framework linq entity-framework-6

我正在尝试编写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表中拉回这两个字段,从而产生一个体面/合理的查询?我不会假装知道上面的查询很糟糕 - 但它对我来说似乎并不像。

期待您的想法!

2 个答案:

答案 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)

如果我理解正确,那么PlayerPlayedGame通过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