【问题标题】:How to filter in Entity Framework Core without loading data on initialisation如何在不加载初始化数据的情况下在 Entity Framework Core 中进行过滤
【发布时间】:2021-12-30 14:15:27
【问题描述】:

我正在使用 Entity Framework Core 使用以下代码从数据库中查询数据:

CamicDbContext context = new CamicDbContext();

public override object Read(DataManagerRequest dm, string key = null)
{
    IQueryable<Country> query = context.Set<Country>();

    query = query.Where(x => x.IsDeleted == false);
    query = query.Where(x => x.Code == "PT");

    int count = query.Cast<Country>().Count();

    return dm.RequiresCounts ? new DataResult() { Result = query, Count = count } : (object)query;
}

当声明query 变量时,它似乎获取了表中的所有项目(图1),这似乎是错误的,因为它对于较大的实体效率低下;因此,我想在不获取数据的情况下初始化变量,构建查询,然后仅在到达return query 时执行查询,这样服务器只会查询并返回与过滤器匹配的项目。

我尝试了多种解决方案。这个 - Creating dynamic queries with Entity Framework - 接近我想要实现的目标,但其中的代码与我上面提到的代码具有相同的效果。我错过了使用延迟执行的东西吗?

我执行了

IQueryable<Country> query = context.Set<Country>();

结果视图显示表中的所有 264 个条目:

【问题讨论】:

  • 我相信在调试模式下查看结果视图会加载IQueryables。如果我没记错的话,在运行时它不应该查询,直到你得到计数。
  • 当你已经有一个IQueryable&lt;Country&gt; 实例时,为什么还要调用Cast&lt;Country&gt;()
  • 声明变量query 不会获取表的全部内容。仅在执行/迭代查询时才检索行。所以你的代码应该已经做了你打算做的事情。如果没有,请添加 minimal reproducible example 以显示您遇到的问题。
  • “结果视图显示...” - 注意到消息“扩展结果视图将枚举 IEnumerable”?枚举意味着执行。
  • @Progman 感谢您的提醒。事实上cast 是不必要的。谢谢你的回答,我错过了@IvanStoev 的那些小信息。

标签: c# asp.net-core entity-framework-core


【解决方案1】:

当查询变量被声明时,它似乎正在获取表中的所有项目(图1),

不是

我错过了什么可以使用延迟执行?

你是

这是我打开登录后测试应用程序的屏幕截图。在测试应用程序的代码中,我创建了一个上下文,构建了一个查询,然后运行它.. 实际上有好几次..

x 是上下文。如果我打开调试器并在调试器工具提示中展开“订单”“表”,底部会显示一条消息,指出查看结果将枚举可枚举。如果我枚举,那么SQL将被执行:

在这方面没有任何子句 - 我们正在枚举代表整个表的 Orders,因此 SQL 运行实际上是 SELECT * FROM Orders。我的 Orders 测试表中没有任何数据,这就是我们在枚举完成后看到“无结果”的原因,但如果有 1000 个订单,调试器工具提示中将包含 1000 个项目


下一行代码运行 LINQ Where

这实际上并没有执行任何 SQL,但它提供了一个 EF 可以 转换为 SQL 的子句,所以如果你查看 IQueryable 返回的 DebugView 属性Where,您可以看到 EF 在运行时将形成的 SQL (...WHERE Ref = '')。

枚举可查询对象实际上会运行 SQL。在调试器中调用结果视图将枚举,就像在您的可查询对象上执行 ToArrayToListToDictionary(在内部它们都枚举),甚至是普通的旧 foreach 等。只要有枚举,就会触发SQL的运行。

如果你使用像 First、Count、Single 这样的东西,那么这些也会触发 SQL 查询的执行;他们不会枚举整个集合,他们会修改正在运行的 SQL,但他们确实会导致执行..

..这让我想到了 cmets 中提出的观点。

