调试 AzureFunction 以及部署 azure 函数时缺少 ProviderName

Missing ProviderName when debugging AzureFunction as well as deploying azure function

我在获取 DbContext 以正确从 local.settings.json

中提取连接字符串时遇到问题

上下文:

错误信息:

'The connection string 'ShipBob_DevEntities' in the application's configuration file does not contain the required providerName attribute."'

Json配置:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "AzureWebJobsDashboard": ""
},

"ConnectionStrings": {
"ShipBob_DevEntities": {
  "ConnectionString": "metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string='data source=***;initial catalog=***;persist security info=True;User Id=***;Password=***;;multipleactiveresultsets=True;application name=EntityFramework'",
  "providerName": "System.Data.EntityClient"
    }
  }
}  

测试的配置版本:

我冒昧地使用 dotPeek 反编译了 EntityFramework.dll,并将问题追溯到 System.Data.Entity.Internal.LazyInternalConnection.TryInitializeFromAppConfig。在这个方法中有一个对 LazyInternalConnection.FindConnectionInConfig 的调用,它吐出一个 ConnectionStringSettings 对象,该对象的 ProviderName 值设置为 null。不幸的是,我无法调试它似乎用来生成此值的 AppConfig.cs class,所以我被卡住了。

目前我已经查阅了这两篇文章。其中一个声明将提供者名称作为它自己的标记;但是,这不起作用。

https://github.com/Azure/azure-functions-cli/issues/193
https://github.com/Azure/azure-functions-cli/issues/46

有人知道 local.settings.json 中用于 Entity Framework 连接的正确格式吗?

我之前遇到过类似的问题,我会用下面的方法来达到我的目的,你可以参考一下:

local.settings.json

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=brucchstorage;AccountKey=<AccountKey>",
    "AzureWebJobsDashboard": "DefaultEndpointsProtocol=https;AccountName=brucchstorage;AccountKey=<AccountKey>",
    "sqldb-connectionstring": "Data Source=.\sqlexpress;Initial Catalog=DefaultConnection;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
  },
  "ConnectionStrings": {
    "Bruce_SQLConnectionString": "Data Source=.\sqlexpress;Initial Catalog=DefaultConnection;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
  }
} 

用于检索连接字符串:

var connString = ConfigurationManager.AppSettings["sqldb-connectionstring"];
//or var connString = ConfigurationManager.ConnectionStrings["Bruce_SQLConnectionString"].ConnectionString;
using (var dbContext = new BruceDbContext(connString))
{
    //TODO:
}

或者您可以为 DbContext 初始化无参数构造函数,如下所示:

public class BruceDbContext:DbContext
{
    public BruceDbContext()
        : base("Bruce_SQLConnectionString")
    {
    }

    public BruceDbContext(string connectionString) : base(connectionString)
    {
    }
}

然后,您可以按如下方式为您的 DbContext 创建实例:

using (var dbContext = new BruceDbContext(connString))
{
    //TODO:
}

此外,您可以参考 Local settings file Azure Functions。

所以解决方案最终变得微不足道。在 local.settings.json 中指定的 ProviderName 属性必须 是驼峰式。

来自最初的 git 中心讨论:
https://github.com/Azure/azure-functions-cli/issues/46
将提供商名称显示为 pascal 大小写

https://github.com/Azure/azure-functions-cli/issues/193
在伪代码中显示提供者名称为驼峰式 很容易错过,但是你的配置部分必须完全如下

"ConnectionStrings": {
"ShipBob_DevEntities": {
  "ConnectionString": "metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string='data source=***;initial catalog=***;persist security info=True;User Id=***;Password=***;;multipleactiveresultsets=True;application name=EntityFramework'",
  "ProviderName":  "System.Data.EntityClient"
  }
}  

这些要点很重要:

  • 确保您的连接字符串具有元数据信息
  • 如果从 xml 配置复制字符串,请确保取消转义撇号
  • 确保 ProviderName 属性是 驼峰式大小写
  • 确保提供商名称是 System.Data.EntityClient

修复了部署中缺少提供者名称的问题

请注意,此答案假定您正在尝试使用 DbContext 的无参数构造函数。如果您正在创建新代码,您可以轻松地遵循第二个赞成的答案

我找到了一种方法来规避提供商名称问题,同时仍然保留对门户配置的使用,从而保留部署槽。它涉及使用静态属性设置数据库上下文的默认连接字符串

private static string _connectionString = "name=ShipBob_DevEntities";

    static ShipBob_DevEntities()
    {
        if(!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("AzureFunction")))
        {
            var connectionString = System.Environment.GetEnvironmentVariable("EntityFrameworkConnectionString");

            if (!string.IsNullOrEmpty(connectionString))
            {
                _connectionString = connectionString;
            }
        }
    }

    public ShipBob_DevEntities()
        : base(_connectionString)
    {
        this.Configuration.LazyLoadingEnabled = false;
    }  

这涉及开发人员在 Azure 门户中创建应用程序设置作为标志。在我的例子中,它是 AzureFunction。这确保我们的代码仅在 azure 函数中 运行 并且此 DbContext 的所有其他客户端,无论它们是 Web 应用程序、windows 应用程序等,仍然可以继续按预期运行。这还涉及将您的连接字符串作为 AppSetting 而不是实际的连接字符串添加到 Azure 门户。请使用包含 元数据 信息但不包含提供商名称的完整连接字符串!

