'Cannot access a disposed context instance.' 从存储库调用方法时出错

'Cannot access a disposed context instance.' error when calling a method from a repository

我正在尝试从服务的存储库调用方法。

上下文、存储库和服务都被定义为作用域服务。

这是我首先调用的方法:

  public async void ReceiveWebhook(HttpContext httpContext)
    {
        // some unimportant checks here

        var productPurchaseRequest = new ProductPurchaseRequest
        {
            Amount = Convert.ToInt32(result?.Quantity),
            Timestamp = DateTime.Now,
            ProductType = productType,
            PaymentProviderOrderId = Convert.ToInt32(result?.OrderId),
            PaymentProviderProductId = Convert.ToInt32(result?.ProductId),
            PaymentProviderTransactionId = result?.TransactionId!,
            PaymentModel = PaymentModel.Subscription,
            PhoneNumber = result?.Passthrough!
            //todo: change payment model
        };
        var bought = await _productProvisioningRepository.PurchaseProduct(productPurchaseRequest);

    }

这是方法:PurchaseProduct() 在存储库中:

    public async Task<bool> PurchaseProduct(ProductPurchaseRequest productPurchaseRequest)
    {
        await using var transactionScope = await _context.Database.BeginTransactionAsync();
        
        var query = from u in _context.signumid_user
            where u.PhoneNumber == productPurchaseRequest.PhoneNumber
            select u;
        
        var user = await query.FirstOrDefaultAsync();
        
        if (user == null)
        {
            return false;
            //todo: log user with phone number does not exist
        }
        
        try
        {
            var transaction = new Transaction
            {
                Timestamp = productPurchaseRequest.Timestamp,
                PaymentProviderOrderId = productPurchaseRequest.PaymentProviderOrderId,
                PaymentProviderProductId = productPurchaseRequest.PaymentProviderProductId,
                PaymentProviderTransactionId = productPurchaseRequest.PaymentProviderTransactionId
            };
            var transactionDb = await _context.signumid_transaction.AddAsync(transaction);
            await _context.SaveChangesAsync();

            var productEntry = new ProductEntry
            {
                Amount = productPurchaseRequest.Amount,
                ExpiryDate = productPurchaseRequest.Timestamp.AddMonths(1),
                PaymentModel = (int) PaymentModel.Subscription,
                ProductType = productPurchaseRequest.ProductType,
                UserId = 1
            };
            
            var productEntryDb = await _context.signumid_product_entry.AddAsync(productEntry);
            await _context.SaveChangesAsync();

            var transactionProductEntry = new Transaction_ProductEntry
            {
                TransactionId = transactionDb.Entity.Id,
                ProductEntryId = productEntryDb.Entity.Id
            };
            
            var transactionProductEntryDb = await _context.sisgnumid_transaction_product_entry.AddAsync(transactionProductEntry);
            await _context.SaveChangesAsync();

            //todo: check if everything is okay with database entries
            
            await transactionScope.CommitAsync();
            return true;
        }
        catch (Exception e)
        {
            // todo: add log
            Console.WriteLine(e);
            await transactionScope.RollbackAsync();
            return false;
        }
    }

这是 program.cs 文件:

using System.Diagnostics;
using System.Reflection;
using System.Text.Json.Serialization;
using FluentMigrator.Runner;
using Hangfire;
using Hangfire.PostgreSql;
using Hangfire.SQLite;
using Hangfire.SqlServer;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.DataEncryption;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Newtonsoft.Json;
using Serilog;
using Serilog.Exceptions;
using Serilog.Exceptions.Core;
using Signumid.ExceptionMiddleware;
using Signumid.Global;
using Signumid.MigratorRunner;
using Signumid.ProductProvisioning;
using Signumid.ProductProvisioning.Migrations;

var builder = WebApplication.CreateBuilder(args);
var configurationBuilder = new ConfigurationBuilder()
    .SetBasePath(builder.Environment.ContentRootPath)
    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
    .AddEnvironmentVariables();
var configurationRoot = configurationBuilder.Build();
ConfigureLogging(configurationRoot);

var applicationSettings = new Signumid.ApplicationSettings.ApplicationSettings();

configurationRoot.Bind(applicationSettings);

SignumIdGlobal.InitialiseApplicationSettings(applicationSettings);

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.Configure<JsonOptions>(options =>
{
    options.SerializerOptions.PropertyNameCaseInsensitive = false;
    options.SerializerOptions.PropertyNamingPolicy = null;
    options.SerializerOptions.WriteIndented = true;
    options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});

MigratorRunner.MigrateDatabase(builder.Services,
    SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.ConnectionString,
    SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.DatabaseProvider,
    Assembly.GetAssembly(typeof(InitialMigration)));


builder.Services.AddScoped<ProductProvisioningContext>();
builder.Services.AddScoped<ProductProvisioningRepository>();
builder.Services.AddScoped<ProductProvisioningService>();


ConfigureHangfire(builder.Services);
ConfigureHealthCheck(builder.Services);

builder.Services.AddCors(options =>
    options.AddPolicy("CorsPolicy",
        corsPolicyBuilder =>
        {
            corsPolicyBuilder
                .AllowAnyMethod()
                .AllowAnyHeader()
                .AllowCredentials()
                .SetIsOriginAllowed(_ => true)
                .WithExposedHeaders("Content-Disposition", "Content-Length");
        }));

builder.Services.AddAuthorization();

var app = builder.Build();
// Configure hangfire to use the new JobActivator we defined.

// Use the previously configured CorsPolicy policy
app.UseCors("CorsPolicy");

app.ConfigureExceptionHandler();
// Configure the HTTP request pipeline.
app.UseSwagger();
app.UseSwaggerUI();

app.UseHttpsRedirection();

