You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# 9和.NET 5中Linq GroupJoin报错:存在打开的DataReader

问题:使用EF Core进行GroupJoin时触发"DataReader已打开"异常

运行以下代码时遇到异常:

static void GroupJoinCategoriesAndProducts()
{
    using (var db = new Northwind())
    {
        var queryGroup = db.Categories
            .AsEnumerable()
            .GroupJoin(
                inner: db.Products,
                outerKeySelector: category => category.CategoryId,
                innerKeySelector: product => product.CategoryID,
                resultSelector: (c, matchingProducts) => new
                {
                    c.CategoryName,
                    Products = matchingProducts.OrderBy(p => p.ProductName)
                });

        foreach(var item in queryGroup)
        {
            WriteLine("{0} has {1} products.",
                arg0: item.CategoryName,
                arg1: item.Products.Count());
            foreach (var product in item.Products)
            {
                WriteLine($" {product.ProductName}");
            }
        }
    }
}

触发的异常信息:

System.InvalidOperationException
  HResult=0x80131509
  Message="There is already an open DataReader associated with this Connection which must be closed first."
  Source=Microsoft.Data.SqlClient
  StackTrace:
   em Microsoft.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand command)
   em Microsoft.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(String method, SqlCommand command)
   em Microsoft.Data.SqlClient.SqlCommand.ValidateCommand(Boolean isAsync, String method)
   em Microsoft.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry, String method)
   em Microsoft.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
   em Microsoft.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior)
   em Microsoft.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
   em System.Data.Common.DbCommand.ExecuteReader()
   em Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReader(RelationalCommandParameterObject parameterObject)
   em Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.InitializeReader(DbContext _, Boolean result)
   em Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   em Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
   em System.Linq.Lookup`2.CreateForJoin(IEnumerable`1 source, Func`2 keySelector, IEqualityComparer`1 comparer)
   em System.Linq.Enumerable.<GroupJoinIterator>d__98`4.MoveNext()
   em LinqWithEFCore.Program.GroupJoinCategoriesAndProducts() em C:\Users\benhu\source\repos\TesteLinq\LinqWithEFCore\Program.cs:linha 29
   em LinqWithEFCore.Program.Main(String[] args) em C:\Users\benhu\source\repos\TesteLinq\LinqWithEFCore\Program.cs:linha 91

原因分析

问题出在AsEnumerable()和延迟加载的组合使用:

  • db.Categories.AsEnumerable()会立即把所有分类加载到内存,但db.Products仍是IQueryable,属于延迟加载状态。
  • 遍历每个分类时,访问matchingProducts.Count()和foreach (var product in item.Products)会为每个分类单独发起一次数据库查询,此时之前的DataReader尚未关闭,导致同一连接上存在多个活跃的DataReader,触发异常。

解决方法

方法1:将Products提前加载到内存

把db.Products转为内存集合,让GroupJoin完全在本地执行,避免多次发起数据库查询:

static void GroupJoinCategoriesAndProducts()
{
    using (var db = new Northwind())
    {
        var categories = db.Categories.ToList();
        var products = db.Products.ToList();
        
        var queryGroup = categories
            .GroupJoin(
                inner: products,
                outerKeySelector: category => category.CategoryId,
                innerKeySelector: product => product.CategoryID,
                resultSelector: (c, matchingProducts) => new
                {
                    c.CategoryName,
                    Products = matchingProducts.OrderBy(p => p.ProductName)
                });

        foreach(var item in queryGroup)
        {
            WriteLine("{0} has {1} products.",
                arg0: item.CategoryName,
                arg1: item.Products.Count());
            foreach (var product in item.Products)
            {
                WriteLine($" {product.ProductName}");
            }
        }
    }
}

方法2:使用EF Core关联预加载

利用Category实体的Products导航属性(需提前配置),一次性加载所有分类及其关联产品,减少数据库交互次数:

static void GroupJoinCategoriesAndProducts()
{
    using (var db = new Northwind())
    {
        var queryGroup = db.Categories
            .Include(c => c.Products) // 预加载关联产品
            .Select(c => new
            {
                c.CategoryName,
                Products = c.Products.OrderBy(p => p.ProductName)
            })
            .ToList(); // 一次性加载所有数据到内存

        foreach(var item in queryGroup)
        {
            WriteLine("{0} has {1} products.",
                arg0: item.CategoryName,
                arg1: item.Products.Count());
            foreach (var product in item.Products)
            {
                WriteLine($" {product.ProductName}");
            }
        }
    }
}

方法3:启用MultipleActiveResultSets(MARS)

在数据库连接字符串中添加MultipleActiveResultSets=True,允许同一连接上同时存在多个DataReader,适合需要保留延迟加载的场景:

Server=.;Database=Northwind;Trusted_Connection=True;MultipleActiveResultSets=True;

修改后原代码可正常运行,但该方式会增加连接资源消耗,需根据业务场景选择。


内容的提问来源于stack exchange,提问作者Benhur Azevedo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 18:45:13