【问题标题】:In linq, is comparing by reference or string faster?在 linq 中,按引用比较还是字符串比较快?
【发布时间】:2018-07-31 14:25:56
【问题描述】:

我有一个巨大的查询,我正在尝试提高其性能。它慢的主要原因是因为我使用了很多Includes

我刚刚注意到我正在对我的 Player 类的两个实例进行参考比较:

Where (p => p.Player == player)

这是我的 Player 类:

class player
{
    public string Id { get; set;}
    // other properties
}

改用Where(p => p.Player.Id == player.Id) 会更快吗?

【问题讨论】:

  • 在您的环境中对操作进行计时时的结果是什么?
  • 它们离我太近了,我无法分辨。尽管如此,我只是在使用请求完成时restlet给我的时间。我没有写分析器。我想我只是想知道总的来说,按引用进行比较是否比字符串比较快。
  • 您已经以两种方式编写了代码。如果您想知道哪种方式更快,双向运行,然后您就会知道。在互联网上让随机的人猜测哪个更快不太可能让你得到准确的答案。如果,正如您所指出的,您无法区分差异,那么哪个更快有什么关系?如果您无法区分,那么其他人也无法区分。另外,您说您知道性能问题在哪里,而且不在这里,那您为什么要浪费时间寻找问题不存在
  • 据我所知,EF 中的Db.Players.Where(p => p.Player == player) 无论如何都会给你一个严重的异常
  • 你应该使用最有意义的东西(可能是p.Player.Id

标签: c# entity-framework linq .net-core-2.0


【解决方案1】:

唉,你忘了告诉我们你的背景。看到你的标签entity-framework 我假设你想以IQueryable 执行LINQ 查询,而不是AsEnumerable

但是让我们假设您想知道执行时哪些代码会产生最智能的 SQL 代码。

看来您的Player 是一个实体类,与另一个实体存在一对多(或多对多)关系,例如PlayedGames 每个Player 都有零个或多个PlayedGames , 每个PlayedGame 都被一个Player 玩过

在实体框架中,您可以这样设计:

class Player
{
    public int Id {get; set;}
    public string Name {get; set;}

    // every Player has zero or more PlayedGames
    public virtual ICollection<PlayedGame> PlayedGames{get; set;}
}

class PlayedGame
{
    public int Id {get; set;}
    public int Score {get; set;}

    // every PlayedGame was played by exactly one Player using foreign key
    public int PlayerId{get; set;}
    public virtual Player Player {get; set;}
}

假设,不知何故你有一个播放器。您可以通过多种方式获取此 Player 的 PlayedGames。其中两个与您的代码非常相似:

Player player1 = ...
var gamesOfPlayer = dbContext.PlayedGames
    .Where(game => game.Player == player1)
    .ToList();

或者你可以去:

var gamesOfPlayer = dbContext.PlayedGames
    .Where(game => game.Player.Id == player1.Id)
    .ToList();

您的查询会更详细,但您会明白要点。

还有其他几种对我来说更直观的可能性:

var gamesOfPlayer = dbContext.PlayedGames
    .Where(game => game.PlayerId == player1.Id)
    .ToList();

var gamesOfPlayer = player.PlayedGames;

我个人会选择最后一个解决方案。对我来说,这对我来说似乎是最自然的。但是让我们检查一下实体框架将为此编写的 SQL 代码。

计划变更:
当我尝试使用.Where(game =&gt; game.Player == player1) 执行查询时出现异常。我已经预料到,毕竟 SQL 不知道你什么时候会认为两个玩家是相同的。我希望实体框架可能足够聪明,可以认为用户的意思是Where the player has an Id equal to player1

所以我们必须跳过这个。还有三个。
对于测试,我使用了 Microsoft 的 SQL Server Profiler。对 SQL 进行了一些翻译以使其更具可读性

(1) 使用 Player.Id
.Where(game =&gt; game.Player.Id == player1.Id)
这会导致以下 SQL:

执行选择 PlayedGames.Id 作为 Id, PlayedGames.Score 作为得分 来自 dbo.PlayedGames 作为 PlayedGames 其中playedGames.PlayerId = @constant1

请注意,实体框架足够聪明,可以看到 Player.Id 实际上是外键

(2) 使用外键Played.Id
.Where(game =&gt; game.PlayerId == player1.Id)

exec Select
   PlayedGames.Id as Id,
   PlayedGames.Score as Score

来自 dbo.PlayedGames 作为 PlayedGames 其中playedGames.PlayerId = @constant1

因为前面的代码已经足够聪明地使用外键,所以这段代码导致相同的 SQL 也就不足为奇了

(3) 使用 Player 的属性 PlayedGames
var gamesOfPlayer = player.PlayedGames;

exec Select
    PlayedGames.Id as Id,
    PlayedGames.Score as Score
from dbo.PlayedGames as PlayedGames
where playedGames.PlayerId = @constant1

实体框架未将其转换为来自 Players 和 PLayedGames 的加入!
Entity Framework 检测到我不需要 Player 的任何属性,因此再次使用外键直接转到 PlayedGames 表。

(其实这是我的首选方法,我早就知道会这样)。

结论
- 我不确定你是否能够执行.Where(game =&gt; game.Player == player1)。 - 所有其他方法都将使用外键。它们导致相同的 SQL

我个人会选择看起来最自然的代码。对于那些有 SQL 背景的人,这将是在 Where 语句中带有外键的方法。对于那些正在考虑集合的人(在后台将数据库作为抽象事物),它将是Player.PlayedGames的用户。

我还在代码中对此进行了测试,在该代码中,我使用玩家玩过的游戏获取了玩家的一些属性。同样,三个 Where 方法导致相同的 SQL 语句。

使用 Select 而不是 Include

建议:仅在您打算更改获取的值时才使用 Include

查询数据库的速度较慢的部分之一是将获取的数据传输到本地进程。因此,明智的做法是限制传输的数据量。

如果您使用 Include 获取 a player with his PlayedGames,您将获得 PlayedGames 的所有属性,包括外键 PlayerId,或者您已经知道该值等于获取的 Player.Id。因此,如果您获取 1000 个玩家,每个玩家玩 20 场游戏,您将转移 2 万个您已经知道其值的外键。

除此之外,您可能不打算在提取后使用其他属性。

所以而不是:

var playersWithGames = dbContects.Players
    .Include(player => player.PlayedGames)
    .Where(player => ...)
    .ToList();

在大多数用例中,以下方法会更有效:

var playersWitGames = dbContext.Players
   .Where(player => ...)
   .Select(player => new
   {   // select only the properties you plan to use
       Id = player.Id,
       Name = player.Name,
       // not needed for this query: Birthday, emergency telephone number,
       // bank account, marital status

       Games = player.PlayedGames
           .Where(game => ...) // if you don't need all games
           .Select(game => new
           {
                // not sure if needed: game.Id
                // certainly not needed: game.PlayerId
                Date = game.Date,
                Score = game.Score,
                ...
            })
            .ToList(),
   });

使用获取的结果时,只要您不使用不打算使用的属性,您就不会发现使用 IncludeSelect 有任何区别。

但话又说回来:如果你使用了一个你不打算使用的属性,你会得到一个编译器错误,所以你永远不会转移一个属性意外

结论:仅当您打算更改获取的项目时才使用 Include

我几乎不感谢您每次计划更改Player with all his PlayedGames,您将更改Player 的属性,或者可能是他的PlayedGames 之一的属性,但很少会一次性更改他的几个PlayedGames .

根据我的经验,我很少在一对多关系中使用Include。有时是一对零或一的关系:“用他的地址更改玩家”,尽管通常是:“更改玩家的地址”

【讨论】:

  • 感谢您的描述性回答。我对最后一部分仍然很困惑。不包括 PlayedGames,我们可以直接在 Select 子句中使用它?
  • 是的,你可以。在大多数用例中,我创建了一个匿名类型,我只选择我计划使用的属性。在某些情况下,例如由于返回值,我在我的 select 语句中使用了 new mytype。这需要一个默认构造函数和公共设置属性
猜你喜欢
  • 2013-05-06
  • 2011-06-21
  • 2012-02-09
  • 1970-01-01
  • 2013-02-12
  • 1970-01-01
  • 1970-01-01
  • 2023-03-12
  • 1970-01-01
相关资源
最近更新 更多