当前位置: 首页 > 知识库问答 >
问题:

Webapi中对DbContext的多并发访问

颛孙高义
2023-03-14

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

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

我使用 DI 和存储库进行网络Api。我定义了IUnitOfWork的范围,我定义了瞬态,但没有任何工作。

这是我的创业公司:

....
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();
        }
    }
}

这是我的工作单元:

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;

                }

            }
        }
    }

这是我的堆栈跟踪:

在微软。微软的EntityFrameworkCore . internal . concurrencydetector . entercriticalsection()。EntityFrameworkCore . change tracking . internal . state manager . d _ _ 61。MoveNext() -从引发异常的上一个位置开始的堆栈跟踪的结尾-在系统中。系统上的runtime . exception services . exceptiondispatchinfo . throw()。系统上的runtime . compiler services . taskawaiter . handlenonsuccessanddebuggernotification(Task Task)。Microsoft的runtime . compiler services . taskawaiter 1 . get result()。EntityFrameworkCore . change tracking . internal . state manager . d _ _ 59。MoveNext() -从引发异常的上一个位置开始的堆栈跟踪的结尾-在系统中。系统上的runtime . exception services . exceptiondispatchinfo . throw()。系统上的runtime . compiler services . taskawaiter . handlenonsuccessanddebuggernotification(Task Task)。Microsoft的runtime . compiler services . taskawaiter 1 . get result()。EntityFrameworkCore . db context . d _ _ 48。MoveNext() -从引发异常的上一个位置开始的堆栈跟踪的结尾-在系统中。系统上的runtime . exception services . exceptiondispatchinfo . throw()。系统上的runtime . compiler services . taskawaiter . handlenonsuccessanddebuggernotification(Task Task)。Eela上的runtime . compiler services . taskawaiter 1 . get result()。Service.LogRepository.d__7。D:\Eela\Eela中的MoveNext()。Service\LogRepository.cs:第41行-从抛出异常的前一个位置开始的堆栈跟踪的结尾-在系统中。系统上的runtime . exception services . exceptiondispatchinfo . throw()。系统上的runtime . compiler services . taskawaiter . handlenonsuccessanddebuggernotification(Task Task)。Eela上的runtime . compiler services . taskawaiter 1 . get result()。web . models . requestloggingmmiddleware . d _ _ 5。D:\Eela\Eela中的MoveNext()。web \ Models \ requestloggingmmiddleware . cs:第82行

更新:

我的创业公司有一个中间件.cs:

app.UseMiddleware<RequestLoggingMiddleware>();

当我对它进行注释时,我的代码可以毫无问题地工作。我包括RequestLoggingMiddleware。我的问题中的cs来源。

主要问题在哪里?

共有1个答案

隆睿
2023-03-14

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

有两种解决方案。第一个是让context fabric在每次调用< code>Invoke方法时创建一个context实例。第二个是将日志记录存储在中间件的一个集合中。并在某种情况下(记录计数达到某个特定数目或超时达到零)将它们保存到数据库。

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

 类似资料:
  • 问题内容: 我知道这不是线程安全的,但是我不确定其确切含义。 在和都使用的情况下,以下哪种情况会引起问题并需要同步? 两个线程同时读取相同的索引 假设您不在乎获取旧元素还是新元素,则替换尝试同时访问的元素。 问题答案: 两个线程同时读取相同的索引 如果 列表是由分支and 的线程构造的,并且列表在线程被分支之前已完全构建并加载,则可以从公共线程读取多个线程。 这样做的原因是,使用线程和派生该线程的

  • 问题内容: 我试图弄清楚如何将对象从表单发布到Web api服务。在我的控制器中,我定义了一个想要向其添加输入值的模型。 在我的输入字段中,我将它们绑定起来,例如: 在提交表单时,将这两个字段传递给我的服务并提交给我的WebApi 我已经通过两种方式尝试了此提交: 并且也如下,不声明参数 在webAPI中,我为帖子设置了一个方法 ReportLabel(dto)的定义如下: 我遇到的问题是,当我从

  • 背景: 我在同一个解决方案中有一个Web API项目和一个类库项目,它们共享相同的模型类。两个项目共享同一个数据库,并且都使用DbContext来读/写数据。 Web API项目以典型的UnitOfWork模式设置,工作正常。 这个班级项目有点不同。我在构造函数中指定连接字符串,而不是在Web中指定。配置文件: 我使用WebAPI调用类库中定义的函数。当我试图使用类库查询数据库时,我得到一个错误。

  • 问题内容: 我知道许多操作系统都在文件系统上执行某种锁定,以防止视图不一致。Java和/或Android是否可以保证文件访问的线程安全性?在我自己编写并发代码之前,我想尽可能多地了解这一点。 如果我错过了回答的类似问题,请随时关闭此线程。谢谢。 问题答案: Android建立在Linux之上,因此继承了Linux的文件系统语义。除非您明确锁定文件,否则多个应用程序和线程可以打开该文件以进行读/写访

  • 问题内容: 在具有并发访问的程序中使用映射时,是否需要在函数中使用互斥体来 读取 值? 问题答案: 读者众多,没有作家可以: https://groups.google.com/d/msg/golang- nuts/HpLWnGTp-n8/hyUYmnWJqiQJ 一个作家,没有读者是可以的。(否则,地图不会太好。) 否则,如果至少有一个作家,而作家或读者至少还有一个,那么 所有 读者 和 作家都

  • 我正试图从web API项目中使用Microsoft.Identity.web库对Microsoft Graph API SDK进行简单调用。 用户已经登录,正在从SPA调用受保护的控制器,一切都正常工作--但是现在,这些控件应该调用图形API,而我没有启动并运行它... 而且 MicrosoftIdentityWebChallengeUserException:IDW10502:由于对用户的质询