实体框架:已经有与此命令关联的打开的DataReader

问题描述 投票:277回答:16

我使用的是Entity Framework,有时我会收到此错误。

EntityCommandExecutionException
{"There is already an open DataReader associated with this Command which must be closed first."}
   at System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands...

即使我没有进行任何手动连接管理。

此错误间歇性地发生。

触发错误的代码(为便于阅读而缩短):

        if (critera.FromDate > x) {
            t= _tEntitites.T.Where(predicate).ToList();
        }
        else {
            t= new List<T>(_tEntitites.TA.Where(historicPredicate).ToList());
        }

使用Dispose模式以便每次都打开新连接。

using (_tEntitites = new TEntities(GetEntityConnection())) {

    if (critera.FromDate > x) {
        t= _tEntitites.T.Where(predicate).ToList();
    }
    else {
        t= new List<T>(_tEntitites.TA.Where(historicPredicate).ToList());
    }

}

仍然有问题

如果连接已经打开,为什么它不会EF重用它。

linq entity-framework sql-server-2008
16个回答
342
投票

这与关闭连接无关。 EF正确管理连接。我对这个问题的理解是,在第一个完成读取之前执行下一个DataReader时,在单个连接上执行多个数据检索命令(或具有多个选择的单个命令)。避免该异常的唯一方法是允许多个嵌套的DataReaders =启用MultipleActiveResultSets。这种情况总是发生的另一种情况是,当您遍历查询结果(IQueryable)时,您将在迭代过程中触发已加载实体的延迟加载。


1
投票
我发现我有相同的错误,当我使用Func<TEntity, bool>代替Expression<Func<TEntity, bool>>作为predicate时发生。

一旦我将所有Func's更改为Expression's,就会停止抛出异常。

