c# - 如果我的上下文已释放,在收到来自 Azure 服务总线监听器/使用者的消息后如何使用 EF Core?

标签 c# azure entity-framework-core azureservicebus eventhandler

我有一个网站 Angular 前端,后端有 WebAPI 以及所有 Controller ,我还有一个服务(C# 类),我将其作为单例调用,作为长时间运行的任务来监听传入的 Azure 服务总线消息。

仅供引用 - 我无法将任何作用域服务 (DbContext) 传递给单例 (ServiceBusConsumer),因此我无法将我的数据库上下文传递给此服务。

问题 - 一旦我收到传入的服务总线消息,我如何调用我的数据库并使用它?

这是我监听和接收消息的服务。

启动.cs

services.AddSingleton<IServiceBusConsumer, ServiceBusConsumer>();

Program.cs -> 在 Main() 中我启动服务

var bus = services.GetRequiredService<IServiceBusConsumer>();
bus.RegisterOnMessageHandlerAndReceiveMessages();

ServiceBusConsumer.cs

public class ServiceBusConsumer : IServiceBusConsumer
{
    private readonly IConfiguration _config;
    private readonly ServiceBusClient _queueClient;
    private readonly ServiceBusProcessor _processor;

    // private readonly DataContext _context;

    public ServiceBusConsumer(IConfiguration config,
    // DataContext context)
    {
        _config = config;
        // _context = context;
        _queueClient = new ServiceBusClient(_config["ServiceBus:Connection"]);
        _processor = _queueClient.CreateProcessor(_config["ServiceBus:Queue"], new ServiceBusProcessorOptions());
    }

    public void RegisterOnMessageHandlerAndReceiveMessages() {
        _processor.ProcessMessageAsync += MessageHandler;
        _processor.ProcessErrorAsync += ErrorHandler;
        _processor.StartProcessingAsync();
    }

    private async Task MessageHandler(ProcessMessageEventArgs args)
    {
        string body = args.Message.Body.ToString();
        JObject jsonObject = JObject.Parse(body);
        var eventStatus = (string)jsonObject["EventStatus"];

        await args.CompleteMessageAsync(args.Message);

        // _context is disposed 
        // want to connect to DB here but don't know how!
        // var ybEvent = _context.YogabandEvents.Where(p => p.ServiceBusSequenceNumber == args.Message.SequenceNumber).FirstOrDefault();

    }

    private Task ErrorHandler(ProcessErrorEventArgs args)
    {
        var error = args.Exception.ToString();
        return Task.CompletedTask;
    }
}

错误

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.\nObject name: 'DataContext'.

这是Program.cs

public class Program
{
    public static async Task Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();
        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;
            var loggerFactory = services.GetRequiredService<ILoggerFactory>();
            try 
            {
                var context = services.GetRequiredService<DataContext>();

                
                var userManager = services.GetRequiredService<UserManager<User>>();
                var roleManager = services.GetRequiredService<RoleManager<Role>>();


                var bus = services.GetRequiredService<IServiceBusConsumer>();
                bus.RegisterOnMessageHandlerAndReceiveMessages();
                
            }
            catch (Exception ex)
            {
                var logger = loggerFactory.CreateLogger<Program>();
                logger.LogError(ex, "An error occured during migration");
            }
        }

        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

这里是 Startup.cs -> 只是ConfigureServices 方法

public void ConfigureServices(IServiceCollection services)
    {
        services.AddAutoMapper(typeof(MappingEvents));
        services.AddAutoMapper(typeof(MappingMembers));
        services.AddAutoMapper(typeof(MappingUsers));
        services.AddAutoMapper(typeof(MappingYogabands));
        services.AddAutoMapper(typeof(MappingReviews));

        // objects being passed back to the UI. Before I was passing User/Photo/etc and they 
        // had loops/refrences back to the user objects
        services.AddControllers().AddNewtonsoftJson(opt => 
        {
            opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Error;
        });


        services.AddDbContext<DataContext>(x =>
            // x.UseSqlite(_config.GetConnectionString("DefaultConnection"), y => y.UseNetTopologySuite()));
            x.UseSqlServer(_config.GetConnectionString("SqlServerConnection"), y => y.UseNetTopologySuite()));


        services.Configure<AuthMessageSenderOptions>(_config.GetSection("SendGrid"));
        services.Configure<AuthMessageSenderOptionsNew>(_config.GetSection("SendGrid"));
        services.Configure<ConfirmationOptions>(_config.GetSection("Confirmation"));

        services.Configure<CloudinarySettings>(_config.GetSection("CloudinarySettings"));
        
        services.AddApplicationServices();
        services.AddIdentityServices(_config);
        services.AddSwaggerDocumentation();
        
        services.AddCors(opt => 
        {
            opt.AddPolicy("CorsPolicy", policy => 
            {
                policy.AllowAnyHeader().AllowAnyMethod().WithOrigins("https://localhost:4200");
            });
        });
    }

这里是AddApplicationServices()

public static IServiceCollection AddApplicationServices(this IServiceCollection services)
    {
        // scoped - better option when you want to maintain state within a request
        // services.AddScoped<IEventConsumer, EventConsumer>();
        services.AddScoped<IServiceBusProducer, ServiceBusProducer>();
        services.AddSingleton<IServiceBusConsumer, ServiceBusConsumer>();
        services.AddScoped<IEmailSender, EmailSender>();
        services.AddScoped<IEmailSender, EmailSenderNew>();

        services.AddScoped<IEmailService, EmailService>();
        services.AddScoped<ITokenService, TokenService>();
        services.AddScoped<IUnitOfWork, UnitOfWork>();
        services.AddScoped(typeof(IGenericRepository<>), (typeof(GenericRepository<>)));
        services.AddScoped<LogUserActivity>();

        services.Configure<ApiBehaviorOptions>(options => 
        {
            options.InvalidModelStateResponseFactory = actionContext => 
            {
                var errors = actionContext.ModelState
                .Where(e => e.Value.Errors.Count > 0)
                .SelectMany(x => x.Value.Errors)
                .Select(x => x.ErrorMessage).ToArray();
                
                var errorResponse = new ApiValidationErrorResponse 
                {
                    Errors = errors
                };

                return new BadRequestObjectResult(errorResponse);
            };
        });

        return services;
    }

最佳答案

您的问题似乎出在 DI 上。 您的 ServiceBusConsumer 服务是单例,但您注入(inject) DbContext 作为构造函数。这通常是建议,但在这种情况下,它不起作用。
您在构造函数中注入(inject) DbContext 并“保存”到它的“链接”。但随后它会被处理掉,因此“链接”将不起作用。

相反,您应该注入(inject)一个 DbContextFactory。通过工厂,您可以按需创建 DbContext 实例。

private readonly IDbContextFactory<DataContext> _contextFactory;

public ServiceBusConsumer(IConfiguration config, IDbContextFactory<DataContext> contextFactory)
{
     // Add this line
     _contextFactory = contextFactory;
}

private async Task MessageHandler(ProcessMessageEventArgs args)
{
    // With the new C# 8 syntax you can do
    using var db = _contextFactory.CreateDbContext();
    // Otherwise, wrap it up
    using (var db = _contextFactory.CreateDbContext())
    {
    }
}

这是一个文档链接,其中展示了如何使用它:https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/#using-a-dbcontext-factory-eg-for-blazor

您只需注册即可:

public void ConfigureServices(IServiceCollection services)
{
    // Add this line to register a context factory
    services.AddDbContextFactory<DataContext>(
        options =>
            .UseSqlServer(_config.GetConnectionString("SqlServerConnection"), y => y.UseNetTopologySuite()));
}

您不能使用与 Controller 相同的 DI,因为它们通常不是单例,因此不会遇到此问题。 AFAIK DbContextFactory 正是为此目的而创建的(考虑到 Blazor)。如果您需要的服务不是 DbContext,则需要在构造函数中注入(inject)服务提供者,然后直接请求服务,尽管 Microsoft 不建议这样做。

关于c# - 如果我的上下文已释放,在收到来自 Azure 服务总线监听器/使用者的消息后如何使用 EF Core?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67596293/

相关文章:

c# - unsigned char * - 等效的 C#

c# - 使用 Azure API,如何列出虚拟目录并将其添加到网站?

c# - 如何建模 Document 类?

Azure 标准内部负载均衡器后端计算机部分 Internet 访问

entity-framework-core - EF Core 能否跟踪非最顶层投影中的实体?

c# - 您无权查看此目录或页面。 (ASP.NET 网站到 Azure)

azure - 如何返回平均值 - Azure 认知搜索或语义搜索查询

azure - 从处理多个应用程序洞察实例的 Azure Monitor 工作簿获取异常详细信息

entity-framework-core - Entity Framework 7 为模型构建器设置小数精度

db2 - IBM.EntityFrameworkCore - DbContext.OnConfiguring 和 DbContext.OnModelCreating 未被调用