.NET Core 2.1 DbContext ObjectDisposedException Внедрение зависимостей - PullRequest
3 голосов
/ 01 апреля 2019

Я делаю n-уровневое приложение MVC, используя .NET Core 2.1 и Entity Framework. Есть также размещенная очередь MQTT, в которой мое приложение слушает как клиент. Я также использую инъекцию зависимости. Это работает отлично, пока сообщение не будет помещено в очередь, и я хочу сохранить это сообщение в БД. Как только это происходит, я получаю следующее сообщение об ошибке ObjectDisposedException:

Невозможно получить доступ к удаленному объекту. Распространенной причиной этой ошибки является удаление контекста, который был разрешен путем внедрения зависимости, а затем попытка использовать тот же экземпляр контекста в другом месте вашего приложения. Это может произойти, если вы вызываете Dispose () для контекста или заключаете контекст в оператор using. Если вы используете внедрение зависимости, вы должны позволить контейнеру введения зависимости позаботиться об удалении экземпляров контекста. Имя объекта: 'xxxDbContext'.

Я могу нажать продолжить, после чего приложение просто продолжает работать. Он создает исключение только для первого сообщения, полученного из очереди. Любое другое действие с контроллерами / менеджерами / репозиториями работает просто отлично. Мой код выглядит следующим образом:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddDefaultIdentity<User>()
            .AddEntityFrameworkStores<xxxDbContext>();

    services.AddDbContext<xxxDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")
    ));

    // Some identity configuration omitted here

    services.AddScoped<IIdeationRepository, IdeationRepository>();
    services.AddScoped<IIdeationManager, IdeationManager>();
    // Some other DI configuration omitted as well.
}

public Configure(IApplicationBuilder app, IHostingEnvironment env,
    IApplicationLifetime applicationLifetime, IServiceProvider serviceProvider)
{
    // Start MQTT
    var broker = new MqttBroker(serviceProvider.GetService<IIdeationManager>(),
        serviceProvider.GetService<IConfiguration>());

    // On application exit terminate MQTT to make sure the connection is ended properly
    applicationLifetime.ApplicationStopping.Register(() => broker.Terminate());

    // Some default http pipeline code omitted
}

MqttBroker.cs

public MqttBroker(
    [FromServices] IIdeationManager ideationManage,
    [FromServices] IConfiguration configuration)
{
    _ideationManager = ideationManager;
    _configuration = configuration;

    Initialize();
}

    // Some code where I just parse the message and on receive send it to the
    // ideation manager, this just works so I omitted it.
}

Менеджер просто отправляет его прямо в хранилище, где появляется сообщение об ошибке.

Repository.cs

private xxxDbContext ctx;

public IdeationRepository(xxxDbContext xxxDbContext)
{
    this.ctx = xxxDbContext;
}

// This method crashes with the error
public IdeationReply ReadIdeationReply(int id)
{
    return ctx
        .IdeationReplies
        .Include(r => r.Votes)
        .FirstOrDefault(r => r.IdeationReplyId == id);
}

DbContext.cs

public class xxxDbContext : IdentityDbContext<User>
{
    public DbSet<Ideation> Ideations { get; set; }
    // Some more dbsets omitted

    public CityOfIdeasDbContext(DbContextOptions<CityOfIdeasDbContext> options) 
        : base (options)
    {
        CityOfIdeasDbInitializer.Initialize(this, dropCreateDatabase: false);
    } 

    // In configuring I just create a logger, nothing special

    // In OnModelCreating I just setup some value converters for other tables
    // than the ones I need here

    internal int CommitChanges()
    {
        if (delaySave)
        {
            int infectedRecords = base.SaveChanges();       
            return infectedRecords;
        }

        throw new InvalidOperationException(
            "No UnitOfWork present, use SaveChanges instead");
    }
}

Я прочитал это , но ни одна из этих ситуаций, кажется, не относится ко мне. И когда я печатаю трассировку стека в Dispose(), это происходит методом Main(), поэтому мне это не очень помогает.

Кто-нибудь знает, как решить или где я могу найти, чтобы решить эту проблему?

Заранее спасибо!

1 Ответ

4 голосов
/ 05 апреля 2019

Экземпляр IServiceProvider, который передается в Configure, равен scoped , что означает, что он удаляется платформой после завершения Configure - любой из scoped служб, которыеего создания также удаляются во время этого процесса.

В вашем примере вы запрашиваете экземпляр IIdeationManager (который scoped ), а затем пытаетесь использовать его в вашем MqttBroker класс (который, по сути, является синглтон ).К тому времени, когда вы попытаетесь использовать вашу реализацию IIdeationManager, экземпляр CityOfIdeasDbContext в области , который был создан и подключен DI, был удален, и поэтому возникло исключение ObjectDisposedException.

Для решения этой проблемы вы можете использовать общий шаблон, который используется, когда синглтону требуется доступ к сервису с заданной областью: создайте область, разрешите службу, используйте службу и затем удалите область.В общем, это выглядело бы примерно так:

using (var scope = serviceProvider.CreateScope())
{
    var ideationManager = scope.ServiceProvider.GetService<IIdeationManager>();

    // Do something with ideationManager.
}

// scope and all created disposable services have been disposed.

Когда вы запрашиваете реализацию IIdeationManager, система DI видит, что (в конечном итоге) ей нужна область действия CityOfIdeasDbContext, и создает ее для вас.,После удаления scope этот экземпляр CityOfIdeasDbContext также удаляется.

Чтобы это работало в вашем примере, ваш MqttBroker может взять экземпляр IServiceProvider в свой конструктор и использовать егодля создания области видимости, которую я показал выше (она все еще может принимать IConfiguration как есть, учитывая, что она сама является синглтоном).

Экземпляр IServiceProvider, который должен быть передан в класс MqttBrokerдолжно не быть IServiceProvider, которое передается в Configure - это уже ограничено и, как я описал, будет очищено после завершения Configure, что на самом деле является вашей проблемоймы начали с.Для этого используйте app.ApplicationServices, который является корневым провайдером и не имеет области действия.

...