Я работаю над проектом 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, но оно не использует тот же объект. Это все, что я считаю актуальным. Есть идеи, что могло быть причиной этого?
c#
entity-framework
asp.net-web-api
Андре Луис
источник
источник
Ответы:
Я не уверен, используете ли вы IoC и Dependency Injection для разрешения своего DbContext, где бы он ни был. Если вы это делаете, и вы используете собственный IoC из .NET Core (или любого другого IoC-контейнера) и получаете эту ошибку, обязательно зарегистрируйте свой DbContext как Transient. Делать
ИЛИ
вместо того
AddDbContext добавляет контекст как ограниченный, что может вызвать проблемы при работе с несколькими потоками.
Также операции async / await могут вызывать это поведение при использовании асинхронных лямбда-выражений.
Добавление его как переходного процесса также имеет свои недостатки. Вы не сможете вносить изменения в какую-либо сущность в нескольких классах, использующих контекст, потому что каждый класс получит свой собственный экземпляр вашего DbContext.
Простое объяснение этому состоит в том, что
DbContext
реализация не является потокобезопасной. Вы можете прочитать об этом здесьисточник
Task.Run(async () => context.Set...)
не дожидаясь этого или создавая контекст db с ограниченным объемом, не дожидаясь результата. Это означает, что ваш контекст, вероятно, уже удален при доступе к нему. Если вы используете Microsoft DI, вы должны сами создать в нем область зависимостиTask.Run
. Также просмотрите эти ссылки. stackoverflow.com/questions/45047877/… docs.microsoft.com/en-us/dotnet/api/…В некоторых случаях эта ошибка возникает при вызове асинхронного метода без
await
ключевого слова, что можно просто решить, добавив егоawait
перед вызовом метода. однако ответ может не иметь отношения к упомянутому вопросу, но может помочь решить аналогичную ошибку.источник
First()
на работуawait / FirstAsync()
.Исключение означает, что
_context
он используется двумя потоками одновременно; либо двумя потоками в одном запросе, либо двумя запросами._context
Возможно, ваша заявленная статичность? Так не должно быть.Или вы вызываете
GetClients
несколько раз один и тот же запрос из другого места в вашем коде?Возможно, вы уже делаете это, но в идеале вы должны использовать инъекцию зависимостей для своего
DbContext
, что означает, что вы будете использоватьAddDbContext()
в своем Startup.cs, и ваш конструктор контроллера будет выглядеть примерно так:private readonly MyDbContext _context; //not static public MyController(MyDbContext context) { _context = context; }
Если у вас другой код, покажите нам, и, возможно, мы сможем помочь.
источник
_context
объект в других потоках? Как внутриTask.Run()
например?await
асинхронные методы. Если вы не используетеawait
, вы можете непреднамеренно попасть в многопоточность.Решите мою проблему, используя эту строку кода в моем файле Startup.cs.
Добавление временной службы означает, что каждый раз, когда служба запрашивается, создается новый экземпляр, когда вы работаете с внедрением зависимостей.
services.AddDbContext<Context>(options => options.UseSqlServer(_configuration.GetConnectionString("ContextConn")), ServiceLifetime.Transient);
источник
У меня была такая же проблема, и оказалось, что родительская служба была одиночкой. Так что контекст тоже автоматически стал синглтоном. Хотя он был объявлен как Per Life Time Scoped в DI.
Внедрение службы с разными сроками жизни в другую
Никогда не вставляйте службы Scoped & Transient в службу Singleton. (Это эффективно преобразует временную или ограниченную службу в синглтон.)
Никогда не вводите временные службы в службу с заданной областью (это преобразует временную службу в службу с заданной областью).
источник
У меня была такая же ошибка. Это произошло потому, что я вызвал метод, построенный как
public async void ...
вместоpublic async Task ...
.источник
Я думаю, что этот ответ еще может кому-то помочь и сэкономить много раз. Я решил аналогичную проблему, перейдя
IQueryable
наList
(или на массив, коллекцию ...).Например:
var list=_context.table1.where(...);
к
var list=_context.table1.where(...).ToList(); //or ToArray()...
источник
Я столкнулся с той же проблемой, но не из перечисленных выше. Я создал задачу, создал область внутри задачи и попросил контейнер получить службу. Это сработало, но затем я использовал вторую службу внутри задачи и забыл также попросить ее для новой области. Из-за этого вторая служба использовала уже удаленный 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();
источник
Entity Framework Core не поддерживает выполнение нескольких параллельных операций в одном
DbContext
экземпляре. Это включает как параллельное выполнениеasync
запросов, так и любое явное одновременное использование из нескольких потоков. Поэтому всегдаawait async
вызывайте немедленно или используйте отдельныеDbContext
экземпляры для операций, которые выполняются параллельно.источник
Моя ситуация иная: я пытался заполнить базу данных 30 пользователями, принадлежащими определенным ролям, поэтому я запускал этот код:
for (var i = 1; i <= 30; i++) { CreateUserWithRole("Analyst", $"analyst{i}", UserManager); }
Это была функция синхронизации. Внутри у меня было 3 звонка:
Когда я заменил
.Result
на.GetAwaiter().GetResult()
, эта ошибка исчезла.источник
Я получил такое же сообщение. Но в моем случае это не имеет никакого смысла. Моя проблема в том, что я по ошибке использовал свойство 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
источник
У меня есть фоновая служба, которая выполняет действие для каждой записи в таблице. Проблема в том, что если я перебираю и изменяю некоторые данные в одном экземпляре DbContext, возникает эта ошибка.
Одно из решений, как упоминалось в этом потоке, состоит в том, чтобы изменить время жизни DbContext на временное, определив его как
но поскольку я вношу изменения в несколько разных служб и фиксирую их сразу, используя
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.
источник
Мне удалось получить эту ошибку, передав
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()
источник
Во-первых, проголосуйте за (как минимум) ответ alsami. Это привело меня на правильный путь.
Но для тех из вас, кто занимается IoC, здесь можно сделать более глубокое погружение.
Моя ошибка (как и у других)
Моя настройка кода. «Только основы» ...
public class MyCoolDbContext: DbContext{ public DbSet <MySpecialObject> MySpecialObjects { get; set; } }
а также
public interface IMySpecialObjectDomainData{}
и (обратите внимание, что MyCoolDbContext вводится)
public class MySpecialObjectEntityFrameworkDomainDataLayer: IMySpecialObjectDomainData{ public MySpecialObjectEntityFrameworkDomainDataLayer(MyCoolDbContext context) { /* HERE IS WHERE TO SET THE BREAK POINT, HOW MANY TIMES IS THIS RUNNING??? */ this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null); } }
а также
public interface IMySpecialObjectManager{}
а также
public class MySpecialObjectManager: IMySpecialObjectManager { public const string ErrorMessageIMySpecialObjectDomainDataIsNull = "IMySpecialObjectDomainData is null"; private readonly IMySpecialObjectDomainData mySpecialObjectDomainData; public MySpecialObjectManager(IMySpecialObjectDomainData mySpecialObjectDomainData) { this.mySpecialObjectDomainData = mySpecialObjectDomainData ?? throw new ArgumentNullException(ErrorMessageIMySpecialObjectDomainDataIsNull, (Exception)null); } }
И, наконец, мой многопоточный класс, вызываемый из консольного приложения (приложение интерфейса командной строки)
public interface IMySpecialObjectThatSpawnsThreads{}
а также
public class MySpecialObjectThatSpawnsThreads: IMySpecialObjectThatSpawnsThreads { public const string ErrorMessageIMySpecialObjectManagerIsNull = "IMySpecialObjectManager is null"; private readonly IMySpecialObjectManager mySpecialObjectManager; public MySpecialObjectThatSpawnsThreads(IMySpecialObjectManager mySpecialObjectManager) { this.mySpecialObjectManager = mySpecialObjectManager ?? throw new ArgumentNullException(ErrorMessageIMySpecialObjectManagerIsNull, (Exception)null); } }
и наращивание DI. (Опять же, это для консольного приложения (интерфейс командной строки) ... которое немного отличается от поведения веб-приложений)
private static IServiceProvider BuildDi(IConfiguration configuration) { /* this is being called early inside my command line application ("console application") */ string defaultConnectionStringValue = string.Empty; /* get this value from configuration */ ////setup our DI IServiceCollection servColl = new ServiceCollection() ////.AddLogging(loggingBuilder => loggingBuilder.AddConsole()) /* THE BELOW TWO ARE THE ONES THAT TRIPPED ME UP. */ .AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>() .AddTransient<IMySpecialObjectManager, MySpecialObjectManager>() /* so the "ServiceLifetime.Transient" below................is what you will find most commonly on the internet search results */ # if (MY_ORACLE) .AddDbContext<ProvisioningDbContext>(options => options.UseOracle(defaultConnectionStringValue), ServiceLifetime.Transient); # endif # if (MY_SQL_SERVER) .AddDbContext<ProvisioningDbContext>(options => options.UseSqlServer(defaultConnectionStringValue), ServiceLifetime.Transient); # endif servColl.AddSingleton <IMySpecialObjectThatSpawnsThreads, MySpecialObjectThatSpawnsThreads>(); ServiceProvider servProv = servColl.BuildServiceProvider(); return servProv; }
Меня удивили переходные (переходные) изменения на
Обратите внимание, я думаю, поскольку IMySpecialObjectManager внедрялся в «MySpecialObjectThatSpawnsThreads», эти внедренные объекты должны были быть временными для завершения цепочки.
Дело в том, что ....... не только (My) DbContext нуждался в .Transient ..., но и в большей части DI Graph.
Совет по отладке:
Эта строка:
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
Поместите там точку останова отладчика. Если ваш MySpecialObjectThatSpawnsThreads создает количество потоков N (например, 10 потоков) ... и эта строка используется только один раз ... это ваша проблема. Ваш DbContext пересекает потоки.
БОНУС:
Я бы посоветовал прочитать ниже URL / статью (старая, но хорошая) о различиях веб-приложений и консольных приложений.
https://mehdi.me/ambient-dbcontext-in-ef6/
Заголовок статьи на случай, если ссылка изменится.
Я столкнулся с этой проблемой с помощью WorkFlowCore https://github.com/danielgerlag/workflow-core
<ItemGroup> <PackageReference Include="WorkflowCore" Version="3.1.5" /> </ItemGroup>
пример кода ниже .. чтобы помочь будущим интернет-поисковикам
namespace MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Workflows { using System; using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Constants; using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Glue; using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.WorkflowSteps; using WorkflowCore.Interface; using WorkflowCore.Models; public class MySpecialObjectInterviewDefaultWorkflow : IWorkflow<MySpecialObjectInterviewPassThroughData> { public const string WorkFlowId = "MySpecialObjectInterviewWorkflowId"; public const int WorkFlowVersion = 1; public string Id => WorkFlowId; public int Version => WorkFlowVersion; public void Build(IWorkflowBuilder<MySpecialObjectInterviewPassThroughData> builder) { builder .StartWith(context => { Console.WriteLine("Starting workflow..."); return ExecutionResult.Next(); }) /* bunch of other Steps here that were using IMySpecialObjectManager.. here is where my DbContext was getting cross-threaded */ .Then(lastContext => { Console.WriteLine(); bool wroteConcreteMsg = false; if (null != lastContext && null != lastContext.Workflow && null != lastContext.Workflow.Data) { MySpecialObjectInterviewPassThroughData castItem = lastContext.Workflow.Data as MySpecialObjectInterviewPassThroughData; if (null != castItem) { Console.WriteLine("MySpecialObjectInterviewDefaultWorkflow complete :) {0} -> {1}", castItem.PropertyOne, castItem.PropertyTwo); wroteConcreteMsg = true; } } if (!wroteConcreteMsg) { Console.WriteLine("MySpecialObjectInterviewDefaultWorkflow complete (.Data did not cast)"); } return ExecutionResult.Next(); })) .OnError(WorkflowCore.Models.WorkflowErrorHandling.Retry, TimeSpan.FromSeconds(60)); } } }
а также
ICollection<string> workFlowGeneratedIds = new List<string>(); for (int i = 0; i < 10; i++) { MySpecialObjectInterviewPassThroughData currentMySpecialObjectInterviewPassThroughData = new MySpecialObjectInterviewPassThroughData(); currentMySpecialObjectInterviewPassThroughData.MySpecialObjectInterviewPassThroughDataSurrogateKey = i; //// private readonly IWorkflowHost workflowHost; string wfid = await this.workflowHost.StartWorkflow(MySpecialObjectInterviewDefaultWorkflow.WorkFlowId, MySpecialObjectInterviewDefaultWorkflow.WorkFlowVersion, currentMySpecialObjectInterviewPassThroughData); workFlowGeneratedIds.Add(wfid); }
источник
В моем случае я использую компонент шаблона в Blazor.
<BTable ID="Table1" TotalRows="MyList.Count()">
Проблема заключается в вызове метода (Count) в заголовке компонента. Чтобы решить проблему, я изменил его так:
int total = MyList.Count();
и позже :
<BTable ID="Table1" TotalRows="total">
источник
Я знаю, что эту проблему задали два года назад, но у меня только что возникла эта проблема, и исправление, которое я использовал, действительно помогло.
Если вы выполняете два запроса с одним и тем же контекстом - вам может потребоваться удалить
AsNoTracking
. Если вы все же используете,AsNoTracking
вы создаете новый считыватель данных для каждого чтения. Два считывателя данных не могут читать одни и те же данные.источник
В моем случае я использовал блокировку, которая не позволяет использовать await и не создает предупреждение компилятора, когда вы не ожидаете async.
Проблема:
lock (someLockObject) { // do stuff context.SaveChangesAsync(); } // some other code somewhere else doing await context.SaveChangesAsync() shortly after the lock gets the concurrency error
Исправление: дождитесь асинхронности внутри блокировки, заблокировав ее с помощью .Wait ()
lock (someLockObject) { // do stuff context.SaveChangesAsync().Wait(); }
источник
Другой возможный случай: если вы используете прямое соединение, не забудьте закрыть if. Мне нужно было выполнить произвольный SQL-запрос и прочитать результат. Это было быстрое исправление, я не хотел определять класс данных, не настраивал «нормальное» соединение SQL. Поэтому я просто повторно использовал соединение с базой данных EFC как
var connection = Context.Database.GetDbConnection() as SqlConnection
. Убедитесь , что вы звоните ,connection.Close()
прежде чем делатьContext.SaveChanges()
.источник
У меня такая же проблема, когда я пытаюсь использовать
FirstOrDefaultAsync()
метод async в приведенном ниже коде. А когда починилFirstOrDefault()
- проблема решилась!_context.Issues.Add(issue); await _context.SaveChangesAsync(); int userId = _context.Users .Where(u => u.UserName == Options.UserName) .FirstOrDefaultAsync() .Id; ...
источник
Если ваш метод что-то возвращает, вы можете решить эту ошибку,
.Result
завершив задание и.Wait()
ничего не вернув .источник
Мне просто удалось заставить его снова работать. В этом нет особого смысла, но это сработало:
Позже я займусь этим дальше, но метод, который я вызвал с помощью hangfire, получает DBContext, и это возможная причина.
источник