Webapi中对DbContext的多次并发访问

时间:2017-11-14 07:40:59

标签: c# entity-framework asp.net-core entity-framework-core asp.net-core-webapi

我有一个WebApi,它使用EFCore2.0和2客户端同时尝试访问一个动作方法...... 一个客户端一切正常。但是当两个或多个同时尝试访问一个特定的操作方法时,我在Microsoft.EntityFrameworkCore上遇到了这个错误:

  

在上一个操作之前,在此上下文中启动了第二个操作   完成。任何实例成员都不保证是线程安全的

我在WebApi中使用了DI和存储库。我为IUnitOfWork定义了Scope,我定义了Transient,但没有任何工作。

这是我的创业公司:

....
services.AddSingleton(provider => Configuration);
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.AddScoped<IUnitOfWork, ApplicationDbContext>();
            services.AddTransient<IRoleService, RoleService>();
            services.AddTransient<ISecurityService, SecurityService>();
            services.AddTransient<IDbInitializerService, DbInitializerService>();
            services.AddTransient<ITokenStoreService, TokenStoreService>();
            services.AddTransient<ITokenValidatorService, TokenValidatorService>();
            services.AddTransient<ICookieValidatorService, CookieValidatorService>();
            services.AddTransient<IRequestRepository, RequestRepository>();
            services.AddDbContextPool<ApplicationDbContext>(options =>
            {
                options.UseSqlServer(
                    Configuration["ConnectionStrings:ApplicationDbContextConnection"].ToString(),
                    serverDbContextOptionsBuilder =>
                    {
                        var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                        serverDbContextOptionsBuilder.CommandTimeout(minutes);
                        serverDbContextOptionsBuilder.EnableRetryOnFailure();
                    });
            });
....

这是我的DbContext:

namespace Eela.Data
{

