У меня есть функция 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, но, похоже, я ничего не могу использовать для этого объекта.Есть ли способ:
Комментарий @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);
}
@StevePy - интересно! Я прочитал, что функция таймера ТРЕБУЕТ статическую функцию запуска. Это новый способ попробовать. Я займусь этим.
@StevePy - большое спасибо. Я рад сообщить, что это сработало, как и ожидалось. Похоже, предыдущий пост, который я читал, в котором говорилось, что метод RUN ДОЛЖЕН быть статичным для таймера, устарел. Ясно, что это работает. Если вы напишете свой комментарий в качестве ответа ниже, я с радостью приму его для вас.
Не беспокойтесь, это был интересный вопрос, чтобы немного перепроверить. . Источники онлайн-документации могут быть немного устаревшими, поскольку все меняется, иногда вам нужна информация, которая относится к определенной версии чего-либо, а самый популярный контент предназначен для более новой или старой версии. :) Еще хуже, если на содержании не указана версия или даже дата.





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