TimerTrigger не внедряет контекст базы данных EF

У меня есть функция Azure (v3) с использованием Entity Framework (3.0.11).

Я пытаюсь запустить код на TimerTrigger, однако внедрение базы данных в триггер таймера, похоже, не работает.

Вот несколько (быстро анонимизированных) примеров кода.

CSPROJ

<Project Sdk = "Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <AzureFunctionsVersion>v3</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include = "AzureFunctions.Extensions.DependencyInjection" Version = "1.1.3" />
    <PackageReference Include = "Microsoft.Azure.Functions.Extensions" Version = "1.1.0" />
    <PackageReference Include = "Microsoft.EntityFrameworkCore.SqlServer" Version = "3.1.10" />
    <PackageReference Include = "Microsoft.NET.Sdk.Functions" Version = "3.0.11" />
  </ItemGroup>
  <ItemGroup>
    <None Update = "host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update = "local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
  </ItemGroup>
</Project>

модель и DBContext

namespace DataImport
{
    public class Sample
    {
        public int SampleID { get; set; }
        public string SampleField { get; set; }
    }

    public class MyDbContext : DbContext
    {
        public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
        public virtual DbSet<Sample> MyRecords { get; set; }
    }
}

стартовый класс

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;

[assembly: FunctionsStartup(typeof(DataImport.Startup))]
namespace DataImport
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            string con = builder.GetContext().Configuration.GetSection("ConnectionStrings:DefaultConnection").Value.ToString();
            builder.Services.AddDbContext<MyDbContext>(config => config.UseSqlServer(con));
        }
    }
}

программа.cs

using System;
using System.Linq;
using System.Threading.Tasks;
using AzureFunctions.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace DataImport
{


    public class Program
    {
        private readonly MyDbContext db;
        public Program(MyDbContext database)
        {
            db = database;
        }

        [FunctionName("SampleFunction_works")]
        public async Task<IActionResult> HttpRun([HttpTrigger(AuthorizationLevel.Anonymous, "GET")] HttpRequest req, ILogger log, ExecutionContext context)
        {
            log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
            var foo = db.MyRecords.Where(c => c.SampleField == "000").FirstOrDefault();
            await db.MyRecords.AddAsync(new Sample());
            log.LogInformation(foo.SampleField);
            return new OkObjectResult(foo);
        }

        [FunctionName("SampleFunction_no_work")]
        public static void Run([TimerTrigger("%TimerInterval%")] TimerInfo myTimer, ILogger log, ExecutionContext context)
        {
            log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
            // tried dozens of things here, nothing works sofar.
            // injecting IServiceProvider fails,
            // what other ways to solve this?
            // could a timer trigger perhaps make an HTTP call to the HttpRun function above? 
        }
    }
}

при запуске SampleFunction_works с подключением к базе данных мы видим результат вызова функции как успешный. Внедрение работает в контексте триггера HTTP. Однако на таймере это не работает.

На данный момент я пробовал 8 часов разных вещей:

  • неудивительно, что доступ к db без внедрения приводит к нулевому свойству, в этом нет никакой магии.
  • добавление MyDbContext к функции Run не удается, потому что ее нельзя внедрить public static void Run([TimerTrigger("%TimerInterval%")] TimerInfo myTimer, ILogger log, MyDbContext db)
Microsoft.Azure.WebJobs.Host: Error indexing method 'SampleFunction_no_work'. Microsoft.Azure.WebJobs.Host: Cannot bind parameter 'db' to type MyDbContext. Make sure the parameter Type is supported by the binding. If you're using binding MyDbContext(e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).
  • выполнение того же, что и в предыдущем случае, но добавление IServiceProvider services к сигнатуре метода приводит к аналогичному сообщению об ошибке, добавление строки db = services.GetRequiredService<MyDbContext>(); не имеет значения, если ее нельзя внедрить
  • некоторые переменные ДЕЙСТВИТЕЛЬНО кажутся инъекционными в этой области, например, ExecutionContext, но, похоже, я ничего не могу использовать для этого объекта.