    public class
        ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
    {


        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var services = new ServiceCollection();
            services.AddOptions();
            services.AddScoped<IHostingEnvironment, CustomHostingEnvironment>();
            services.AddSingleton<ILoggerFactory, LoggerFactory>();
            var serviceProvider = services.BuildServiceProvider();
            var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
            var logger = loggerFactory.CreateLogger<ConfigProvider>();
            var hostingEnvironment = serviceProvider.GetRequiredService<IHostingEnvironment>();
            Console.WriteLine($"Using `{hostingEnvironment.ContentRootPath}` as the ContentRootPath");
            var configuration = new ConfigurationBuilder()
                .SetBasePath(basePath: hostingEnvironment.ContentRootPath)
                .AddJsonFile(path: "appsettings.json", reloadOnChange: true, optional: false)
                .AddEncryptedProvider(hostingEnvironment: hostingEnvironment, logger: logger)
                .AddJsonFile(path: $"appsettings.{hostingEnvironment.EnvironmentName}.json", optional: true)
                .Build();            
            var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
            var connectionString = configuration["ConnectionStrings:ApplicationDbContextConnection"];
            var useInMemoryDatabase = configuration[key: "UseInMemoryDatabase"].Equals(value: "true",
                comparisonType: StringComparison.OrdinalIgnoreCase);
            if (useInMemoryDatabase)
                builder.UseInMemoryDatabase("MyDatabase");
            else
            builder.UseSqlServer(connectionString);
            builder.ConfigureWarnings(warnings => warnings.Log(CoreEventId.IncludeIgnoredWarning));
            return new ApplicationDbContext(builder.Options);
        }
    }

    public class ApplicationDbContext : DbContext, IUnitOfWork
    {

        public ApplicationDbContext(DbContextOptions options) : base(options)
        { }
        protected override void OnModelCreating(ModelBuilder model)
        {
            base.OnModelCreating(model);

            model.Entity<Person>().Property(p => p.PersonId).ValueGeneratedOnAdd();
            model.Entity<Person>()
                .HasDiscriminator<int>(name: "Type")
                .HasValue<WorkerTaxi>(value: Convert.ToInt32(value: AccountType.TaxiWorker))
                .HasValue<User>(value: Convert.ToInt32(value: AccountType.User))
                .HasValue<Reseller>(value: Convert.ToInt32(value: AccountType.Reseller));

            model.Entity<Log>().Property(p => p.Id).ValueGeneratedOnAdd();
            model.Entity<Log>()
                .HasDiscriminator<int>(name: "Type")
                .HasValue<LogRequest>(value: Convert.ToInt32(value: LogLevel.Information))
                .HasValue<LogError>(value: Convert.ToInt32(value: LogLevel.Error));

            model.Entity<Request>().Property(p => p.RequestId).ValueGeneratedOnAdd();
            model.Entity<Request>()
                .HasDiscriminator<int>(name: "Type")
                .HasValue<RequestTaxi>(value: Convert.ToInt32(value: RequestType.TaxiRequester));

            model.Entity<ApplicationUsers>().Property(p => p.Id).ValueGeneratedOnAdd();
            model.Entity<Role>().Property(p => p.RoleId).ValueGeneratedOnAdd();
            model.Entity<Car>().Property(p => p.CarId).ValueGeneratedOnAdd();
            model.Entity<Address>().Property(p => p.AddressId).ValueGeneratedOnAdd();
            model.Entity<Organization>().Property(p => p.OrganizationId).ValueGeneratedOnAdd();
            model.Entity<Credit>().Property(p => p.CreditId).ValueGeneratedOnAdd();
            model.Entity<StablePrice>().Property(p => p.StablePriceId).ValueGeneratedOnAdd();
            model.Entity<Package>().Property(p => p.PackageId).ValueGeneratedOnAdd();
            model.Entity<Rating>().Property(p => p.RatingId).ValueGeneratedOnAdd();
            model.Entity<City>().Property(p => p.CityId).ValueGeneratedOnAdd();
            model.Entity<SpecialAddress>().Property(p => p.SpecialAddressId).ValueGeneratedOnAdd();
            model.Entity<UserToken>().Property(p => p.Id).ValueGeneratedOnAdd();
            model.Entity<PersonRequest>(entity =>
            {
                entity.HasKey(e => new {e.RequestId, e.PersonId})
                    .HasName(name: "PK_dbo.PersonRequest");

                entity.HasIndex(e => e.RequestId)
                    .HasName(name: "IX_RequestId");

                entity.HasIndex(e => e.PersonId)
                    .HasName(name: "IX_PersonId");
            });
            model.Entity<PackageReseller>(entity =>
            {
                entity.HasKey(e => new { e.PackageId, e.ResellerId })
                    .HasName(name: "PK_dbo.PackageReseller");

                entity.HasIndex(e => e.PackageId)
                    .HasName(name: "IX_PackageId");

                entity.HasIndex(e => e.ResellerId)
                    .HasName(name: "IX_ResellerId");
            });
            model.Entity<UserRole>(entity =>
            {
                entity.HasKey(e => new { e.ApplicationUserId, e.RoleId })
                    .HasName(name: "PK_dbo.UserRole");

                entity.HasIndex(e => e.ApplicationUserId)
                    .HasName(name: "IX_ApplicationUserId");

                entity.HasIndex(e => e.RoleId)
                    .HasName(name: "IX_RoleId");
            });
        }
        public virtual DbSet<ApplicationUsers> ApplicationUsers { get; set; }
        public virtual DbSet<Role> Role { get; set; }
        public virtual DbSet<UserRole> UserRole { get; set; }
        public virtual DbSet<UserToken> UserToken { get; set; }

        public virtual DbSet<Address> Address { get; set; }
        public virtual DbSet<Credit> Credit { get; set; }
        public virtual DbSet<Organization> Organization { get; set; }
        public virtual DbSet<City> City { get; set; }
        public virtual DbSet<StablePrice> StablePrice { get; set; }
        public virtual DbSet<PersonRequest> PersonRequest { get; set; }
        public virtual DbSet<Discount> Discount { get; set; }
        public virtual DbSet<Rating> Rating { get; set; }
        public virtual DbSet<SpecialAddress> SpecialAddress { get; set; }


        public void AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class
        {
            Set<TEntity>().AddRange(entities: entities);
        }

        public void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class
        {
            Set<TEntity>().RemoveRange(entities: entities);
        }

        public void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class
        {
            Entry(entity: entity).State = EntityState.Modified; // Or use ---> this.Update(entity);
        }

        public void ExecuteSqlCommand(string query)
        {
            Database.ExecuteSqlCommand(sql: query);
        }

        public void ExecuteSqlCommand(string query, params object[] parameters)
        {
            Database.ExecuteSqlCommand(sql: query, parameters: parameters);
        }

        public int SaveAllChanges()
        {
            return SaveChanges();
        }

        public Task<int> SaveAllChangesAsync()
        {
            return SaveChangesAsync();
        }
    }
}

这是我的IUnitOfWork:

namespace Eela.Data
{
    public interface IUnitOfWork : IDisposable
    {
        DbSet<TEntity> Set<TEntity>() where TEntity : class;

        void AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
        void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;

        EntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
        void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class;

        void ExecuteSqlCommand(string query);
        void ExecuteSqlCommand(string query, params object[] parameters);

        int SaveAllChanges();
        Task<int> SaveAllChangesAsync();

