Core Entity Framework: вторая операция началась в этом контексте до завершения предыдущей операции

Я работаю над проектом ASP.Net Core 2.0 с использованием Entity Framework Core

<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" />
  <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0"/>

И в одном из моих методов списка я получаю эту ошибку:

InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()

Это мой метод:

    [HttpGet("{currentPage}/{pageSize}/")]
    [HttpGet("{currentPage}/{pageSize}/{search}")]
    public ListResponseVM<ClientVM> GetClients([FromRoute] int currentPage, int pageSize, string search)
    {
        var resp = new ListResponseVM<ClientVM>();
        var items = _context.Clients
            .Include(i => i.Contacts)
            .Include(i => i.Addresses)
            .Include("ClientObjectives.Objective")
            .Include(i => i.Urls)
            .Include(i => i.Users)
            .Where(p => string.IsNullOrEmpty(search) || p.CompanyName.Contains(search))
            .OrderBy(p => p.CompanyName)
            .ToPagedList(pageSize, currentPage);

        resp.NumberOfPages = items.TotalPage;

        foreach (var item in items)
        {
            var client = _mapper.Map<ClientVM>(item);

            client.Addresses = new List<AddressVM>();
            foreach (var addr in item.Addresses)
            {
                var address = _mapper.Map<AddressVM>(addr);
                address.CountryCode = addr.CountryId;
                client.Addresses.Add(address);
            }

            client.Contacts = item.Contacts.Select(p => _mapper.Map<ContactVM>(p)).ToList();
            client.Urls = item.Urls.Select(p => _mapper.Map<ClientUrlVM>(p)).ToList();
            client.Objectives = item.Objectives.Select(p => _mapper.Map<ObjectiveVM>(p)).ToList();
            resp.Items.Add(client);
        }

        return resp;
    }

Я немного потерян, потому что он работает, когда я запускаю его локально, но когда я развертываю его на моем промежуточном сервере (IIS 8.5), он получает эту ошибку, и она работает нормально. Ошибка начала появляться после увеличения максимальной длины одной из моих моделей. Я также обновил максимальную длину соответствующей модели просмотра. И есть много других методов списка, которые очень похожи, и они работают.

У меня была работа Hangfire, но эта работа не использует одну и ту же сущность. Это все, что я могу считать релевантным. Любые идеи о том, что может быть причиной этого?

Ответ 1

Исключение означает, что _context используется двумя потоками одновременно; либо два потока в одном запросе, либо по двум запросам.

_context ваш _context объявлен статическим? Так не должно быть.

Или вы вызываете GetClients несколько раз в одном и том же запросе из другого места в вашем коде?

Возможно, вы уже делаете это, но в идеале вы будете использовать внедрение зависимостей для вашего DbContext, что означает, что вы будете использовать AddDbContext() в вашем Startup.cs, и ваш конструктор контроллера будет выглядеть примерно так:

private readonly MyDbContext _context; //not static

public MyController(MyDbContext context) {
    _context = context;
}

Если ваш код не такой, покажите нам, и, возможно, мы сможем помочь в дальнейшем.

Ответ 2

Я не уверен, что вы используете IoC и Dependency Injection для решения вашего DbContext, где бы он ни использовался. Если вы это сделаете, и вы используете собственный IoC из.NET Core (или любого другого IoC-Container), и вы получаете эту ошибку, обязательно зарегистрируйте свой DbContext как переходный. Делать

services.AddTransient<MyContext>();

ИЛИ ЖЕ

services.AddDbContext<MyContext>(ServiceLifetime.Transient);

вместо

services.AddDbContext<MyContext>();

AddDbContext добавляет контекст как область действия, что может вызвать проблемы при работе с несколькими потоками.

Также операции async/await могут вызвать это поведение при использовании асинхронных лямбда-выражений.

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

Ответ 3

Эта ошибка в некоторых случаях вызвана этим сценарием: вы вызываете асинхронный метод, но не используете await перед вызовом метода. Моя проблема решена добавлением await перед методом. однако ответ может не относиться к указанному вопросу, но может помочь в подобной ошибке.

Ответ 4

Я столкнулся с той же проблемой, но причина не была ни одна из перечисленных выше. Я создал задачу, создал область действия внутри задачи и попросил контейнер получить сервис. Это работало нормально, но затем я использовал второй сервис внутри задачи, и я забыл также попросить его в новой области. Из-за этого 2-й сервис использовал DbContext, который уже был удален.

