我有一个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源代码。
主要问题在哪里?
答案 0 :(得分:1)
我的猜测是中间件只被实例化一次。因此,这意味着使用单个上下文实例来执行对数据库的并发访问。
有两种解决方案。第一个是使用上下文结构并在每次调用Invoke
方法时创建上下文实例。第二个是将日志记录存储在集合中的中间件中。并在某些条件下将它们保存到数据库(记录的数量达到某个特定数字或超时已达到零)。
根据日志记录的数量,您可能会遇到性能问题。第二种方法需要实现对集合的正确并发访问。在某些情况下,您可能会丢失一些日志记录。