[我相信EntityFramworkExpression's做一些聪明的事情,而对Func's根本做不了


1
投票
2个缓解此问题的解决方案:

    强制内存缓存在您执行以下操作后,将延迟加载.ToList()查询,因此您可以打开新的DataReader遍历它。
  1. [.Include(// [要在查询中加载的其他实体 /)称为“预先加载”,它允许您(实际上)包括执行查询过程中的关联对象(实体)DataReader的。

1
投票

0
投票
public static Func<PriceList, bool> IsCurrent() { return p => (p.ValidFrom == null || p.ValidFrom <= DateTime.Now) && (p.ValidTo == null || p.ValidTo >= DateTime.Now); } Or public static IEnumerable<PriceList> IsCurrent(this IEnumerable<PriceList> prices) { .... }

如果我们尝试在Where()中使用它,将会抛出异常,我们应该做的是建立这样的谓词:

public static Expression<Func<PriceList, bool>> IsCurrent()
{
    return p => (p.ValidFrom == null || p.ValidFrom <= DateTime.Now) &&
                (p.ValidTo == null || p.ValidTo >= DateTime.Now);
}

更多内容,请阅读:http://www.albahari.com/nutshell/predicatebuilder.aspx


0
投票
var details = _webcontext.products.ToList(); if (details != null) { Parallel.ForEach(details, x => { Products obj = new Products(); obj.slno = x.slno; obj.ProductName = x.ProductName; obj.Price = Convert.ToInt32(x.Price); li.Add(obj); }); return li; }

0
投票

-6
投票
...first query while (_dbContext.Connection.State != System.Data.ConnectionState.Closed) { System.Threading.Thread.Sleep(500); } ...second query

您可以在几毫秒内改变睡眠时间

Pd积。使用线程时有用

122
投票

或者使用MARS(MultipleActiveResultSets),也可以编写代码,这样就不必打开多个结果集。

您可以做的是将数据检索到内存中,这样就不会打开阅读器。它通常是由于尝试打开另一个结果集时遍历结果集而引起的。

示例代码:

public class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

public class Blog
{
    public int BlogID { get; set; }
    public virtual ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int PostID { get; set; }
    public virtual Blog Blog { get; set; }
    public string Text { get; set; }
}

假设您正在包含以下内容的数据库中进行查找:

var context = new MyContext();

//here we have one resultset
var largeBlogs = context.Blogs.Where(b => b.Posts.Count > 5); 

foreach (var blog in largeBlogs) //we use the result set here
{
     //here we try to get another result set while we are still reading the above set.
    var postsWithImportantText = blog.Posts.Where(p=>p.Text.Contains("Important Text"));
}

我们可以通过添加。ToList()这样的简单解决方案:

var largeBlogs = context.Blogs.Where(b => b.Posts.Count > 5).ToList();

这迫使entityframework将列表加载到内存中,因此当我们在foreach循环中对其进行迭代时,它不再使用数据读取器打开列表,而是在内存中。

例如,如果您要延迟加载某些属性,可能不需要这样做。这主要是一个示例,希望可以解释为什么/为什么会出现此问题,因此您可以做出相应的决定]


67
投票

还有另一种解决这个问题的方法。是否更好的方法取决于您的情况。

问题是由于延迟加载导致的,所以一种避免它的方法是通过使用Include来避免延迟加载:

var results = myContext.Customers
    .Include(x => x.Orders)
    .Include(x => x.Addresses)
    .Include(x => x.PaymentMethods);

如果使用适当的Include,则可以避免启用MARS。但是,如果您错过了一个,则会收到错误消息,因此启用MARS可能是修复此错误的最简单方法。


44
投票

当您尝试迭代的集合是一种延迟加载(IQueriable)时,会出现此错误。

foreach (var user in _dbContext.Users)
{    
}

将IQueriable集合转换为其他可枚举的集合将解决此问题。例子

_dbContext.Users.ToList()

注意:.ToList()每次都会创建一个新集合,如果要处理大数据,可能会导致性能问题。


13
投票

我通过向构造函数添加选项轻松地(实用)解决了这个问题。因此,我仅在需要时使用它。

public class Something : DbContext
{
    public Something(bool MultipleActiveResultSets = false)
    {
        this.Database
            .Connection
            .ConnectionString = Shared.ConnectionString /* your connection string */
                              + (MultipleActiveResultSets ? ";MultipleActiveResultSets=true;" : "");
    }
...

7
投票

尝试输入连接字符串以设置MultipleActiveResultSets=true。这允许在数据库上执行多任务。

Server=yourserver ;AttachDbFilename=database;User Id=sa;Password=blah ;MultipleActiveResultSets=true;App=EntityFramework

这对我有用...无论您是在app.config中还是以编程方式设置了连接...希望对您有所帮助


4
投票

我最初决定在我的API类中使用静态字段来引用MyDataContext对象的实例(其中MyDataContext是EF5 Context对象),但这似乎是造成问题的原因。我向我的每个API方法添加了类似于以下内容的代码,从而解决了该问题。

using(MyDBContext db = new MyDBContext())
{
    //Do some linq queries
}

正如其他人所述,EF数据上下文对象不是线程安全的。因此,将它们放置在静态对象中最终将在正确的条件下导致“数据读取器”错误。

我最初的假设是,仅创建对象的一个​​实例将更加高效,并提供更好的内存管理。从我收集的研究此问题的资料来看,事实并非如此。实际上,将对API的每次调用视为独立的线程安全事件似乎更为有效。当对象超出范围时,请确保正确释放所有资源。

这特别有意义,如果您将您的API带入下一个自然发展阶段,那就是将其公开为WebService或REST API。

    OS:Windows Server 2012
  • 。NET:已安装4.5,使用4.0的项目
  • 数据源:MySQL
  • 应用程序框架:MVC3
  • 身份验证:表格

3
投票
我注意到,当我向视图发送IQueriable并将其在double foreach中使用时,会发生此错误,其中内部foreach也需要使用连接。简单示例(ViewBag.parents可以是IQueriable或DbSet):

foreach (var parent in ViewBag.parents) { foreach (var child in parent.childs) { } }

简单的解决方案是在使用集合之前在集合上使用.ToList()。另请注意,MARS不适用于MySQL。

2
投票
启用MARS和将整个结果集检索到内存之间的良好中间点是,仅在初始查询中检索ID,然后在实现时遍历这些ID来实现每个实体。

例如(使用this answer中的“博客和帖子”示例实体:]

using (var context = new BlogContext()) { // Get the IDs of all the items to loop through. This is // materialized so that the data reader is closed by the // time we're looping through the list. var blogIds = context.Blogs.Select(blog => blog.Id).ToList(); // This query represents all our items in their full glory, // but, items are only materialized one at a time as we // loop through them. var blogs = blogIds.Select(id => context.Blogs.First(blog => blog.Id == id)); foreach (var blog in blogs) { this.DoSomethingWith(blog.Posts); context.SaveChanges(); } }

这样做意味着您只将几千个整数拉入内存,而不是成千上万个整个对象图,这应将内存使用率降至最低,同时使您能够逐项工作而不启用MARS。

此示例的另一个好处是,您可以在遍历每个项目时保存更改,而不必等到循环结束(或其他一些变通方法)即使启用了MARS(请参阅herehere)。

© www.soinside.com 2019 - 2024. All rights reserved.