Task task = Task.Run(() =>
    {
        using (var scope = serviceScopeFactory.CreateScope())
        {
            var otherOfferService = scope.ServiceProvider.GetService<IOfferService>();
            // everything was ok here. then I did: 
            productService.DoSomething(); // (from the main scope) and this failed because the db context associated to that service was already disposed.
            ...
        }
    }

Я должен был сделать это:

var otherProductService = scope.ServiceProvider.GetService<IProductService>();
otherProductService.DoSomething();

Ответ 5

У меня была такая же ошибка. Это произошло потому, что я вызвал метод, который был сконструирован как public async void... вместо public async Task...

Ответ 6

Я получил то же сообщение. Но это не имеет никакого смысла в моем случае. Моя проблема в том, что я использовал свойство NotMapped по ошибке. В некоторых случаях это, вероятно, означает только ошибку синтаксиса Linq или класса модели. Сообщение об ошибке кажется вводящим в заблуждение. Первоначальное значение этого сообщения заключается в том, что вы не можете вызывать async для одного и того же dbcontext более одного раза в одном запросе.

[NotMapped]
public int PostId { get; set; }
public virtual Post Post { get; set; }

Вы можете проверить эту ссылку для подробностей, https://www.softwareblogs.com/Posts/Details/5/error-a-second-operation-started-on-this-context-before-a-previous-operation-completed

Ответ 7

У меня есть фоновый сервис, который выполняет действие для каждой записи в таблице. Проблема в том, что если я перебираю и изменяю некоторые данные в одном и том же экземпляре DbContext, эта ошибка возникает.

Одно из решений, как упомянуто в этом потоке, состоит в том, чтобы изменить время жизни DbContext на переходное, определяя его как

services.AddDbContext<DbContext>(ServiceLifetime.Transient);

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

Поскольку мой код выполняется в службе, я делал что-то вроде

using (var scope = Services.CreateScope())
{
   var entities = scope.ServiceProvider.GetRequiredService<IReadService>().GetEntities();
   var writeService = scope.ServiceProvider.GetRequiredService<IWriteService>();
   foreach (Entity entity in entities)
   {
       writeService.DoSomething(entity);
   } 
}

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

using (var readScope = Services.CreateScope())
using (var writeScope = Services.CreateScope())
{
   var entities = readScope.ServiceProvider.GetRequiredService<IReadService>().GetEntities();
   var writeService = writeScope.ServiceProvider.GetRequiredService<IWriteService>();
   foreach (Entity entity in entities)
   {
       writeService.DoSomething(entity);
   } 
}

Таким образом, эффективно используются два разных экземпляра DbContext.

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

Ответ 8

Мне удалось получить эту ошибку, передав IQueryable в метод, который затем использовал этот список IQueryable как часть другого запроса к тому же контексту.

public void FirstMethod()
{
    // This is returning an IQueryable
    var stockItems = _dbContext.StockItems
        .Where(st => st.IsSomething);

    SecondMethod(stockItems);
}

public void SecondMethod(IEnumerable<Stock> stockItems)
{
    var grnTrans = _dbContext.InvoiceLines
        .Where(il => stockItems.Contains(il.StockItem))
        .ToList();
}

Чтобы остановить это, я использовал подход здесь и материализовал этот список перед передачей второго метода, изменив вызов на SecondMethod, чтобы он был SecondMethod(stockItems.ToList()

Ответ 9

У меня была похожая проблема. Я решил свою проблему, переключившись с цикла foreach цикл for.

Ответ 10

Я думаю, что этот ответ все еще может помочь кому-то и сэкономить много раз. Я решил аналогичную проблему, изменив IQueryable на List (или на массив, коллекцию...).

Например:

var list=_context.table1.where(...);

в

var list=_context.table1.where(...).ToList(); //or ToArray()...

Ответ 11

Я получил ту же проблему, когда я пытаюсь использовать FirstOrDefaultAsync() в асинхронном методе в коде ниже. И когда я исправил FirstOrDefault() - проблема была решена!

_context.Issues.Add(issue);
        await _context.SaveChangesAsync();

        int userId = _context.Users
            .Where(u => u.UserName == Options.UserName)
            .FirstOrDefaultAsync()
            .Id;
...

Ответ 12

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

  1. Удалите Hangfire из StartUp (я создавал там свою работу)
  2. Удалена база данных hangfire
  3. Перезагрузили сервер

Я буду исследовать позже, но метод, который я назвал с hangfire, получает DBC-текст, и это возможная причина.