        int SaveChanges(bool acceptAllChangesOnSuccess);
        int SaveChanges();
        Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken());
        Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken());
    }
}

这是我的一个存储库:

public class RequestRepository : IRequestRepository
   {
       private readonly IMapper _mapper;
       private readonly IUnitOfWork _unitOfWork;
       private readonly DbSet<Request> _request;
       private readonly DbSet<Person> _person;
       private readonly DbSet<PersonRequest> _personRequest;
       public RequestRepository(IMapper mapper, IUnitOfWork unitOfWork)
       {
           _mapper = mapper;
           _unitOfWork = unitOfWork;
           _request = _unitOfWork.Set<Request>();
           _person = _unitOfWork.Set<Person>();
           _personRequest = _unitOfWork.Set<PersonRequest>();
       }
     public async Task<DetailPageViewModel> GetRequestAsync(string requestId)
       {
           var request = await (from x in _request
               where x.RequestId == Guid.Parse(requestId)
               from y in x.PersonsRequests
               where y.Person is User
               select new DetailPageViewModel
               {
                   RequestId = x.RequestId.ToString(),
                   CustomerName = y.Person.LastName,
                   SourceAddress = ((RequestTaxi) x).SourceAddress,
                   DestinationAddress = ((RequestTaxi) x).DestinationAddress,
                   DestinationLat = x.DestinationLat,
                   DestinationLon = x.DestinationLon,
                   EstimateDistance = ((RequestTaxi) x).Distance.ToString(CultureInfo.InvariantCulture),
                   EstimateDriverPrice = x.Price.ToString(),
                   EstimatePassengerPrice = x.PaymentType == PaymentType.Cash ? x.Price.ToString() : "0",
                   SourceLat = ((RequestTaxi) x).SourceLat,
                   SourceLon = ((RequestTaxi) x).SourceLon
               }).FirstOrDefaultAsync();

           return
               _mapper.Map<DetailPageViewModel>(
                   source: request);
       }
       .....

最后,这是我的控制者之一:

public class DetailPageController:Controller
   {
       private readonly IPersonRequestRepository _personRequest;
       private readonly IRequestRepository _request;
       private readonly IApplicationUsersRepository _appUser;
       private readonly IStablePriceRepository _stablePrice;
       private readonly ILogRepository _log;
       private readonly ICreditRepository _credit;
       private readonly INotificationService _notification;
       private readonly IPasswordGenerator _charecterGenerator;

       public DetailPageController(IPersonRequestRepository personRequest,ICreditRepository credit,
           ILogRepository log,IStablePriceRepository stablePrice,IApplicationUsersRepository appUser,
           IRequestRepository request,INotificationService notification,IPasswordGenerator charecterGenerator)
       {
           _personRequest = personRequest;
           _credit = credit;
           _log = log;
           _stablePrice = stablePrice;
           _appUser = appUser;
           _request = request;
           _notification = notification;
           _charecterGenerator = charecterGenerator;
       }

       [HttpPost]
       [ActionName("GetRequest")]
       public async Task<ActionResult> GetRequest([FromBody]string model)
       {
           var requestId = model;
           return Json(data: await _request.GetRequestAsync(requestId));
       }

RequestLoggingMiddleware.cs:

public class RequestLoggingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<RequestLoggingMiddleware> _logger;
        private readonly ILogRepository _logRepository;
        private readonly IConfigurationRoot _configuration;

        public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger,
            ILogRepository logRepository,IConfigurationRoot configuration)
        {
            _next = next;
            _logger = logger;
            _logRepository = logRepository;
            _configuration = configuration;
        }

        public async Task<OperationResult> Invoke(HttpContext context)
        {
            using (MemoryStream requestBodyStream = new MemoryStream())
            {
                using (MemoryStream responseBodyStream = new MemoryStream())
                {
                    Stream originalRequestBody = context.Request.Body;
                    context.Request.EnableRewind();
                    Stream originalResponseBody = context.Response.Body;

                    OperationResult op= new OperationResult();
                    try
                    {
                        await context.Request.Body.CopyToAsync(requestBodyStream);
                        requestBodyStream.Seek(0, SeekOrigin.Begin);

                        string requestBodyText = new StreamReader(requestBodyStream).ReadToEnd();

                        requestBodyStream.Seek(0, SeekOrigin.Begin);
                        context.Request.Body = requestBodyStream;

                        string responseBody = "";


                        context.Response.Body = responseBodyStream;
                        Stopwatch watch = Stopwatch.StartNew();
                        await _next(context);
                        watch.Stop();

                        responseBodyStream.Seek(0, SeekOrigin.Begin);
                        responseBody = new StreamReader(responseBodyStream).ReadToEnd();
                        var log = new LogRequestViewModel
                        {
                            Host= context.Request.Host.Host,
                            Path= context.Request.Path,
                            QueryString= context.Request.QueryString.ToString(),
                            ClientIp= context.Connection.RemoteIpAddress.MapToIPv4(),
                            Date= DateTime.Now.ToString(CultureInfo.InvariantCulture),
                            Duration= watch.ElapsedMilliseconds,
                            Method= context.Request.Method,
                            RequestContentLength= context.Request.ContentLength,
                            RequestContentType= context.Request.ContentType,
                            Application= GetType().Namespace,
                            User= context.User.Claims
                                .FirstOrDefault(x => x.Type == _configuration["UserIdType"])?.Value,
                            Headers= string.Join(",", context.Request.Headers.Select(he => he.Key + ":[" + he.Value + "]").ToList()),
                            RequestBodyText= requestBodyText,
                            ResponseBodyText = responseBody

                        };
                        var result = await _logRepository.SaveRequestLogAsync(log);
                        if (!result.Success)
                        {
                            op.Success = false;
                            op.AddMessage("Couldn't add request log to database");
                            _logger.LogError(message: result.MessageList.FirstOrDefault());
                            var ex = new Exception(message: result.MessageList.FirstOrDefault());
                            await _logRepository.SaveErrorLogAsync(exception: ex);
                        }
                        responseBodyStream.Seek(0, SeekOrigin.Begin);

                        await responseBodyStream.CopyToAsync(originalResponseBody);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(message: ex.Message);
                        await _logRepository.SaveErrorLogAsync(exception: ex);
                        byte[] data = System.Text.Encoding.UTF8.GetBytes("Unhandled Error occured, the error has been logged and the persons concerned are notified!! Please, try again in a while.");
                        originalResponseBody.Write(data, 0, data.Length);
                        op.Success = false;
                        op.AddMessage(ex.Message);
                    }
                    finally
                    {
                        context.Request.Body = originalRequestBody;
                        context.Response.Body = originalResponseBody;

                    }
                    const string logTemplate = @"
                        Client IP: {clientIP}
                        Request path: {requestPath}
                        Request content type: {requestContentType}
                        Request content length: {requestContentLength}
                        Start time: {startTime}
                        Duration: {duration}";
                    _logger.LogInformation(logTemplate,
                        context.Connection.RemoteIpAddress.ToString(),
                        context.Request.Path,
                        context.Request.ContentType,
                        context.Request.ContentLength,
                        DateTime.UtcNow,
                        Stopwatch.StartNew());
                    return op;

                }

            }
        }
    }

