Open Closed

Best practice to avoid InvalidOperationException while use EntityFrameworkCore #3162


User avatar
0
zhongfang created
  • ABP Framework version: v5.2.2
  • UI type: Blazor
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): no
  • Exception message and stack trace:
[21:15:01 ERR] An exception occurred in the database while saving changes for context type 'Yee.Change.Account.EntityFrameworkCore.AccountDbContext'.
System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.ConcurrencyDetector.EnterCriticalSection()
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.ConcurrencyDetector.EnterCriticalSection()
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Volo.Abp.EntityFrameworkCore.AbpDbContext`1.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Volo.Abp.Uow.UnitOfWork.SaveChangesAsync(CancellationToken cancellationToken)
   at Volo.Abp.Uow.UnitOfWork.CompleteAsync(CancellationToken cancellationToken)
   at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
[21:15:01 WRN] Unhandled exception rendering component: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
   at BootstrapBlazor.Components.Button.<OnInitialized>b__16_0()
   at BootstrapBlazor.Components.BootstrapComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at BootstrapBlazor.Components.BootstrapComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
[21:15:01 ERR] Unhandled exception in circuit 'ihxcWkmkdBt_4qWN9ok7xkBVHfMORbNtZ--JQmxpUWA'.
System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
   at BootstrapBlazor.Components.Button.<OnInitialized>b__16_0()
   at BootstrapBlazor.Components.BootstrapComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at BootstrapBlazor.Components.BootstrapComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
[21:15:01 INF] Executed endpoint '/_blazor'
[21:15:01 INF] Request finished HTTP/1.1 GET https://localhost:44313/_blazor?id=x8MY4ZFA5575PWSz5kjBsQ - - - 101 - - 8460.8296ms
[21:15:01 WRN] Could not save the audit log object:
AUDIT LOG: [---: -------] Yee.Change.Account.SerialNumbers.SerialNumberMoreAppService.GetNextValueAsync
- UserName - UserId                 : admin - 2b080ea8-6fdc-5e40-685e-39fe0f560247
- ClientIpAddress        : 127.0.0.1
- ExecutionDuration      : 733
- Actions:
  - Yee.Change.Account.SerialNumbers.SerialNumberMoreAppService.GetNextValueAsync (729 ms.)
    {}
- Exceptions:
  - Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'AccountDbContext'.
    System.ObjectDisposedException: Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'AccountDbContext'.
   at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
   at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
   at Microsoft.EntityFrameworkCore.DbContext.get_ChangeTracker()
   at Volo.Abp.EntityFrameworkCore.AbpDbContext`1.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Volo.Abp.Domain.Repositories.EntityFrameworkCore.EfCoreRepository`2.InsertAsync(TEntity entity, Boolean autoSave, CancellationToken cancellationToken)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Yee.Change.Account.SerialNumbers.SerialNumberMoreAppService.GetNextValueAsync()
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.GlobalFeatures.GlobalFeatureInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Validation.ValidationInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Auditing.AuditingInterceptor.ProceedByLoggingAsync(IAbpMethodInvocation invocation, IAuditingHelper auditingHelper, IAuditLogScope auditLogScope)
   at Volo.Abp.Auditing.AuditingInterceptor.ProcessWithNewAuditingScopeAsync(IAbpMethodInvocation invocation, AbpAuditingOptions options, ICurrentUser currentUser, IAuditingManager auditingManager, IAuditingHelper auditingHelper, IUnitOfWorkManager unitOfWorkManager)

[21:15:01 ERR] Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it (or one of its parent scopes) has already been disposed.
System.ObjectDisposedException: Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it (or one of its parent scopes) has already been disposed.
   at Autofac.Core.Lifetime.LifetimeScope.BeginLifetimeScope(Object tag)
   at Autofac.Extensions.DependencyInjection.AutofacServiceScopeFactory.CreateScope()
   at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Volo.Abp.AuditLogging.AuditingStore.SaveLogAsync(AuditLogInfo auditInfo)
   at Volo.Abp.AuditLogging.AuditingStore.SaveAsync(AuditLogInfo auditInfo)
  • Steps to reproduce the issue:"
  • I have a module . and provide a function as below. Only return a serial nuber. because the primary key is big intergal.
namespace Yee.Change.Account.SerialNumbers
{
    using System;
    using System.Text;
    using System.Threading.Tasks;
    using System.Collections.Generic;

    public class SerialNumberMoreAppService : AccountAppService, ISerialNumberMoreAppService
    {
        public virtual async Task<long> GetNextValueAsync()
        {
            ISerialNumberRepository repository = this.LazyServiceProvider.LazyGetRequiredService<ISerialNumberRepository>();

            var netSerial = await repository.InsertAsync(new SerialNumber(), autoSave: true);
            await UnitOfWorkManager.Current.SaveChangesAsync();

            return netSerial.Id;
        }
    }
}
  • In another module, I want to call above function to get the serial number. but failed , and throw the exception I mentioned.
namespace Yee.Change.AdminBlazorUI.TempTasks
{
    using Volo.Abp.Data;
    using Volo.Abp.Users;
    using Volo.Abp.MultiTenancy;
    using Yee.Change.Account.SerialNumbers;

    public class UserNumberIdsResetAppService : ApplicationService, IUserNumberIdsResetAppService
    {
        public async Task Reset()
        {
            IIdentityUserRepository userRepository = this.LazyServiceProvider.LazyGetRequiredService<IIdentityUserRepository>();
            ISerialNumberMoreAppService serialNumberMoreAppService = this.LazyServiceProvider.LazyGetRequiredService<ISerialNumberMoreAppService>();

            List<IdentityUser> users;
            using (this.DataFilter.Disable<IMultiTenant>())
            {
                users = await userRepository.GetListAsync(userName: "10093@ok.no");
            }

            users.ForEach(async u =>
            {
                await this.UpdateUser(serialNumberMoreAppService, u);
            });
        }

        async Task UpdateUser(ISerialNumberMoreAppService serialNumberMoreAppService, IdentityUser user)
        {
            long number = await serialNumberMoreAppService.GetNextValueAsync();
            user.SetProperty("NumberId", number);
        }
    }
}

5 Answer(s)
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    See: https://github.com/abpframework/abp/issues/7560#issuecomment-772413041

    Can you try this?

    public class UserNumberIdsResetAppService : ApplicationService, IUserNumberIdsResetAppService
    {
        public async Task Reset()
        {
            IIdentityUserRepository userRepository = this.LazyServiceProvider.LazyGetRequiredService();
            ISerialNumberMoreAppService serialNumberMoreAppService = this.LazyServiceProvider.LazyGetRequiredService();
    
            List users;
            using (this.DataFilter.Disable())
            {
                users = await userRepository.GetListAsync(userName: "10093@ok.no");
            }
            
            foreach(var user in users)
            {
               long number = await serialNumberMoreAppService.GetNextValueAsync();
               user.SetProperty("NumberId", number);
            }
        }
    }
    

    And I see that you are using ISerialNumberMoreAppService in the UserNumberIdsResetAppService , we do not recommend using another app service within an app service, you may need an ISerialNumberMoreManager.

  • User Avatar
    0
    zhongfang created

    Thank you!

    replace Repository with Manager

    public class SerialNumberMoreAppService : AccountAppService, ISerialNumberMoreAppService
        {
            public virtual async Task&lt;long&gt; GetNextValueAsync()
            {
                SerialNumberManager repository = this.LazyServiceProvider.LazyGetRequiredService&lt;SerialNumberManager&gt;();
    
                var netSerial = await repository.CreateAsync("");
                await UnitOfWorkManager.Current.SaveChangesAsync();
    
                return netSerial.Id;
            }
        }
    

    Can get increased id successfully, but ...

    but user.SetProperty only valid for the first user?

    I want to reset propertiy for all users.

    foreach (var user in users)
          {
                    var number = await serialNumberMoreAppService.GetNextValueAsync();
                    user.SetProperty(ChangeUserConsts.NumberIdPropertityName, number);
                    await UnitOfWorkManager.Current.SaveChangesAsync();
          }
    
  • User Avatar
    0
    zhongfang created

    The questions has changed, now there are 2 as below

    1. ExtraPropertity only Saved for the first one

    2. Only return users of current tenant.

    I want to get all users in the database, so I disable the DataFilter. But it seems that the IMultiTenant has not been disabled. I only get the users in the current tenant.

    using (this.DataFilter.Disable&lt;IMultiTenant&gt;())
                {
                    users = await userRepository.GetListAsync();
                }
    
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    ExtraPropertity only Saved for the first one

    You can try to bulk update users:

    foreach (var user in users)
    {
        var number = await serialNumberMoreAppService.GetNextValueAsync();
        user.SetProperty(ChangeUserConsts.NumberIdPropertityName, number);
    }
    repository.UpdateManyAsync(users);
    

    Only return users of current tenant.

    Are you using a separate database for tenants?

    If so , you need to query all tenants and change the current tenant:

    var tenants = await TenantRepository.GetListAsync();
    foreach(var tenant in tenants)
    {
       using(CurrentTenant.Change(tenant.Id))
       {
           var users = ....;
           foreach(var user in users)
           {
              var number = ...
              user.Setproperty....
           }
           repository.UpdateManyAsync(users);
       }
    }
    
  • User Avatar
    0
    zhongfang created

    thank you! all my questions have been resolved.

Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11