跨Azure账户的Timer触发器Azure Functions配置Entity Framework Core连接Azure SQL故障求助
我在一个Azure账户中拥有Azure SQL数据库,另一个Azure账户部署了Timer触发器类型的Azure Functions。已经用Entity Framework Core完成数据库映射,但执行查询操作时无法正常工作,推测是配置存在问题。尝试多种解决方案仍未解决,现附上相关代码文件及运行错误信息,希望能得到帮助,提前感谢!
代码文件
Function1.cs
using System; using System.Data.SqlClient; using System.Threading.Tasks; using System.Collections.Generic; using System.Text; using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; using Microsoft.Azure.Functions.Extensions; using Microsoft.Net.Http; using Microsoft.EntityFrameworkCore.SqlServer.Design; using Microsoft.EntityFrameworkCore.SqlServer; using Microsoft.EntityFrameworkCore; using System.Linq; using Newtonsoft.Json; namespace azuretosharepoint { public static class Function1 { [FunctionName("Function1")] public static async Task RunAsync([TimerTrigger("0 */1 * * * *")] TimerInfo myTimer, ILogger log) { var azsh = new ProgettokpidatabaseContext(); var listsharepoint = azsh.KpiTables.ToList(); Console.WriteLine(listsharepoint); } } }
progettokpidatabaseContext.cs
using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Design; using System.Collections.Generic; using System.Text; using Newtonsoft.Json; #nullable disable namespace azuretosharepoint { public partial class ProgettokpidatabaseContext : DbContext { public ProgettokpidatabaseContext() { } public ProgettokpidatabaseContext(DbContextOptions<ProgettokpidatabaseContext> options) : base(options) { } public virtual DbSet<KpiTable> KpiTables { get; set; } public virtual DbSet<KptKpiTarget> KptKpiTargets { get; set; } public virtual DbSet<KpvKpiValue> KpvKpiValues { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { #warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263. //optionsBuilder.UseSqlServer("Server=tcp:progetto-kpi.database.windows.net,1433;Initial Catalog=progetto-kpi-database;Persist Security Info=False;User ID=progetto-kpi;Password=**********;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"); optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("sqlconnectionstring")); } } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS"); modelBuilder.Entity<KpiTable>(entity => { entity.HasKey(e => e.KpiId); entity.ToTable("KPI_table"); entity.Property(e => e.KpiId) .ValueGeneratedNever() .HasColumnName("KPI_ID"); entity.Property(e => e.KpiBusinessArea) .IsRequired() .HasMaxLength(50) .HasColumnName("KPI_Business_Area"); entity.Property(e => e.KpiDescription) .HasMaxLength(250) .HasColumnName("KPI_Description"); entity.Property(e => e.KpiDoi) .HasColumnType("datetime") .HasColumnName("KPI_DOI") .HasDefaultValueSql("(getdate())"); entity.Property(e => e.KpiDou) .HasColumnType("datetime") .HasColumnName("KPI_DOU") .HasDefaultValueSql("(getdate())"); entity.Property(e => e.KpiFrequency) .HasMaxLength(10) .HasColumnName("KPI_Frequency"); entity.Property(e => e.KpiManual).HasColumnName("KPI_Manual"); entity.Property(e => e.KpiName) .IsRequired() .HasMaxLength(50) .HasColumnName("KPI_Name"); }); modelBuilder.Entity<KptKpiTarget>(entity => { entity.HasKey(e => e.KptId) .HasName("PK_KPT_table"); entity.ToTable("KPT_KPI_Targets"); entity.Property(e => e.KptId) .ValueGeneratedNever() .HasColumnName("KPT_ID"); entity.Property(e => e.KptDate) .HasColumnType("date") .HasColumnName("KPT_Date"); entity.Property(e => e.KptDoi) .HasColumnType("datetime") .HasColumnName("KPT_DOI") .HasDefaultValueSql("(getdate())"); entity.Property(e => e.KptDou) .HasColumnType("datetime") .HasColumnName("KPT_DOU") .HasDefaultValueSql("(getdate())"); entity.Property(e => e.KptKpiId).HasColumnName("KPT_KPI_ID"); entity.Property(e => e.KptTarget) .HasColumnType("decimal(18, 2)") .HasColumnName("KPT_Target"); entity.HasOne(d => d.KptKpi) .WithMany(p => p.KptKpiTargets) .HasForeignKey(d => d.KptKpiId) .OnDelete(DeleteBehavior.ClientSetNull) .HasConstraintName("FK__KPT_KPI_T__KPT_K__2FCF1A8A"); }); modelBuilder.Entity<KpvKpiValue>(entity => { entity.HasKey(e => e.KpvId) .HasName("PK_KPV_table"); entity.ToTable("KPV_KPI_Values"); entity.Property(e => e.KpvId) .ValueGeneratedNever() .HasColumnName("KPV_ID"); entity.Property(e => e.KpvDate) .HasColumnType("date") .HasColumnName("KPV_Date"); entity.Property(e => e.KpvDoi) .HasColumnType("datetime") .HasColumnName("KPV_DOI") .HasDefaultValueSql("(getdate())"); entity.Property(e => e.KpvDou) .HasColumnType("datetime") .HasColumnName("KPV_DOU") .HasDefaultValueSql("(getdate())"); entity.Property(e => e.KpvKpiId).HasColumnName("KPV_KPI_ID"); entity.Property(e => e.KpvValue) .HasColumnType("decimal(18, 2)") .HasColumnName("KPV_Value"); entity.HasOne(d => d.KpvKpi) .WithMany(p => p.KpvKpiValues) .HasForeignKey(d => d.KpvKpiId) .OnDelete(DeleteBehavior.ClientSetNull) .HasConstraintName("FK__KPV_KPI_V__KPV_K__3587F3E0"); }); OnModelCreatingPartial(modelBuilder); } partial void OnModelCreatingPartial(ModelBuilder modelBuilder); } }
local.settings.json
{ "IsEncrypted": false, "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", "Values": { "sqlconnectionstring": "Server=tcp:progetto-kpi.database.windows.net,1433;Initial Catalog=progetto-kpi-database;Persist Security Info=False;User ID=progetto-kpi;Password=*********;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;", "ProviderName": "System.Data.SqlClient" } }
azuretosharepoint.csproj
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <AzureFunctionsVersion>v3</AzureFunctionsVersion> <UserSecretsId>4e8db286-9c45-4574-b556-eb0a04ac345e</UserSecretsId> <OutputType>Library</OutputType> </PropertyGroup> <ItemGroup> <PackageReference Include="Dapper" Version="2.0.90" /> <PackageReference Include="Dapper.Contrib" Version="2.0.78" /> <PackageReference Include="Dapper.FluentMap" Version="2.0.0" /> <PackageReference Include="Dapper.SimpleCRUD" Version="2.3.0" /> <PackageReference Include="EntityFramework" Version="6.4.4" /> <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.18.0-beta1" /> <PackageReference Include="Microsoft.AspNetCore.AzureKeyVault.HostingStartup" Version="2.0.4" /> <PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="2.0.0-preview1-final" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.10" /> <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.12" /> <PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0-preview.4.21253.7" /> <PackageReference Include="System.Data.Common" Version="4.3.0" /> <PackageReference Include="System.Data.SqlClient" Version="4.8.2" /> <PackageReference Include="System.IO" Version="4.3.0" /> <PackageReference Include="System.Linq" Version="4.3.0" /> <PackageReference Include="System.Runtime" Version="4.3.1" /> </ItemGroup> <ItemGroup> <None Update="host.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> <None Update="local.settings.json"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToPublishDirectory>Never</CopyToPublishDirectory> </None> </ItemGroup> <ItemGroup> <Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" /> </ItemGroup> </Project>
运行错误信息
Azure Functions Core Tools Core Tools Version: 3.0.3568 Commit hash: e30a0ede85fd498199c28ad699ab2548593f759b (64-bit) Function Runtime Version: 3.0.15828.0 Can't determine project language from files. Please use one of [--csharp, --javascript, --typescript, --java, --python, --powershell, --custom] Missing value for AzureWebJobsStorage in local.settings.json. This is required for all triggers other than httptrigger, kafkatrigger, rabbitmqtrigger, orchestrationTrigger, activityTrigger, entityTrigger. You can run 'func azure functionapp fetch-app-settings <functionAppName>' or specify a connection string in local.settings.json.
解决方案
1. 修复local.settings.json结构错误
你的配置文件结构不符合Azure Functions的要求,AzureWebJobsStorage和FUNCTIONS_WORKER_RUNTIME必须放在Values节点下,否则运行时无法读取这些核心配置。修改后的正确结构如下:
{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", "sqlconnectionstring": "Server=tcp:progetto-kpi.database.windows.net,1433;Initial Catalog=progetto-kpi-database;Persist Security Info=False;User ID=progetto-kpi;Password=*********;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;", "ProviderName": "System.Data.SqlClient" } }
2. 正确通过依赖注入获取连接字符串
Azure Functions中,直接使用Environment.GetEnvironmentVariable可能无法正确读取local.settings.json中的配置(尤其是本地调试场景)。建议通过依赖注入IConfiguration来读取连接字符串,同时重构DbContext的配置方式:
修改DbContext构造函数
public partial class ProgettokpidatabaseContext : DbContext { private readonly IConfiguration _configuration; public ProgettokpidatabaseContext(IConfiguration configuration) { _configuration = configuration; } public ProgettokpidatabaseContext(DbContextOptions<ProgettokpidatabaseContext> options, IConfiguration configuration) : base(options) { _configuration = configuration; } // 保留原有的DbSet和OnModelCreating代码... protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { optionsBuilder.UseSqlServer(_configuration["sqlconnectionstring"]); } } }
修改Function使用依赖注入
将静态函数改为实例类,通过构造函数注入DbContext,同时使用ILogger替代Console.WriteLine(Functions推荐使用日志组件输出信息):
namespace azuretosharepoint { public class Function1 { private readonly ProgettokpidatabaseContext _dbContext; public Function1(ProgettokpidatabaseContext dbContext) { _dbContext = dbContext; } [FunctionName("Function1")] public async Task RunAsync([TimerTrigger("0 */1 * * * *")] TimerInfo myTimer, ILogger log) { var listsharepoint = await _dbContext.KpiTables.ToListAsync(); log.LogInformation($"成功获取{listsharepoint.Count}条KPI记录"); } } }
3. 修复EF Core与.NET Core版本不兼容问题
你的项目目标框架是.netcoreapp3.1,但使用的EF Core版本是5.0.1——这两个版本不兼容(EF Core 5.x对应.NET 5.0)。你有两个选择:
- 升级项目框架:将csproj中的
<TargetFramework>改为net5.0,适配EF Core 5.x:<TargetFramework>net5.0</TargetFramework> - 降级EF Core版本:将所有EF Core相关包降级到
3.1.x系列,确保版本一致,例如:<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.20" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.20"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.20" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.20" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.20"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference>
4. 解决项目语言识别问题
如果仍然出现Can't determine project language from files提示,运行Functions Core Tools时可以手动指定语言参数:
func start --csharp
内容的提问来源于stack exchange

