Получить DbContext в асинхронном действии

Я переписываю/перемещаю веб-сайт с ASP MVC на ASP MVC Core. Это приложение имеет динамическое меню, которое зависит от вошедшего в систему пользователя. Чтобы построить меню, каждый контроллер является производным от пользовательского BaseController, который устанавливает ViewBag пунктов меню, а позже, в Layout, эти элементы извлекаются и передаются в качестве аргументов PartialView.

public BaseController:Controller
{
    public BaseController()
    {
          ...
          ViewBag.Menu=Utils.GetMenu();
          ...
    }
}

Я не хочу использовать ту же логику, что и парни, которые писали старый код. Поэтому я решил использовать ViewComponent для рендеринга меню. Но у меня есть проблема с этим подходом. В методе Invoke мне нужно запросить пункты меню. Прямо сейчас я получаю экземпляр DbContext от поставщика услуг (HttpContext.RequestServices) и использую его для запроса любых данных, которые мне нужны. Но функция Invoke вызывается асинхронно из Layout и я знаю, что посылать DbContext в async методы не очень хорошо:

<cache expires-after="@TimeSpan.FromHours(2)" enabled="true">
       @await Component.InvokeAsync(typeof(Admin.ViewComponents.MeniuViewComponent))
</cache>

Хороший ли это подход? Безопасно ли получать DbContext (зарегистрированный как Scoped в Startup) в методе Invoke (или любом другом async методе или действии) и использовать его? И если это не очень хорошая идея, как мне поступить в таких ситуациях, когда мне нужны данные из БД в async методах?


person schneider    schedule 14.10.2020    source источник
comment
Похоже, вы не отделяете свой уровень представления от уровня данных. Есть ли у вас репозитории для манипулирования данными БД и сервисы для обработки некоторой бизнес-логики? Общепринятым подходом является отказ от каких-либо вызовов БД внутри представления. Вы должны подготовить все данные и отправить их в представлении, которое у вас есть.   -  person Eugene D    schedule 14.10.2020
comment
Да. Но в репозитории я пытаюсь получить данные из кеша (я использую MemoryCache), и если это не так, я получаю их из БД. В любом случае, разве компоненты просмотра не должны обеспечивать поддержку стиля действия? Я не пытаюсь получить данные из View, я пытаюсь получить их из функции Invoke.   -  person schneider    schedule 14.10.2020


Ответы (2)


В моем проекте у меня был случай получить DbContext внутри HostedService из ядра asp.net.

Я внедрил IServiceProvider внутрь конструктора и создал свою собственную область видимости, чтобы получить зарегистрированный DbContext:

private readonly IServiceProvider _serviceProvider;
public BaseController(IServiceProvider provider){
  _serviceProvider = provider;
}

private void DoSth(){
  using var scope = _serviceProvider.CreateScope();
  var db = scope.ServiceProvider.GetRequiredService<YOUR_DbContext_CLASS>();

  ... //do something great stuff here with the database
}
person Danny Schneider    schedule 14.10.2020
comment
вам нужно обернуть переменную scope в предложение using. иначе ваш контекст БД не будет удален - person Yegor Androsov; 15.10.2020
comment
@ЕгорАндросов спасибо за подсказку, вы абсолютно правы. Это ошибка опечатки/копирования, так как я использую переменную области видимости с использованием, чтобы избавиться от нее в конце. - person Danny Schneider; 15.10.2020
comment
@DannySchneider спасибо! Итак, всякий раз, когда я имею дело с функциями, которые можно назвать асинхронными, я должен создавать новый dbcontext? - person schneider; 15.10.2020
comment
Я думаю, это зависит от того, попробуйте выяснить время жизни вашей области и где внутри жизненного цикла запроса необходим ваш контекст БД. Зарегистрированный класс с ограниченной областью действия создается один раз в течение одного жизненного цикла запроса. Если прицел не утилизируется до того, как вы его используете, я думаю, что лучше всего подойдет способ от @mj1313... - person Danny Schneider; 15.10.2020

Я думаю, вы можете напрямую внедрить зависимость DbContext в ViewComponent

public class MenuViewComponent : ViewComponent
{
    private readonly MenuDbContext _context;

    public TopMenuViewComponent(MenuDbContext context)
    {
        _context = context;

    }

    public async Task<IViewComponentResult> InvokeAsync()
    {
        var model = _context.MenuItems.ToList();

        return View("Default", model);
    }
}

Дополнительные сведения см. в doc.

person mj1313    schedule 15.10.2020
comment
Да, я знаю, что могу получить это таким образом. Но введенный dbcontext в этом случае используется всеми методами, вызываемыми во время запроса. Безопасно ли работать с этим общим dbcontext? - person schneider; 15.10.2020