Есть ли способ:

  1. внедрить триггер таймера в базу данных?
  2. использовать триггер таймера для вызова HTTP-триггера, расположенного в той же функции?
  3. любое другое решение, которое позволит мне получить доступ к базе данных EF в контексте timertrigger?

обновлять:

Комментарий @StevePy ниже был правильным. Вы можете сделать метод RUN таймера нестатичным и использовать силу внедрения. Ранее я читал, что это невозможно, но оказалось, что информация устарела.

Дополнительные сведения см. в этой записи БЛОГА: https://marcroussy.com/2019/05/31/azure-functions-built-in-dependency-injection/

Или возьмите этот пример кода, чтобы запустить его локально:

        [FunctionName("MY_FANCY_FUCNTION")]
        public async Task Run([TimerTrigger("%TimerInterval%")] TimerInfo myTimer, ILogger ilog, ExecutionContext context)
        {
            ilog.LogInformation($"TIMER EXECUTED IS DB NULL? '{db == null}'");
            // note that the key part of this DOES log out as NOT NULL
            // which is what we want.
            return;
            await Main(ilog, context);
        }

Попробуйте использовать нестатический метод Run. Во многих примерах используется статический метод, который можно рекомендовать, если у вас нет зависимостей и метод является чистым. (т.к. функциональные методы должны стремиться к чистоте) См. marcroussy.com/2019/05/31/… примеры TimerTriggers /w DI.

Steve Py 11.12.2020 06:03

@StevePy - интересно! Я прочитал, что функция таймера ТРЕБУЕТ статическую функцию запуска. Это новый способ попробовать. Я займусь этим.

Alex C 11.12.2020 18:34

@StevePy - большое спасибо. Я рад сообщить, что это сработало, как и ожидалось. Похоже, предыдущий пост, который я читал, в котором говорилось, что метод RUN ДОЛЖЕН быть статичным для таймера, устарел. Ясно, что это работает. Если вы напишете свой комментарий в качестве ответа ниже, я с радостью приму его для вас.

Alex C 11.12.2020 18:46

Не беспокойтесь, это был интересный вопрос, чтобы немного перепроверить. . Источники онлайн-документации могут быть немного устаревшими, поскольку все меняется, иногда вам нужна информация, которая относится к определенной версии чего-либо, а самый популярный контент предназначен для более новой или старой версии. :) Еще хуже, если на содержании не указана версия или даже дата.

Steve Py 11.12.2020 23:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
615
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Комментарий @StevePy под моими вопросами был правильным. Вы можете сделать метод RUN таймера нестатичным и использовать силу внедрения. Ранее я читал, что это невозможно, но оказалось, что информация устарела.

Дополнительные сведения см. в этой записи БЛОГА: https://marcroussy.com/2019/05/31/azure-functions-built-in-dependency-injection/

Или возьмите этот пример кода, чтобы запустить его локально:

        [FunctionName("MY_FANCY_FUCNTION")]
        public async Task Run([TimerTrigger("%TimerInterval%")] TimerInfo myTimer, ILogger ilog, ExecutionContext context)
        {
            ilog.LogInformation($"TIMER EXECUTED IS DB NULL? '{db == null}'");
            // note that the key part of this DOES log out as NOT NULL
            // which is what we want.
            return;
            await Main(ilog, context);
        }

обратите внимание, что в интересах отдать должное там, где это необходимо, я переключу принятый ответ на ответ Стива, если и когда он ответит, но я отмечаю как ответивший сейчас, чтобы гарантировать, что вопрос имеет принятый ответ.

Ответ принят как подходящий

Попробуйте использовать нестатический метод Run. Во многих примерах используется статический метод, который можно рекомендовать, если у вас нет зависимостей и метод является чистым. (поскольку функциональные методы должны стремиться к чистоте) См. https://marcroussy.com/2019/05/31/azure-functions-built-in-dependency-injection/ пример TimerTriggers /w DI.

Другие вопросы по теме