【问题标题】:Does Entity Framework query the database multiple times if I use different fields of the same Linq query at different times?如果我在不同时间使用同一个 Linq 查询的不同字段,实体框架是否会多次查询数据库?
【发布时间】:2016-08-30 09:16:43
【问题描述】:

我尝试了 Internet 和 SOF,但找不到有用的资源。也许我可能没有使用正确的措辞进行搜索。如果由于这个原因我之前错过了任何问题,请告诉我,我会记下这个问题。


我正在处理一个繁忙的数据库,因此我需要向数据库发送更少的查询。

如果我从不同级别的代码访问同一 Linq 查询的不同列,那么 Entity Framework 是否足够聪明,可以预见所需的列并将它们全部带入,还是它调用数据库两次?

例如。

var query = from t1 in table_1
            join t2 in table_2 on t1.col1 equals t2.col1
            where t1.EmployeeId == EmployeeId
            group new { t1, t2 } by t1.col2 into grouped
            orderby grouped.Count() descending
            select new { Column1 = grouped.Key, Column2 = grouped.Sum(g=>g.t2.col4) };

var records = query.Take(10);

// point x
var x = records.Select(a => a.Column1).ToArray();

var y = records.Select(a => a.Column2).ToArray();

EF 是否生成两次查询数据库以方便 x 和 y(首先发送查询以获取 Column1,然后发送另一个查询以获取 Column2)还是足够聪明地知道它需要实现两个列并将它们都带入在点 x

添加以澄清问题的意图:

我知道我可以简单地在 query.Take(10) 末尾添加一个贪婪的方法并完成它,但我试图了解我尝试的方法(在我看来,更优雅)是否有效请不要让 EF 进行两次查询。

【问题讨论】:

  • 不明白你在更新中的意思
  • Take(10) 将返回 10 行,而不是列
  • @SarveshMishra Take(10) 不会立即返回 10 行,它会返回一个 IQueryable<T>,在最终执行查询时添加最多 10 条记录。但是,是的,它不会以任何方式更改选定的列,正确。
  • 可能对您来说也很有趣可能是这个问题:stackoverflow.com/questions/32308095/… 这是一个类似的问题,您可以在其中看到如果您使用 EntityFramework 错误会出现哪些重大性能问题(例如,不要使用 @987654325 @ 或在不应该使用的地方使用 ToList()) :)
  • @Menol - 编辑了我的答案。我认为它之前也解释过,但也许现在更清楚为什么它会去 DB 一次或两次?

标签: c# performance entity-framework linq


【解决方案1】:

是的,目前您的代码将生成 2 个查询,这些查询将被执行到数据库。原因是因为您生成了 2 个不同的 sql:

  1. 首先是top query,只取10条记录,然后只取Column1
  2. 第二个是top query,只取10条记录,然后只取Column2

这些是 2 个查询的原因是因为您在不同的 Select 语句上有一个 ToArray -> 生成不同的 sql。大多数 linq 查询的执行方式不同,并且仅在您使用 ToArray()/ToList()/FirstOrDefault() 之类的东西时才会执行 - 那些实际上为您提供具体数据的查询。在您的原始查询中,您有 2 个不同的 ToArray 尚未检索到的数据 - 这意味着 2 个查询(一次用于第一个字段,然后用于第二个字段)。

以下代码将导致对数据库的单个查询

var records = (from t1 in table_1
               join t2 in table_2 on t1.col1 equals t2.col1
               where t1.EmployeeId == EmployeeId
               group new { t1, t2 } by t1.col2 into grouped
               orderby grouped.Count() descending
               select new { Column1 = grouped.Key, Column2 = grouped.Sum(g=>g.t2.col4) })
              .Take(10).ToList();

var x = records.Select(a => a.Column1).ToArray();
var y = records.Select(a => a.Column2).ToArray();

在我上面的解决方案中,我在仅过滤掉您需要的数据 (Take(10)) 之后添加了一个 ToList(),然后它将执行到数据库。然后,您将所有数据都保存在内存中,您可以对其执行任何其他 linq 操作,而无需再次进入数据库。


添加到您的代码ToString(),以便您可以在不同的点检查生成的 sql。然后您将了解执行的时间和内容:

var query = from t1 in table_1
            join t2 in table_2 on t1.col1 equals t2.col1
            where t1.EmployeeId == EmployeeId
            group new { t1, t2 } by t1.col2 into grouped
            orderby grouped.Count() descending
            select new { Column1 = grouped.Key, Column2 = grouped.Sum(g=>g.t2.col4) };
var generatedSql = query.ToString(); // Here you will see a query that brings all records

var records = query.Take(10);
generatedSql = query.ToString(); // Here you will see it taking only 10 records


// point x
var xQuery = records.Select(a => a.Column1);
generatedSql = xQuery.ToString(); // Here you will see only 1 column in query

// Still nothing has been executed to DB at this point

var x = xQuery.ToArray(); // And that is what will be executed here

// Now you are before second execution

var yQuery = records.Select(a => a.Column2);
generatedSql = yQuery.ToString(); // Here you will see only the second column in query

// Finally, second execution, now with the other column

var y = yQuery.ToArray();

【讨论】:

  • 谢谢。我实际上是想了解当我尝试另一种方法时会发生什么(对我来说更优雅)。我很抱歉之前没有更清楚。我现在更新了问题。
  • @Menol - Take 与“何时访问数据库以及访问多少次”没有任何关系
  • 嘿@Gilad Green 感谢您有兴趣帮助我。你能帮我理解为什么 EF 没有把两列都放在 X 点吗?当然它应该能够在生成 IL 时在编译时看到第二列的需要。
  • @Menol - 在X 点它仍然没有带来任何东西,并且当时的指示是带上两列。但是,然后您添加Select,然后添加ToArray。 select 将生成的 sql 更改为仅包含 1 字段,然后 ToArray 执行它。我将编辑我的答案,向您展示如何检查正在发生的事情,以便您更好地理解它
  • 再次感谢,如果我可以再多考虑一下你的大脑,正如你所知,C# 不会逐行解释那么为什么 .NET 和/或 EF 不能看到第二个查询生成稍后实际执行的 IL?换句话说,它是如何工作的(好的,我现在需要带上第 1 列,但我发现我还需要在两行代码之后带上第二列,所以现在让我获取它以避免再次查询)?
【解决方案2】:

当您在 EF 中的实体上运行 linq 语句时,如果只准备 Select 语句(这就是类型为 IQueryable 的原因)。数据是延迟加载的。当您尝试使用该查询中的值时,只会使用枚举器评估结果。

因此,当您将其显式转换为集合(.toList() 等)时,它会尝试获取数据以填充列表,因此会触发 sql 命令。

它旨在提高性能。因此,如果要使用实体的特定属性,EF 不会从该表中获取所有列的值

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-31
    • 2011-12-24
    • 2014-01-25
    • 1970-01-01
    相关资源
    最近更新 更多