这个命令已经关联了一个必须先关闭的开放的DataReader。

790

我有这个查询并且在这个函数中出现了错误:

var accounts = from account in context.Accounts
               from guranteer in account.Gurantors
               select new AccountsReport
               {
                   CreditRegistryId = account.CreditRegistryId,
                   AccountNumber = account.AccountNo,
                   DateOpened = account.DateOpened,
               };

 return accounts.AsEnumerable()
                .Select((account, index) => new AccountsReport()
                    {
                        RecordNumber = FormattedRowNumber(account, index + 1),
                        CreditRegistryId = account.CreditRegistryId,
                        DateLastUpdated = DateLastUpdated(account.CreditRegistryId, account.AccountNumber),
                        AccountNumber = FormattedAccountNumber(account.AccountType, account.AccountNumber)
                    })
                .OrderBy(c=>c.FormattedRecordNumber)
                .ThenByDescending(c => c.StateChangeDate);


public DateTime DateLastUpdated(long creditorRegistryId, string accountNo)
{
    return (from h in context.AccountHistory
            where h.CreditorRegistryId == creditorRegistryId && h.AccountNo == accountNo
            select h.LastUpdated).Max();
}

Error is:

已经存在与此命令相关联的打开的 DataReader,必须先关闭。

更新:

添加了堆栈跟踪:

InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.]
   System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand command) +5008639
   System.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(String method, SqlCommand command) +23
   System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean async) +144
   System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result) +87
   System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method) +32
   System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method) +141
   System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior) +12
   System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior) +10
   System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior) +443

