Entity Framework: уже существует открытый DataReader, связанный с этой командой

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

Ответ 1

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

Ответ 2

В качестве альтернативы использованию 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, он больше не использует считыватель данных для открытия списка, он вместо этого находится в памяти.

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

Ответ 3

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

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

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

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

Ответ 4

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

foreach (var user in _dbContext.Users)
{    
}

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

_dbContext.Users.ToList()

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

Ответ 5

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

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

Ответ 6

Попробуйте в строке подключения установить MultipleActiveResultSets=true. Это позволяет многозадачность в базе данных.

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

Это работает для меня... будь то ваше соединение в app.config или вы установили его программно... надеюсь, что это полезно

Ответ 7

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

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

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

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

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

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

  • ОС: Windows Server 2012
  • .NET: Установлено 4.5, Project using 4.0
  • Источник данных: MySQL
  • Структура приложения: MVC3
  • Аутентификация: формы

Ответ 8

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

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

    }
}

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

Ответ 9

Хорошая промежуточная точка между включением 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 (см. здесь и здесь).

Ответ 10

Я обнаружил, что у меня была такая же ошибка, и это произошло, когда я использовал Func<TEntity, bool> вместо Expression<Func<TEntity, bool>> для вашего predicate.

Как только я изменил все Func's на Expression's, исключение остановилось.

Я считаю, что EntityFramwork делает некоторые умные вещи с Expression's, которые он просто не делает с Func's

Ответ 11

2 решения, чтобы смягчить эту проблему:

  1. Принудительно кэшировать память, сохраняя ленивую загрузку с помощью .ToList() после запроса, чтобы вы могли затем выполнить его, открывая новый DataReader.
  2. .Include (/дополнительные сущности, которые вы хотите загрузить в запросе /) это называется активной загрузкой, которая позволяет (действительно) включать связанные объекты (сущности) во время выполнения запроса с помощью DataReader.

Ответ 12

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

Ответ 13

Если мы попытаемся сгруппировать часть наших условий в метод Func < > или extension, мы получим эту ошибку, предположим, что у нас есть такой код:

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(), вместо этого мы должны построить Predicate следующим образом:

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

Ответ 14

Эта проблема может быть решена просто путем преобразования данных в список

 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;
            }

Ответ 15

В моей ситуации проблема возникла из-за регистрации инъекций зависимостей. Я вводил службу на каждую область запроса, которая использовала dbcontext в однонаправленный зарегистрированный сервис. Поэтому dbcontext использовался в нескольких запросах и, следовательно, в ошибке.

Ответ 16

Я решил эту проблему, используя следующий раздел кода перед вторым запросом:

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

вы можете изменить время сна в милисекундах

P.D. Полезно при использовании потоков