Estoy trabajando en un proyecto ASP.Net Core 2.0 usando 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"/>
Y en uno de mis métodos de lista recibo este error:
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()
Este es mi método:
[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;
}
Estoy un poco perdido, especialmente porque funciona cuando lo ejecuto localmente, pero cuando lo implemento en mi servidor de ensayo (IIS 8.5), aparece este error y funciona normalmente. El error comenzó a aparecer después de que aumenté la longitud máxima de uno de mis modelos. También actualicé la longitud máxima del modelo de vista correspondiente. Y hay muchos otros métodos de lista que son muy similares y están funcionando.
Tenía un trabajo de Hangfire en ejecución, pero este trabajo no usa la misma entidad. Eso es todo lo que puedo pensar que es relevante. ¿Alguna idea de qué podría estar causando esto?
fuente
Respuestas:
No estoy seguro de si está utilizando IoC y Dependency Injection para resolver su DbContext donde sea que se pueda usar. Si lo hace y está utilizando IoC nativo de .NET Core (o cualquier otro contenedor de IoC) y recibe este error, asegúrese de registrar su DbContext como Transient. Hacer
O
en vez de
AddDbContext agrega el contexto según el ámbito, lo que puede causar problemas al trabajar con varios subprocesos.
Además, las operaciones asíncronas / de espera pueden causar este comportamiento cuando se utilizan expresiones lambda asíncronas.
Agregarlo como transitorio también tiene sus desventajas. No podrá realizar cambios en alguna entidad en varias clases que estén usando el contexto porque cada clase obtendrá su propia instancia de su DbContext.
La explicación simple para eso es que la
DbContext
implementación no es segura para subprocesos. Puedes leer más sobre esto aquí.fuente
Task.Run(async () => context.Set...)
sin esperarlo o creando un contexto de base de datos con ámbito sin esperar el resultado. Esto significa que su contexto probablemente ya esté eliminado al acceder a él. Si está en Microsoft DI, debe crear un ámbito de dependencia dentro de élTask.Run
. Consulte también estos enlaces. stackoverflow.com/questions/45047877/… docs.microsoft.com/en-us/dotnet/api/…En algunos casos, este error se produce al llamar a un método asíncrono sin la
await
palabra clave, que simplemente se puede resolver agregandoawait
antes de la llamada al método. sin embargo, es posible que la respuesta no esté relacionada con la pregunta mencionada, pero puede ayudar a resolver un error similar.fuente
First()
aawait / FirstAsync()
trabajado.La excepción significa que
_context
está siendo utilizado por dos subprocesos al mismo tiempo; ya sea dos subprocesos en la misma solicitud o por dos solicitudes.¿Es su
_context
estática declarada tal vez? No debería ser así.¿O está llamando
GetClients
varias veces en la misma solicitud desde algún otro lugar de su código?Es posible que ya esté haciendo esto, pero idealmente, estaría usando la inyección de dependencia para su
DbContext
, lo que significa que lo usaráAddDbContext()
en su Startup.cs, y el constructor de su controlador se verá así:private readonly MyDbContext _context; //not static public MyController(MyDbContext context) { _context = context; }
Si su código no es así, enséñenos y tal vez podamos ayudar más.
fuente
_context
objeto en otros hilos? ¿Como dentro de unTask.Run()
por ejemplo?await
métodos asincrónicos. Si no lo usaawait
, puede entrar involuntariamente en subprocesos múltiples.Resuelva mi problema usando esta línea de código en mi archivo Startup.cs.
Agregar un servicio transitorio significa que cada vez que se solicita el servicio, se crea una nueva instancia cuando se trabaja con la inyección de dependencia
services.AddDbContext<Context>(options => options.UseSqlServer(_configuration.GetConnectionString("ContextConn")), ServiceLifetime.Transient);
fuente
Tuve el mismo problema y resultó que el servicio para padres era un singelton. De modo que el contexto también se convirtió automáticamente en singelton. Aunque fue declarado como Per Life Time Scoped en DI.
Inyectando servicios con diferentes vidas útiles en otro
Nunca inyecte servicios Scoped & Transient en el servicio Singleton. (Esto convierte efectivamente el servicio transitorio o de ámbito en el singleton).
Nunca inyecte servicios transitorios en un servicio con ámbito (esto convierte el servicio transitorio en un servicio con ámbito).
fuente
Yo tenía el mismo error. Sucedió porque llamé a un método que se construyó como en
public async void ...
lugar depublic async Task ...
.fuente
Creo que esta respuesta todavía puede ayudar a alguien y salvar muchas veces. Resolví un problema similar cambiando
IQueryable
aList
(o matriz, colección ...).Por ejemplo:
var list=_context.table1.where(...);
a
var list=_context.table1.where(...).ToList(); //or ToArray()...
fuente
Enfrenté el mismo problema, pero el motivo no fue ninguno de los mencionados anteriormente. Creé una tarea, creé un alcance dentro de la tarea y le pedí al contenedor que obtuviera un servicio. Eso funcionó bien, pero luego utilicé un segundo servicio dentro de la tarea y me olvidé de solicitarlo también al nuevo alcance. Por eso, el segundo servicio estaba usando un DbContext que ya estaba eliminado.
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. ... } }
Debería haber hecho esto:
var otherProductService = scope.ServiceProvider.GetService<IProductService>(); otherProductService.DoSomething();
fuente
Entity Framework Core no admite la ejecución de varias operaciones paralelas en la misma
DbContext
instancia. Esto incluye tanto la ejecución paralela deasync
consultas como cualquier uso simultáneo explícito de varios subprocesos. Por lo tanto, siempreawait async
llame inmediatamente o useDbContext
instancias separadas para operaciones que se ejecuten en paralelo.fuente
Mi situación es diferente: estaba tratando de sembrar la base de datos con 30 usuarios, pertenecientes a roles específicos, así que estaba ejecutando este código:
for (var i = 1; i <= 30; i++) { CreateUserWithRole("Analyst", $"analyst{i}", UserManager); }
Esta fue una función de sincronización. Dentro de él tuve 3 llamadas a:
Cuando reemplacé
.Result
con.GetAwaiter().GetResult()
, este error desapareció.fuente
Recibí el mismo mensaje. Pero no tiene ningún sentido en mi caso. Mi problema es que utilicé una propiedad "NotMapped" por error. Probablemente solo signifique un error de sintaxis Linq o clase de modelo en algunos casos. El mensaje de error parece engañoso. El significado original de este mensaje es que no puede llamar a async en el mismo dbcontext más de una vez en la misma solicitud.
[NotMapped] public int PostId { get; set; } public virtual Post Post { get; set; }
Puede consultar este enlace para obtener más detalles, https://www.softwareblogs.com/Posts/Details/5/error-a-second-operation-started-on-this-context-before-a-previous-operation-completed
fuente
Tengo un servicio en segundo plano que realiza una acción para cada entrada en una tabla. El problema es que si repito y modifico algunos datos en la misma instancia de DbContext, se produce este error.
Una solución, como se menciona en este hilo, es cambiar la vida útil de DbContext a transitoria definiéndola como
pero debido a que hago cambios en múltiples servicios diferentes y los confirmo a la vez usando el
SaveChanges()
método, esta solución no funciona en mi caso.Debido a que mi código se ejecuta en un servicio, estaba haciendo algo como
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); } }
para poder utilizar el servicio como si fuera una simple solicitud. Entonces, para resolver el problema, simplemente dividí el alcance único en dos, uno para la consulta y el otro para las operaciones de escritura de la siguiente manera:
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); } }
Así, efectivamente hay dos instancias diferentes del DbContext que se está utilizando.
Otra posible solución sería asegurarse de que la operación de lectura haya terminado antes de comenzar la iteración. Eso no es muy práctico en mi caso porque podría haber muchos resultados que tendrían que cargarse en la memoria para la operación que traté de evitar usando un Queryable en primer lugar.
fuente
Logré obtener ese error pasando un
IQueryable
método que luego usó esa 'lista' IQueryable como parte de otra consulta en el mismo contexto.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(); }
Para evitar que eso suceda, utilicé el enfoque aquí y materialicé esa lista antes de pasarle el segundo método, cambiando la llamada
SecondMethod
a serSecondMethod(stockItems.ToList()
fuente
Primero, vote a favor (al menos) la respuesta de alsami. Eso me puso en el camino correcto.
Pero para aquellos de ustedes que hacen IoC, aquí hay un análisis un poco más profundo.
Mi error (igual que los demás)
Mi configuración de código. "Solo lo básico" ...
public class MyCoolDbContext: DbContext{ public DbSet <MySpecialObject> MySpecialObjects { get; set; } }
y
public interface IMySpecialObjectDomainData{}
y (tenga en cuenta que se está inyectando 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); } }
y
public interface IMySpecialObjectManager{}
y
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); } }
Y finalmente, mi clase de subprocesos múltiples, que se llama desde una aplicación de consola (aplicación de interfaz de línea de comandos)
public interface IMySpecialObjectThatSpawnsThreads{}
y
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); } }
y la acumulación de DI. (Nuevamente, esto es para una aplicación de consola (interfaz de línea de comandos) ... que exhibe un comportamiento ligeramente diferente al de las aplicaciones web)
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; }
Los que me sorprendieron fueron los (cambiar a) transitorios para
Tenga en cuenta que creo que debido a que IMySpecialObjectManager se estaba inyectando en "MySpecialObjectThatSpawnsThreads", esos objetos inyectados debían ser Transitorios para completar la cadena.
El punto es ....... no era solo el (Mi) DbContext que necesitaba .Transient ... sino una parte más grande del DI Graph.
Consejo de depuración:
Esta línea:
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
Ponga su punto de interrupción del depurador allí. Si su MySpecialObjectThatSpawnsThreads está haciendo N número de subprocesos (digamos 10 subprocesos, por ejemplo) ... y esa línea solo se golpea una vez ... ese es su problema. Su DbContext está cruzando hilos.
PRIMA:
Sugeriría leer este artículo / URL a continuación (antiguo pero bueno) sobre las diferencias entre aplicaciones web y aplicaciones de consola
https://mehdi.me/ambient-dbcontext-in-ef6/
Aquí está el encabezado del artículo en caso de que el enlace cambie.
Encontré este problema con WorkFlowCore https://github.com/danielgerlag/workflow-core
<ItemGroup> <PackageReference Include="WorkflowCore" Version="3.1.5" /> </ItemGroup>
código de muestra a continuación ... para ayudar a los futuros usuarios de Internet
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)); } } }
y
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); }
fuente
En mi caso, uso un componente de plantilla en Blazor.
<BTable ID="Table1" TotalRows="MyList.Count()">
El problema es llamar a un método (Count) en el encabezado del componente. Para resolver el problema, lo cambié así:
int total = MyList.Count();
y después :
<BTable ID="Table1" TotalRows="total">
fuente
Sé que este problema se planteó hace dos años, pero acabo de tener este problema y la solución que utilicé realmente ayudó.
Si realiza dos consultas con el mismo contexto, es posible que deba eliminar el archivo
AsNoTracking
. Si lo usaAsNoTracking
, está creando un nuevo lector de datos para cada lectura. Dos lectores de datos no pueden leer los mismos datos.fuente
En mi caso, estaba usando un bloqueo que no permite el uso de await y no crea una advertencia del compilador cuando no esperas un async.
El problema:
lock (someLockObject) { // do stuff context.SaveChangesAsync(); } // some other code somewhere else doing await context.SaveChangesAsync() shortly after the lock gets the concurrency error
La solución: espere el async dentro del bloqueo bloqueándolo con un .Wait ()
lock (someLockObject) { // do stuff context.SaveChangesAsync().Wait(); }
fuente
Otro caso posible: si usa la conexión directa, no olvide cerrar if. Necesitaba ejecutar una consulta SQL arbitraria y leer el resultado. Esta fue una solución rápida, no quería definir una clase de datos, ni configurar una conexión SQL "normal". Así que simplemente reutilicé la conexión de la base de datos de EFC como
var connection = Context.Database.GetDbConnection() as SqlConnection
. Asegúrese de llamarconnection.Close()
antes de hacerloContext.SaveChanges()
.fuente
Tengo el mismo problema cuando intento usar
FirstOrDefaultAsync()
el método asíncrono en el código siguiente. Y cuando lo arregléFirstOrDefault()
, ¡el problema se resolvió!_context.Issues.Add(issue); await _context.SaveChangesAsync(); int userId = _context.Users .Where(u => u.UserName == Options.UserName) .FirstOrDefaultAsync() .Id; ...
fuente
Si su método devuelve algo, puede resolver este error poniendo
.Result
al final del trabajo y.Wait()
si no devuelve nada.fuente
Me las arreglé para hacerlo funcionar de nuevo. No tiene mucho sentido pero funcionó:
Investigaré más adelante, pero el método que llamé con hangfire recibe un DBContext y esa es la posible causa.
fuente