Entity Framework: с этой командой уже есть открытый DataReader - PullRequest
272 голосов
/ 01 февраля 2011

Я использую 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 повторно не использовать соединение, если оно уже открыто.

Ответы [ 16 ]

339 голосов
/ 02 февраля 2011

Речь не о закрытии соединения. EF правильно управляет подключением. Мое понимание этой проблемы состоит в том, что существует несколько команд извлечения данных, выполняемых для одного соединения (или одной команды с множественным выбором), в то время как следующий DataReader выполняется до того, как первая закончит чтение. Единственный способ избежать исключения - разрешить нескольким вложенным DataReaders = включить MultipleActiveResultSets. Другой сценарий, когда это всегда происходит, - это когда вы перебираете результат запроса (IQueryable) и запускаете отложенную загрузку загруженного объекта внутри итерации.

121 голосов
/ 08 мая 2012

В качестве альтернативы использованию 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();

Это заставляет объектную рамку загружатьсясписок в память, поэтому, когда мы выполняем итерацию, хотя он в цикле foreach, он больше не использует средство чтения данных для открытия списка, а вместо этого находится в памяти.

Я понимаю, что это может быть нежелательно, если вынапример, хотите lazyload некоторых свойств.В основном это пример, который, мы надеемся, объясняет, как / почему вы можете решить эту проблему, чтобы вы могли принимать соответствующие решения

65 голосов
/ 25 марта 2011

Есть еще один способ преодолеть эту проблему. Будет ли это лучше, зависит от вашей ситуации.

Проблема возникает из-за отложенной загрузки, поэтому один из способов избежать ее - не выполнять отложенную загрузку с помощью функции Include:

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

Если вы используете соответствующие Include с, вы можете избежать включения MARS. Но если вы пропустите один, вы получите ошибку, так что включение MARS, вероятно, самый простой способ исправить это.

41 голосов
/ 10 мая 2013

Вы получаете эту ошибку, когда коллекция, которую вы пытаетесь перебрать, загружается медленно (IQueriable).

foreach (var user in _dbContext.Users)
{    
}

Преобразование коллекции IQueriable в другую перечислимую коллекцию решит эту проблему.пример

_dbContext.Users.ToList()

Примечание: .ToList () создает новый набор каждый раз, и это может вызвать проблему производительности, если вы имеете дело с большими данными.

12 голосов
/ 04 декабря 2012

Я решил проблему легко (прагматично), добавив опцию в конструктор. Таким образом, я использую это только при необходимости.

public class Something : DbContext
{
    public Something(bool MultipleActiveResultSets = false)
    {
        this.Database
            .Connection
            .ConnectionString = Shared.ConnectionString /* your connection string */
                              + (MultipleActiveResultSets ? ";MultipleActiveResultSets=true;" : "");
    }
...
5 голосов
/ 09 декабря 2015

попробуйте в строке подключения установить «MultipleActiveResultSets = true», что позволит многозадачность в базе данных.«Сервер = ваш сервер; AttachDbFilename = база данных; Идентификатор пользователя = sa; Пароль = blah; MultipleActiveResultSets = true; App = EntityFramework», это работает для меня ... независимо от того, ваше подключение в app.config или вы установили его программно ...полезно

4 голосов
/ 20 ноября 2013

Первоначально я решил использовать статическое поле в своем классе API для ссылки на экземпляр объекта MyDataContext (где MyDataContext - это объект контекста EF5), но именно это, казалось, создавало проблему.Я добавил код, похожий на следующий, в каждый из моих методов API, и это устранило проблему.

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

Как заявили другие люди, объекты контекста данных EF НЕ являются поточно-ориентированными.Таким образом, размещение их в статическом объекте в конечном итоге приведет к ошибке «чтения данных» при правильных условиях.

Мое первоначальное предположение заключалось в том, что создание только одного экземпляра объекта будет более эффективным и позволит лучше управлять памятью.Из того, что я собрал, исследуя эту проблему, дело не в этом.Фактически, кажется более эффективным рассматривать каждый вызов вашего API как изолированное, поточно-ориентированное событие.Обеспечение правильного высвобождения всех ресурсов, так как объект выходит из области видимости.

Это имеет смысл, особенно если вы переходите от своего API к следующему естественному прогрессу, который будет представлять его как API-интерфейс WebService или REST.

Раскрытие информации

  • ОС: Windows Server 2012
  • .NET: установлено 4.5, проект с использованием 4.0
  • Источник данных:MySQL
  • Прикладная платформа: MVC3
  • Аутентификация: Forms
3 голосов
/ 03 мая 2015

Я заметил, что эта ошибка возникает, когда я отправляю IQueriable в представление и использую его в двойном foreach, где внутренний foreach также должен использовать соединение. Простой пример (ViewBag.parents может быть IQueriable или DbSet):

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

    }
}

Простым решением является использование .ToList() для коллекции перед ее использованием. Также обратите внимание, что MARS не работает с MySQL.

2 голосов
/ 03 ноября 2015

Хорошим промежуточным положением между включением MARS и извлечением всего результирующего набора в память является получение только идентификаторов в начальном запросе, а затем цикл по идентификаторам, материализующим каждую сущность на ходу.

Например,(используя примеры сущностей «Блог и сообщения», как в этот ответ ):

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 (см. здесь и здесь ).

1 голос
/ 09 февраля 2019

В моем случае я обнаружил, что до вызова myContext.SaveChangesAsync () отсутствовали операторы "ожидание".Добавление ожидания до того, как эти асинхронные вызовы устранили для меня проблемы со считывателем данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...