[EntityCommandExecutionException: An error occurred while executing the command definition. See the inner exception for details.]
   System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior) +479
   System.Data.Objects.Internal.ObjectQueryExecutionPlan.Execute(ObjectContext context, ObjectParameterCollection parameterValues) +683
   System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption) +119
   System.Data.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator() +38
   System.Linq.Enumerable.Single(IEnumerable`1 source) +114
   System.Data.Objects.ELinq.ObjectQueryProvider.<GetElementFunction>b__3(IEnumerable`1 sequence) +4
   System.Data.Objects.ELinq.ObjectQueryProvider.ExecuteSingle(IEnumerable`1 query, Expression queryRoot) +29
   System.Data.Objects.ELinq.ObjectQueryProvider.System.Linq.IQueryProvider.Execute(Expression expression) +91
   System.Data.Entity.Internal.Linq.DbQueryProvider.Execute(Expression expression) +69
   System.Linq.Queryable.Max(IQueryable`1 source) +216
   CreditRegistry.Repositories.CreditRegistryRepository.DateLastUpdated(Int64 creditorRegistryId, String accountNo) in D:\Freelance Work\SuperExpert\CreditRegistry\CreditRegistry\Repositories\CreditRegistryRepository.cs:1497
   CreditRegistry.Repositories.CreditRegistryRepository.<AccountDetails>b__88(AccountsReport account, Int32 index) in D:\Freelance Work\SuperExpert\CreditRegistry\CreditRegistry\Repositories\CreditRegistryRepository.cs:1250
   System.Linq.<SelectIterator>d__7`2.MoveNext() +198
   System.Linq.Buffer`1..ctor(IEnumerable`1 source) +217
   System.Linq.<GetEnumerator>d__0.MoveNext() +96

我在为填充ASP.NET MVC Kendo UI数据网格而编写一个查询时遇到了这个错误,我不小心为每一行的初始查询添加了额外的查询。请参见我的类似答案。解决方案是摆脱每行的额外查询。这也大大提高了性能。 - Uwe Keim
21个回答

1538

如果你在迭代另一个查询的结果时执行了一个查询,就会发生这种情况。由于示例不完整,所以很难确定这种情况发生的位置。

可能引起此问题的原因之一是,在迭代某些查询结果时触发了惰性加载。

通过在连接字符串中允许MARS来轻松解决此问题。在连接字符串的提供程序部分(指定Data Source、Initial Catalog等位置)中添加MultipleActiveResultSets=true


47
这对我很有效。如果您想了解更多有关启用多个活动结果集(MARS)的信息,请参阅http://msdn.microsoft.com/en-us/library/h32h3abf(v=vs.100).aspx。也考虑阅读有关MARS缺点的内容:https://dev59.com/N3RC5IYBdhLWcg3wOOP1 - Diganta Kumar
5
考虑到性能,您可以通过包含 System.Data.Entity 并使用 Include 语句来解决这个问题,以确保原始查询中加载了这些辅助数据。如果启用了 MARS,则关闭它以检查这些重复数据加载可以通过减少往返次数来加快数据处理调用的速度。 - Chris Moschini
83
启用MARS只应针对非常少量的问题/用例进行。在大多数情况下,引起问题的错误是由调用应用程序中的糟糕代码造成的。更多细节请参见:http://devproconnections.com/development/solving-net-scalability-problem - Michael K. Campbell
168
在 your.Include().Where() 后添加 .ToList() 很可能会解决这个问题。 - Serj Sagan
5
为了一个查询而全局更改SQL连接是荒谬的。正确的答案应该是下面的ToList方法。针对本地化问题的本地修复(即仅更改查询)! - bytedev
显示剩余17条评论

279

return 语句之前,你可以使用 ToList() 方法。

var accounts =
from account in context.Accounts
from guranteer in account.Gurantors

select new AccountsReport
{
    CreditRegistryId = account.CreditRegistryId,
    AccountNumber = account.AccountNo,
    DateOpened = account.DateOpened,
};

return accounts.AsEnumerable()
       .Select((account, index) => new AccountsReport()
       {
           RecordNumber = FormattedRowNumber(account, index + 1),
           CreditRegistryId = account.CreditRegistryId,
           DateLastUpdated = DateLastUpdated(account.CreditRegistryId, account.AccountNumber),
            AccountNumber = FormattedAccountNumber(account.AccountType, account.AccountNumber)
       })
       .OrderBy(c=>c.FormattedRecordNumber)
       .ThenByDescending(c => c.StateChangeDate)
       .ToList();


public DateTime DateLastUpdated(long creditorRegistryId, string accountNo)
{
    var dateReported = (from h in context.AccountHistory
                        where h.CreditorRegistryId == creditorRegistryId && h.AccountNo == accountNo
                        select h.LastUpdated).Max();
    return dateReported;
}

19
我现在已经遇到了这个错误很多次......每次都会忘记!回答这个问题的方法总是使用ToList()函数。 - Cheesus Toast
7
有没有任何不利之处?如果你有10万行资料,我怀疑这样做是否明智。 - Martin Dawson
2
@MartinMazzaDawson,你真的需要一次查询执行100K条记录吗?我认为,在这种情况下使用分页是一个好主意。 - kazem
对我来说可以。添加.ToList解决了JetEntityFrameworkProvider中十进制支持问题的问题。Total = storeDb.OF_Carts.Where(x => x.CartId == ShoppingCartId).ToList().Sum(t => t.Quantity * t.Item.UnitPrice); - hubert17
2
因为.ToList()会导致iEnumerable实际上从数据库中获取数据并填充自身,这会遍历整个记录集,并(我认为)关闭它。因为连接字符串中未设置MARS,所以一次只能“打开”一个记录集。这意味着,“获取数据并将其存储在我的对象中,然后关闭,以便我的代码的其他部分可以在必要时访问数据库。”这显然是一个非常高级的描述,可能在语义上有缺陷。但就人类友好的定义而言,它是准确的。 - Matt Dawdy
显示剩余3条评论

56

使用语法.ToList()将从数据库中读取的对象转换为列表,以避免再次被读取。


39

这是一个工作连接字符串的示例,供需要参考的人使用。

<connectionStrings>
  <add name="IdentityConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\IdentityDb.mdf;Integrated Security=True;MultipleActiveResultSets=true;" providerName="System.Data.SqlClient" />
</connectionStrings>

28
启用MARS是解决问题的权宜之计,而非根本解决方案。 - SandRock
9
来自MARS文档页面的内容:“MARS操作不是线程安全的。” 这意味着,如果问题源于多个线程访问上下文,那么MARS(很可能)不是解决方案。 - marsop
"MultipleActiveResultSets=true" 运行得非常好。 - agileDev

25

在我的情况下,使用Include()解决了这个错误,并且根据情况,与发出多个查询相比,可以更加高效地一次性查询所有内容。

IEnumerable<User> users = db.Users.Include("Projects.Tasks.Messages");

foreach (User user in users)
{
    Console.WriteLine(user.Name);
    foreach (Project project in user.Projects)
    {
        Console.WriteLine("\t"+project.Name);
        foreach (Task task in project.Tasks)
        {
            Console.WriteLine("\t\t" + task.Subject);
            foreach (Message message in task.Messages)
            {
                Console.WriteLine("\t\t\t" + message.Text);
            }
        }
    }
}

如果您的应用程序不需要MARS,那么这是最佳解决方案。 - Fred Wilson

11

我不知道这是否是重复的答案。如果是的话,我很抱歉。我只想让需要的人知道我如何使用 ToList() 解决了我的问题。

在我的情况下,我对以下查询得到了相同的异常。

int id = adjustmentContext.InformationRequestOrderLinks.Where(
             item => item.OrderNumber == irOrderLinkVO.OrderNumber 
                  && item.InformationRequestId == irOrderLinkVO.InformationRequestId)
             .Max(item => item.Id);

我像下面这样解决了问题

List<Entities.InformationRequestOrderLink> links = 
      adjustmentContext.InformationRequestOrderLinks
           .Where(item => item.OrderNumber == irOrderLinkVO.OrderNumber 
                       && item.InformationRequestId == irOrderLinkVO.InformationRequestId)
           .ToList();

int id = 0;

if (links.Any())
{
  id = links.Max(x => x.Id);
}
if (id == 0)
{
//do something here
}

如果您已经遇到了MARS问题,我建议采用这种方法。 - Shittu Joseph Olugbenga

8

看起来您正在使用同一个EF上下文在活动查询中调用DateLastUpdated,并且DateLastUpdate会向数据存储本身发出命令。Entity Framework每次只支持一个活动命令。

您可以像这样将上述两个查询重构为一个查询:

return accounts.AsEnumerable()
       .Select((account, index) => new AccountsReport()
       {
         RecordNumber = FormattedRowNumber(account, index + 1),
         CreditRegistryId = account.CreditRegistryId,
         DateLastUpdated = (
             from h in context.AccountHistory 
             where h.CreditorRegistryId == creditorRegistryId && h.AccountNo == accountNo 
             select h.LastUpdated
         ).Max(),
         AccountNumber = FormattedAccountNumber(account.AccountType, account.AccountNumber)
       })
       .OrderBy(c=>c.FormattedRecordNumber)
       .ThenByDescending(c => c.StateChangeDate);

我还注意到你在查询中调用了像FormattedAccountNumberFormattedRecordNumber这样的函数。除非这些是存储过程或你从数据库导入到实体数据模型并正确映射的函数,否则它们也会抛出异常,因为EF不知道如何将这些函数转换为可以发送到数据存储中的语句。

此外,请注意,调用AsEnumerable不会强制执行查询。查询执行被延迟直到枚举。如果您希望强制进行枚举,可以使用ToListToArray


如果你想的话,可以重构你正在执行的查询,将DateLastUpdated直接放入Accounts Report查询的选择投影中,从而获得所需的效果而不会出现错误。 - James Alexander
将函数代码放在主查询中后,我仍然遇到相同的错误。 - DotnetSparrow

4
作为一个小提示...当SQL对象内部存在数据映射问题时,这也可能会发生。
例如...
我创建了一个返回 VARCHAR 的 SQL 标量函数...然后...将其用于生成 VIEW 中的一列。VIEW 在 DbContext 中正确映射...所以 Linq 调用正常。然而,Entity 期望 DateTime?而 VIEW 返回 String。
这个奇怪的错误信息是:"There is already an open DataReader associated with this Command which must be closed first"。
很难找出原因...但在我修正返回参数后,一切就好了。

4
在我的情况下,我已经从数据上下文中打开了一个查询,就像这样:
    Dim stores = DataContext.Stores _
        .Where(Function(d) filter.Contains(d.code)) _

...然后随后查询了相同的...

    Dim stores = DataContext.Stores _
        .Where(Function(d) filter.Contains(d.code)).ToList

在第一个已解决的问题上添加.ToList解决了我的问题。我认为将其封装到属性中是有意义的,例如:

Public ReadOnly Property Stores As List(Of Store)
    Get
        If _stores Is Nothing Then
            _stores = DataContext.Stores _
                .Where(Function(d) Filters.Contains(d.code)).ToList
        End If
        Return _stores
    End Get
End Property

_stores是一个私有变量,Filters是一个只读属性,从AppSettings中读取。


2
很有可能这个问题是由Entity Framework的“懒加载”特性引起的。通常情况下,除非在初次获取时显式要求,否则所有连接数据(存储在其他数据库表中的任何内容)都只会在需要时被获取。在许多情况下,这是一件好事,因为它可以防止获取不必要的数据,从而提高查询性能(无连接)并节省带宽。
在描述问题的情况下,执行了初始获取,在“select”阶段请求丢失的懒加载数据,发出了额外的查询,然后EF抱怨“打开DataReader”。
接受答案中提出的解决方法将允许执行这些查询,并且整个请求确实会成功。
但是,如果您检查发送到数据库的请求,您将注意到多个请求-每个缺少的(懒加载)数据的一个附加请求。这可能会导致性能下降。
更好的方法是告诉EF在初始查询期间预加载所有需要的懒加载数据。这可以使用“Include”语句完成:
using System.Data.Entity;

query = query.Include(a => a.LazyLoadedProperty);

这样,所有需要的连接都将被执行,并且所有需要的数据将作为单个查询返回。问题描述中所述的问题将得到解决。


1
这是一个有效的答案,因为我从使用Include转而使用EntityEntry.Collection().Load(),我的解决方案从工作变成了破碎。不幸的是,对于泛型的include不能“ThenInclude”另一个泛型,所以我仍在努力让EntityEntry.Collection().Load()起作用。 - AndrewBenjamin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接