EF core DbContext в многопоточном приложении API - PullRequest
1 голос
/ 20 апреля 2019

tl; dr Как использовать Entity Framework в многопоточном приложении .NET Core API, даже если DbContext не является потокобезопасным?

Context

Я работаю над приложением .NET Core API, предоставляющим несколько интерфейсов RESTful, которые обращаются к базе данных и считывают данные из нее, одновременно работая с несколькими TimedHostedServices в качестве фоновых рабочих потоков, которые регулярно опрашивают данные с других веб-сервисов и сохраняют их вбаза данных.

Мне известно о том, что DbContext не является потокобезопасным.Я прочитал много документов, постов и ответов в блоге здесь, на Stackoverflow, и я мог найти много (частично противоречивых) ответов на этот вопрос, но никакой реальной «лучшей практики» при работе с DI.

Вещи, которые япробовал

Использование по умолчанию ServiceLifetime.Scoped с помощью метода расширения AddDbContext приводит к исключениям из-за условий гонки.

Я не хочу работать с блокировками (например, семафором), так какочевидные недостатки:

  • код загрязнен блокировками и попытайтесь / поймать / наконец для безопасного освобождения блокировок
  • на самом деле он не кажется «надежным», т.е. когда я забываючтобы заблокировать область, которая обращается к DbContext.
  • кажется избыточным и «неестественным» искусственно синхронизировать доступ к базе данных в приложении при работе с базой данных, которая также обрабатывает одновременные соединения и доступ

Не вставляя MyDbContext, а вместо этого DbContextOptions<MyDbContext>, создавая контекст только тогда, когда мне нужен доступ к БД, используя оператор using для немедленного удаления его после сеанса чтения / записи.Например, из-за большого количества ресурсов и ненужных открытий / закрытий соединений.

Вопрос

Я действительно озадачен: как этого достичь?

Не знаюя думаю, что мой сценарий использования супер-особенный - заполняет БД из фонового работника и запрашивает его из уровня веб-API - поэтому должен быть осмысленный способ сделать это с помощью ef core.

Большое спасибо!

Ответы [ 2 ]

2 голосов
/ 21 апреля 2019

Вы должны создавать область видимости всякий раз, когда срабатывает TimedHostedServices.

Добавьте поставщика услуг в свой конструктор:

public MyServiceService(IServiceProvider services)
{
    _services = services;
}

, а затем создайте область действия при каждом запуске задачи

using (var scope = _services.CreateScope())
{
    var anotherService = scope.ServiceProvider.GetRequiredService<AnotherService>();

    anotherService.Something();
}

Более полный пример доступен в документе

0 голосов
/ 22 апреля 2019

Другой подход к созданию собственной DbContextFactory и созданию нового экземпляра для каждого запроса.

public class DbContextFactory
{
    public YourDbContext Create()
    {
        var options = new DbContextOptionsBuilder<YourDbContext>()
            .UseSqlServer(_connectionString)
            .Options;

        return new YourDbContext(options);
    }
}

Использование

public class Service
{
    private readonly DbContextFactory _dbContextFactory;

    public Service(DbContextFactory dbContextFactory) 
         => _dbContextFactory = dbContextFactory;

    public void Execute()
    {
        using (var context = _dbContextFactory.Create())
        {
            // use context
        }
    }
}    

С фабрикой вам больше не нужно беспокоиться о границах и освободите свой код от зависимостей ASP.NET Core.
Вы сможете выполнять запросы асинхронно, что невозможно с помощью DbContext с областью действия без обходных путей.
Вы всегда будете уверены в том, какие данные были сохранены при вызове .SaveChanges(), где с помощью DbContext в области видимости есть вероятность, что некоторые объекты были изменены в другом классе.

...