Azure Functions中Cosmos DB Provider与MediatR的DbContext线程问题
在Azure Functions中使用Entity Framework Core的Cosmos DB提供程序,当同时调用两个HTTP触发的函数时,收到DbContext被其他线程访问的异常。已尝试在AddDbContext()中设置Scoped和Transient生命周期,但问题仍然存在。
现有配置
- 使用
AddDbContext()注册并配置DbContext - UnitOfWork类依赖该DbContext,注册为Scoped服务
- 多个MediatR的RequestHandler类依赖UnitOfWork,通过
RegisterServicesFromAssembly()注册
Program.cs
var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() .ConfigureServices(services => { services.AddDbContext<MyDbContext>(options => { options.UseCosmos( "https://your-cosmosdb-uri", "your-cosmosdb-key", "your-database-name" ); }, ServiceLifetime.Transient); services.AddTransient<IUnitOfWork, UnitOfWork>(); services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly) ); }) .Build(); host.Run();
MyFunctions.cs
public class MyFunctions { private readonly IMediator _mediator; public MyFunctions(IMediator mediator) { _mediator = mediator; } [FunctionName("MyFunctionOne")] public async Task<IActionResult> RunOne( [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req, CancellationToken cancellationToken) { var result = await _mediator.Send(new MyRequest(), cancellationToken); return new OkObjectResult(result); } [FunctionName("MyFunctionTwo")] public async Task<IActionResult> RunTwo( [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req, CancellationToken cancellationToken) { var result = await _mediator.Send(new MyRequest(), cancellationToken); return new OkObjectResult(result); } }
UnitOfWork.cs
public class UnitOfWork : IUnitOfWork { private readonly MyDbContext _context; private readonly MyRepository _repo; public UnitOfWork(MyDbContext context) { _context = context; _repo = new MyRepository(_context); } public MyRepository ProductRepository { get => _repo; } }
MyRequestHandler.cs
public class MyRequestHandler : IRequestHandler<MyRequest, MyResponse> { private readonly IUnitOfWork _unitOfWork; public MyRequestHandler(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public async Task<MyResponse> Handle(MyRequest request, CancellationToken cancellationToken) { // Perform operations using UnitOfWork or any other logic await _unitOfWork.SaveChangesAsync(cancellationToken); // Create and return the response return new MyResponse { Result = $"Processed: {request.Data}" }; } }
问题原因分析
Azure Functions的Scoped生命周期特殊性
Azure Functions的工作者模型中,Scoped服务的生命周期并非严格绑定到单个请求——在函数实例复用的场景下,Scoped实例可能被多个请求共享。即使将DbContext设为Transient,若UnitOfWork或MediatR Handler的生命周期配置错误,仍会导致DbContext被跨线程复用。MediatR Handler的隐式复用风险
虽然RegisterServicesFromAssembly()默认将RequestHandler注册为Transient,但如果Handler存在Singleton级别的依赖,或是函数运行时的实例复用机制导致Handler被保留,会间接让多个请求共享同一UnitOfWork和DbContext实例。DbContext本身的线程不安全特性
EF Core的DbContext从设计上就不是线程安全的,Cosmos DB提供程序也遵循这一规则。当同一DbContext实例被多个并发线程调用时,必然触发线程访问异常。UnitOfWork的实例共享问题
即使DbContext是Transient,若UnitOfWork注册为Scoped且函数实例被复用,多个请求可能拿到同一个UnitOfWork实例,进而共享其内部持有的DbContext。
内容的提问来源于stack exchange,提问作者Mouayed Bouameur

