0

我有一个使用 Entity Framework (3.0.11) 的 Azure Function (v3)。

我正在尝试在计时器触发器中运行代码,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没有注入会出现一个空属性,那里没有魔法。
  • 添加MyDbContextRun函数失败,因为它不能被注入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. 使用计时器触发器来调用位于同一函数中的 HTTPtrigger?
  3. 任何其他允许我在 timertrigger 上下文中访问 EF 数据库的解决方案?

更新:

@StevePy 下面的评论是正确的。您可以使 timertrigger 的 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);
        }
4

2 回答 2

1

尝试使用非静态 Run 方法。许多示例使用静态方法,如果您没有依赖项并且该方法是纯的,则可以推荐该方法。(因为函数方法应该力求纯粹)有关TimerTriggers /w DI 的示例,请参见https://marcroussy.com/2019/05/31/azure-functions-built-in-dependency-injection/ 。

于 2020-12-11T21:58:12.993 回答
0

@StevePy 在我的问题下方的评论是正确的。您可以使 timertrigger 的 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);
        }

请注意,为了在到期时给予信用,我会将已接受的答案切换为史蒂夫的答案是否以及当他做出回应时,但我现在将其标记为已回答以确保该问题具有可接受的答案。

于 2020-12-11T17:53:37.367 回答