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
Мне просто удалось заставить его работать снова. Это не имеет большого смысла, но это сработало:
- Удалите Hangfire из StartUp (я создавал там свою работу)
- Удалена база данных hangfire
- Перезагрузили сервер
Я буду исследовать позже, но метод, который я назвал с hangfire, получает DBC-текст, и это возможная причина.