Entity Framework: с этой командой уже есть открытый DataReader

285

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

Sonic Soul
источник
1
Я понимаю, что этот вопрос древний, но мне было бы интересно узнать, какой у вас тип predicateи historicPredicateпеременные. Я обнаружил, что если вы перейдете Func<T, bool>к Where()нему, он будет компилироваться и иногда работать (потому что он делает «где» в памяти). То , что вы должны делать это проходит Expression<Func<T, bool>>в Where().
Джеймс

Ответы:

351

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

Ладислав Мрнка
источник
2
это имело бы смысл. но есть только один выбор в каждом методе.
Sonic Soul
1
@ Соник: вот в чем вопрос. Возможно, выполнено более одной команды, но вы ее не видите. Я не уверен, что это можно отследить в Профилировщике (исключение может быть выдано до выполнения второго считывателя). Вы также можете попробовать привести запрос к ObjectQuery и вызвать ToTraceString, чтобы увидеть команду SQL. Это трудно отследить. Я всегда включаю MARS.
Ладислав Мрнка
2
@Sonic: Нет, я не собирался проверять выполненные и завершенные команды SQL.
Ладислав Мрнка
11
отлично, моей проблемой был второй сценарий: «когда вы выполняете итерацию по результату запроса (IQueryable) и запускаете отложенную загрузку загруженного объекта внутри итерации».
Амр Элгархи
6
Включение MARS, по- видимому, может иметь плохие побочные эффекты: designlimbo.com/?p=235
Сорен Бойзен,
126

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

Джим вольф
источник
7
Это решение сработало для меня. Добавьте .ToList () сразу после запроса и прежде чем делать что-либо еще с результатом.
TJKjaer
9
Будьте осторожны с этим и используйте здравый смысл. Если вы ToListиспользуете тысячу объектов, это увеличит память на тонну. В этом конкретном примере вам лучше объединить внутренний запрос с первым, чтобы генерировался только один запрос, а не два.
kamranicus
4
@subkamran Моя точка зрения была именно такой: думать о чем-то и выбирать то, что подходит для ситуации, а не просто делать. Пример просто случайный, который я придумал объяснить :)
Джим Вольф
3
Определенно, я просто хотел указать это явно для людей, которые
любят
Не стреляйте в меня, но это ни в коем случае не решение вопроса. С каких это пор «извлечение данных в память» является решением проблемы, связанной с SQL? Мне нравится болтать с базой данных, поэтому я ни в коем случае не предпочитаю извлекать что-либо в память «потому что в противном случае возникает исключение SQL». Тем не менее, в предоставленном вами коде нет причин дважды связываться с базой данных. Легко сделать за один звонок. Будьте осторожны с такими сообщениями. ToList, First, Single, ... Следует использовать только тогда, когда данные нужны в памяти (т.е. только те данные, которые вы ХОТИТЕ), а не тогда, когда возникает исключение SQL.
Фредерик Прийк
70

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

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

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

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

Райан Ланди
источник
1
Работал как шарм. .Includeэто гораздо лучшее решение, чем включение MARS, и намного проще, чем написание собственного кода SQL-запроса.
Нолонар
15
Если у кого-то возникла проблема с тем, что вы можете написать только .Include («string»), а не лямбду, вам нужно добавить «using System.Data.Entity», потому что там находится метод расширения.
Джим Вольф
46

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

foreach (var user in _dbContext.Users)
{    
}

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

_dbContext.Users.ToList()

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

Налан Мадхесваран
источник
1
Самое простое решение! Big UP;)
Джейкоб Собус
1
Извлечение неограниченных списков может вызвать серьезные проблемы с производительностью! Как кто-нибудь может отрицать это?
SandRock
1
@ SandRock не для тех, кто работает в небольшой компании - SELECT COUNT(*) FROM Users= 5
Simon_Weaver
5
Подумайте дважды об этом. Молодой разработчик, читающий этот Q / A, может подумать, что это решение на все времена, когда его абсолютно нет. Я предлагаю вам отредактировать свой ответ, чтобы предупредить читателей об опасности получения неограниченных списков из БД.
SandRock
1
@ SandRock Я думаю, что это было бы хорошим местом для вас, чтобы связать ответ или статью, описывающую лучшие практики.
Синджай,
13

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

public class Something : DbContext
{
    public Something(bool MultipleActiveResultSets = false)
    {
        this.Database
            .Connection
            .ConnectionString = Shared.ConnectionString /* your connection string */
                              + (MultipleActiveResultSets ? ";MultipleActiveResultSets=true;" : "");
    }
...
Харви Триана
источник
2
Спасибо. Это работает. Я просто добавил MultipleActiveResultSets = true в строку подключения непосредственно в web.config
Мосхараф Хоссейн
11

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

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

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

Мохамед Хосин
источник
MultipleActiveResultSets = true, добавленный в строку подключения, скорее всего решит проблему. Это не должно быть отвергнуто.
Аарон Худон
да, конечно, я продемонстрировал, как добавить к вашей строке подключения
Мохамед Хосин
4

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

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

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

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

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

раскрытие

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

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

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

    }
}

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

ЕКС
источник
СПАСИБО! Все, что здесь, говорит: «Вложенные циклы - это проблема», но никто не сказал, как это исправить. Я положил ToList()на свой первый звонок, чтобы получить коллекцию из БД. Затем я сделал в foreachэтом списке, и последующие вызовы работали отлично, вместо того, чтобы выдавать ошибку.
AlbatrossCafe
@AlbatrossCafe ... но никто не упоминает, что в этом случае ваши данные будут загружены в память, а запрос будет выполнен в памяти, а не в БД
Lightning3
3

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

После того, как я изменил из всех , Func'sчтобы Expression'sза исключением заканчивалось выброшен.

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

sQuir3l
источник
Это требует больше голосов. Я пытался создать метод в своем классе DataContext, принимая его (MyTParent model, Func<MyTChildren, bool> func)так, чтобы мои ViewModels могли указать определенное whereпредложение к методу Generic DataContext. Ничего не работало, пока я не сделал это.
Джастин
3

2 решения для смягчения этой проблемы:

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

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

Павел
источник
context.SaveChanges();Внутренний цикл :(. Это не хорошо. Он должен быть за пределами цикла.
Джаванд Сингх
1

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

Элайджа Лофгрен
источник
0

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

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;
            }
Дебендра Дэш
источник
ToList () выполняет вызов, но приведенный выше код по-прежнему не располагает соединением. так что ваш _webcontext все еще рискует быть закрытым во время строки 1
Sonic Soul
0

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

Е. Стаал
источник
0

В моем случае проблема не имела ничего общего со строкой соединения MARS, а с сериализацией json. После обновления моего проекта с NetCore2 до 3 я получил эту ошибку.

Более подробную информацию можно найти здесь

OrElse
источник
-6

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

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

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

PD Полезно при использовании потоков

i31nGo
источник
13
Произвольное добавление Thread.Sleep в любое решение является плохой практикой и особенно плохо, когда используется для обхода другой проблемы, когда состояние некоторого значения не совсем понято. Я бы подумал, что «Использование потоков», как указано в нижней части ответа, будет означать, по крайней мере, некоторое базовое понимание потоков - но этот ответ не учитывает никакого контекста, особенно те обстоятельства, когда это очень плохая идея использовать Thread.Sleep - например, в потоке пользовательского интерфейса.
Майк Тур