我编写的代码使 EF 能够使用 WHERE 子句形成查询,但随后它要求数据库执行两次:一次获取计数(调用 .Count 导致 SELECT COUNT(*) ... WHERE ..)然后再次获取实际的数据项(调用 ToList 执行 SELECT columns ... WHERE ...`):

鉴于您必须检索例如枚举期间有 100 个项目,将它们全部检索然后获取它们的本地计数会更有意义;如果您已将它们隐藏在列表中,则该列表将跟踪计数

请记住,当您使用 EF 的集合并且您还没有做任何事情来枚举它们时,可查询的您;正在传递和调用操作本质上代表一个 SQL(或部分构建的)和每次你对它做一些传递数据的事情时,它都会运行 SQL

【讨论】:

    【解决方案2】:

    LINQ 可以通过以下两种方式之一执行:立即或延迟。 立即执行意味着读取数据源并在代码中声明查询的位置执行操作。所有返回单个不可枚举结果的标准查询运算符都会立即执行,而 Deferred 执行基本上意味着该操作不在代码中声明查询的位置执行。仅在枚举查询变量时执行该操作。

    在调试时扩展和查看IQueryable 将充当即时查询执行,从而将该查询带入内存。在非调试环境中,这不会发生,查询将作为延迟查询执行,这意味着一旦调用 Count() 方法,它将实际执行

    有关按执行方式分类标准查询运算符的更多信息可以在这里找到 - https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/classification-of-standard-query-operators-by-manner-of-execution

    【讨论】:

    • 虽然 Linq 查询将在调用 Count 时执行/解析,但计数查询也将解析为 SQL 中的计数,因此它不会从 AFAIK 表中获取并返回所有对象。 (编辑:哦,现在我看到了 Caius 的回答,他确实这么说)
    【解决方案3】:

    https://weblogs.asp.net/zeeshanhirani/using-asqueryable-with-linq-to-objects-and-linq-to-sql

    1.当您使用 AsQueryable 运算符时,允许进行进一步的转换:过滤、排序或聚合 2.附加的 lambda 语句转换为表达式树。我使用 lambda 函数说明了表达式过滤器。 3.如果您在未实现 IQueryable 且仅实现 IEnumerable 的查询上使用 AsQueryable 运算符,那么您对查询应用的任何转换都将自动回退到 IEnumerable 规范。 4. IQueryable if deferred until a foreach 被调用并且表达式树被评估

     Product[] products =
                        {
                        new Product {ProductId=1, Name="Kayak",Category="Watersports",Price=275m, Company=new Company{ CompanyName="Abc Corp",States=new String[]{"Ut","Id" } }  },
                        new Product {ProductId=2, Name="Lifejacket", Category="Watersports",Price=48.95m, Company=new Company{ CompanyName="X Corp",States=new String[]{"Ca","Az" } }},
                        new Product {ProductId=3, Name="Soccer Ball", Category="Soccer",Price=19.50m, Company=new Company{ CompanyName="Y Corp",States=new String[]{"Tx","Wa" } }},
                        new Product {ProductId=4, Name="Corner Flag", Category="Soccer",Price=34.95m, Company=new Company{ CompanyName="Z Corp",States=new String[]{"Co","Wy" } }}
                         };
                      Transaction[] transactions =
                        {
                            new Transaction{ Name ="Bob Smith", ProductId=1, Cost=10.95M, Quantity=10},
                            new Transaction{ Name ="Dan Brown", ProductId=3, Cost=5.99M, Quantity=1}
                        };
        
        
        
                    IQueryable<Product> query = products.AsQueryable<Product>();
        
                    Expression<Func<Product, bool>> filter = x => x.Price > 100;
                     query = query.Where(filter);
        
                    int count = query.Cast<Product>().Count();
        
                    var result = query.Join(transactions,
                        product => new { product.ProductId },
                        transaction => new { transaction.ProductId },
                        (product, transaction) =>
                        new
                        {
                            CustomerName = transaction.Name,
                            ProductName = product.Name,
                            CompanyName = product.Company.CompanyName,
                            Price = product.Price
                        }) ;
        
                    foreach (var item in result)
                    {
                        _output.WriteLine($"Count: {count} Company: {item.CompanyName} Customer: {item.CustomerName} Product : {item.ProductName} Price : {String.Format("{0:C}", item.Price)}");
                    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-21
      • 2023-03-11
      • 1970-01-01
      • 2023-03-25
      • 2021-08-23
      • 2020-12-25
      • 1970-01-01
      • 2020-06-16
      相关资源
      最近更新 更多