这是我的堆栈跟踪:

  

在   Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()   在   Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.d__61.MoveNext()   ---从抛出异常的先前位置开始的堆栈跟踪结束--- at   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()at   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)在System.Runtime.CompilerServices.TaskAwaiter1.GetResult()at   Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.d__59.MoveNext()   ---从抛出异常的先前位置开始的堆栈跟踪结束--- at   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()at   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)在System.Runtime.CompilerServices.TaskAwaiter1.GetResult()at   Microsoft.EntityFrameworkCore.DbContext.d__48.MoveNext()   ---从抛出异常的先前位置开始的堆栈跟踪结束--- at   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()at   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)在System.Runtime.CompilerServices.TaskAwaiter1.GetResult()at   Eela.Service.LogRepository.d__7.MoveNext()in   D:\ Eela \ Eela.Service \ LogRepository.cs:第41行---堆栈跟踪结束   从之前的位置抛出异常---在   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()at   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)在System.Runtime.CompilerServices.TaskAwaiter1.GetResult()at   Eela.Web.Models.RequestLoggingMiddleware.d__5.MoveNext()in   D:\ Eela \ Eela.Web \ Models \ RequestLoggingMiddleware.cs:第82行

更新:

我在startup.cs上有一个中间件:

app.UseMiddleware<RequestLoggingMiddleware>();

当我评论它时,我的代码没有任何问题。 我在我的问题中包含RequestLoggingMiddleware.cs源代码。

主要问题在哪里?

1 个答案:

答案 0 :(得分:1)

我的猜测是中间件只被实例化一次。因此,这意味着使用单个上下文实例来执行对数据库的并发访问。

有两种解决方案。第一个是使用上下文结构并在每次调用Invoke方法时创建上下文实例。第二个是将日志记录存储在集合中的中间件中。并在某些条件下将它们保存到数据库(记录的数量达到某个特定数字或超时已达到零)。

根据日志记录的数量,您可能会遇到性能问题。第二种方法需要实现对集合的正确并发访问。在某些情况下,您可能会丢失一些日志记录。