app.MapHealthChecks("/v/1/health/basic");
app.MapHealthChecks("/v/1/health/simplified",
    new HealthCheckOptions
    {
        ResponseWriter = async (context, report) =>
        {
            context.Response.ContentType = "application/json";
            await context.Response.WriteAsync(JsonConvert.SerializeObject(new
            {
                status = report.Status.ToString(),
                monitors = report.Entries.Select(e => new
                    {key = e.Key, value = Enum.GetName(typeof(HealthStatus), e.Value.Status)})
            }));
        }
    }
).RequireHost(SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.HealthCheckHosts);

app.MapGet("/v/1/signer/getAvailableSignatures",
    (string phoneNumber, int productType, ProductProvisioningService service) => service.RetrieveRemainingProductAmountForUser(phoneNumber, productType));

app.MapGet("/v/1/signer/generatePayLink",
    (string phoneNumber, int quantity, int productId, ProductProvisioningService service) => service.GeneratePayLink(phoneNumber, quantity, productId));

app.MapPost("/v/1/signer/receiveWebhook",  
    (HttpContext context, ProductProvisioningService service) =>  service.ReceiveWebhook(context));

app.UseAuthorization();

app.UseHangfireDashboard();
GlobalConfiguration.Configuration
    .UseActivator(new HangfireActivator(app.Services));


var runner = app.Services.GetRequiredService<IMigrationRunner>();
// Execute the migrations
runner.MigrateUp();

app.Run();

static void ConfigureLogging(IConfigurationRoot configuration)
{
    try
    {
        Serilog.Debugging.SelfLog.Enable(msg => Debug.WriteLine(msg));
        Serilog.Debugging.SelfLog.Enable(Console.Error);
        // Impossible to set with appsettings:
        // 
        // https://github.com/RehanSaeed/Serilog.Exceptions/issues/58
        var loggingConfiguration = new LoggerConfiguration()
            .Enrich
            .WithExceptionDetails(new DestructuringOptionsBuilder().WithDefaultDestructurers())
            .ReadFrom
            .Configuration(configuration);

        Log.Logger = loggingConfiguration
            .CreateLogger();
    }
    catch (Exception e)
    {
        Serilog.Debugging.SelfLog.Enable(msg => Debug.WriteLine(msg));
        Serilog.Debugging.SelfLog.Enable(Console.Error);
        Log.Logger = new LoggerConfiguration()
            .WriteTo.Console()
            .MinimumLevel.Debug() // set to minimal in serilog
            .CreateLogger();
        Log.Debug(e,
            "Unable to import serilog configuration from appsettings.json, logging only to console. Error: {@Ex}", e);
    }

    var currentDomain = AppDomain.CurrentDomain;
    currentDomain.UnhandledException += UnhandledExceptionHandler;
}

static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args)
{
    var e = (Exception) args.ExceptionObject;
    Log.Fatal(e, "Unhandled exception caught : {@error}", e);
    Log.Fatal("Runtime terminating: {0}", args.IsTerminating);
}

static void ConfigureHealthCheck(IServiceCollection services)
{
    if (SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.DatabaseProvider.Equals("SqlLite",
            StringComparison.InvariantCultureIgnoreCase))
    {
        services.AddHealthChecks()
            .AddSqlite(SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.ConnectionString);
    }
    else if (SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.DatabaseProvider.Equals(
                 "postgresql",
                 StringComparison.InvariantCultureIgnoreCase))
    {
        services.AddHealthChecks()
            .AddNpgSql(SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.ConnectionString);
    }
    else if (SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.DatabaseProvider.Equals(
                 "sqlserver",
                 StringComparison.InvariantCultureIgnoreCase))
    {
        services.AddHealthChecks()
            .AddSqlServer(SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.ConnectionString);
    }
    else
    {
        services.AddHealthChecks();
    }
}

static void ConfigureHangfire(IServiceCollection services)
{
    services.AddHangfire(configuration =>
    {
        configuration
            .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
            .UseSimpleAssemblyNameTypeSerializer()
            .UseRecommendedSerializerSettings();
    });

    if (SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.DatabaseProvider.Equals("SqlLite",
            StringComparison.InvariantCultureIgnoreCase))
    {
        GlobalConfiguration.Configuration.UseSQLiteStorage(SignumIdGlobal.ApplicationSettings
            .ProductProvisioningApplicationSettings
            .ConnectionString, new SQLiteStorageOptions
        {
            SchemaName = "product_provisioning"
        });
    }
    else if (SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.DatabaseProvider.Equals(
                 "postgresql",
                 StringComparison.InvariantCultureIgnoreCase))
    {
        GlobalConfiguration.Configuration.UsePostgreSqlStorage(
            SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.ConnectionString,
            new PostgreSqlStorageOptions
            {
                SchemaName = "product_provisioning"
            });
    }
    else if (SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.DatabaseProvider.Equals(
                 "sqlserver",
                 StringComparison.InvariantCultureIgnoreCase))
    {
        GlobalConfiguration.Configuration.UseSqlServerStorage(
            SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.ConnectionString,
            new SqlServerStorageOptions
            {
                SchemaName = "product_provisioning"
            });
    }

    services.AddHangfireServer();
}

当我声明事务范围时,方法的第一行出现错误。 错误如下:

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. Object name: 'ProductProvisioningContext'.

screenshot of the error

你的ReceiveWebhookasync void,改成return Task:

public async Task ReceiveWebhook(HttpContext httpContext)

否则ASP.NET核心不能等到处理结束,将在处理程序完成之前完成请求(包括处理创建的范围和一次性依赖项,如数据库上下文)。

阅读更多:

  1. async/await - when to return a Task vs void?
  2. Why exactly is void async bad?
  3. Async/Await - Best Practices in Asynchronous Programming