编辑

您需要编辑自动生成的 .tt 文件 t4 模板,以确保如果您先使用 db,此代码不会被覆盖。

这是 T4 语法中的 link:https://docs.microsoft.com/en-us/visualstudio/modeling/writing-a-t4-text-template

下面是对 EF T4 模板的解释:https://msdn.microsoft.com/en-us/library/jj613116(v=vs.113).aspx#1159a805-1bcf-4700-9e99-86d182f143fe

我在这里经历了几个类似的问题和答案。他们中的许多人要么具有误导性,要么假设每个人都处于同一水平并且理解 azure 函数是如何工作的。像我这样的新手没有答案。我想在这里逐步总结我的解决方案。我不认为提供的答案是最好的选择,因为它会强制您更改自动生成的 edmx 文件,这些文件可能会被错误覆盖或下次从数据库更新您的 edmx。我认为这里最好的选择是使用连接字符串而不是应用程序设置。

  1. 最重要的是我们了解local.settings.json文件 不适合 AZURE。它是 运行 您在本地的应用程序的名称 明明说所以解决方案与这个文件无关。

  2. App.Config 或 Web.Config 不适用于 Azure 函数连接字符串。如果您有数据库层库,则无法像在 Asp.Net 应用程序中那样使用其中任何一个覆盖连接字符串。

  3. 为了使用,您需要在 Azure 函数的 Application Settings 下的 Azure 门户上定义连接字符串。有 连接字符串。在那里你应该复制你的 DBContext 的连接字符串。如果是 edmx,它将如下所示。有连接类型,我使用它 SQlAzure 但我测试了自定义(有人声称只适用于自定义)适用于两者。

metadata=res:///Models.myDB.csdl|res:///Models.myDB.ssdl|res://*/Models.myDB.msl;provider=System.Data.SqlClient;provider connection string='data source=[yourdbURL];initial catalog=myDB;persist security info=True;user id=xxxx;password=xxx;MultipleActiveResultSets=True;App=EntityFramework

  1. 设置完成后,您需要在应用程序中读取 url 并提供 DBContext。 DbContext 实现了一个带有连接字符串参数的构造函数。默认情况下,构造函数没有任何参数,但您可以扩展它。如果你使用的是 POCO class,你可以简单地修改 DbContext class。如果你像我一样使用数据库生成的 Edmx classes,你不想触摸自动生成的 edmx class 而不是你想在同一个命名空间中创建部分 class 并扩展这个 class 如下。

这是自动生成的 DbContext

namespace myApp.Data.Models
{   

    public partial class myDBEntities : DbContext
    {
        public myDBEntities()
           : base("name=myDBEntities")
        {
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }

}

这是新的部分class,您创建

namespace myApp.Data.Models
{
    [DbConfigurationType(typeof(myDBContextConfig))]
    partial class myDBEntities
    {

        public myDBEntities(string connectionString) : base(connectionString)
        {
        }
    }

      public  class myDBContextConfig : DbConfiguration
        {
            public myDBContextConfig()
            {
                SetProviderServices("System.Data.EntityClient", 
                SqlProviderServices.Instance);
                SetDefaultConnectionFactory(new SqlConnectionFactory());
            }
        }
    }
  1. 毕竟你可以从 Azure 设置中获取连接字符串,在你的 Azure Function 项目中使用下面的代码并提供给你的 DbContext myDBEntities 是您在 Azure 门户中为连接字符串提供的名称。
var connString = ConfigurationManager.ConnectionStrings["myDBEntities"].ConnectionString;


 using (var dbContext = new myDBEntities(connString))
{
        //TODO:
}

这里有两种适合我的方法:

方法一

  • 按照以下格式将连接字符串添加到应用程序设置(分别为local.settings.json):

metadata=res:///xxx.csdl|res:///xxx.ssdl|res://*/xxx.msl;provider=System.Data.SqlClient;provider connection string='data source=xxx.database.windows.net;initial catalog=xxx;user id=xxx;password=xxx;MultipleActiveResultSets=True;App=EntityFramework'`

  • 转到扩展 DbContext ("TestEntities") 的 class 并扩展构造函数以将连接字符串作为参数
public partial class TestEntities: DbContext
{
    public TestEntities(string connectionString)
        : base(connectionString)
    {
    }
  • 如果您想与数据库交互,您需要从应用程序设置中检索连接字符串,然后在初始化 DbContext 时将其传递过来
string connectionString = Environment.GetEnvironmentVariable("connectionStringAppSettings");

using (var dbContext = new TestEntities(connectionString))
{
// Do Something
}
  • 这种方法的问题是每次更新数据库时都需要更新 class "TestEntities" 因为它被覆盖了

方法二

  • 这里的目标是保持 class "TestEntities" 不变,以避免方法 1

  • 中的问题
  • 将连接字符串添加到应用程序设置(分别为 local.settings.json),如方法 1

  • 保持 TestEntities 不变

public partial class TestEntities : DbContext
    {
        public TestEntities ()
            : base("name=TestEntities")
        {
        }
  • 由于 TestEntities 是部分的,您可以通过在同一命名空间中创建另一个同名的部分来扩展 class。 class 的目标是提供将连接字符串作为参数的构造函数

public partial class TestEntities
{
    public TestEntities(string connectionString)
        : base(connectionString)
    {
    }
}
  • 然后你可以像方法 1 一样继续