【问题标题】:Missing ProviderName when debugging AzureFunction as well as deploying azure function调试 AzureFunction 以及部署 azure 函数时缺少 ProviderName
【发布时间】:2019-04-03 21:32:18
【问题描述】:

我在获取DbContext 以正确从我的local.settings.json 中提取我的连接字符串时遇到问题

上下文:

  • 这是一个 Azure 函数项目
  • 主要问题代码在System.Data.Entity.Internal.AppConfig
  • 虽然我有一个local.settings.json 文件,但这不是dotnet 核心。它是 .net 4.6.1

错误信息:

'应用程序配置文件中的连接字符串'ShipBob_DevEntities'不包含所需的providerName属性。''

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"
    }
  }
}  

测试的配置版本:

  • 将提供程序名称移动到实际的 ConnectionString 令牌值中:同样的错误发生
  • ConnectionString 属性内的provider 属性设置为EntityClient:这没有任何作用
  • ShipBob_DevEntities 设为字符串值 = ConnectionString 的值:这会引发新的错误,例如

    不支持关键字元数据

  • 我尝试使用 ADO 连接字符串,它会引发 code first 异常,当您的连接字符串在 database first 方法中不正确时,该异常似乎会发生。

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

到目前为止,我已经查阅了这两篇文章。其中一种状态是将提供者名称作为它自己的令牌;但是,这不起作用。

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

有人知道在 local.settings.json 中用于实体框架连接的正确格式吗?

【问题讨论】:

    标签: c# entity-framework azure azure-functions


    【解决方案1】:

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

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

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

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

    metadata=res:///Models.myDB.csdl|res:///Models.myDB.ssdl|res://*/Models.myDB.msl;provider=System。 Data.SqlClient;提供者 连接字符串='数据源=[yourdbURL];初始 目录=myDB;持久安全信息=真;用户 id=xxxx;password=xxx;MultipleActiveResultSets=True;App=EntityFramework

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

    这是自动生成的 DbContext

    namespace myApp.Data.Models
    {   
    
        public partial class myDBEntities : DbContext
        {
            public myDBEntities()
               : base("name=myDBEntities")
            {
            }
    
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                throw new UnintentionalCodeFirstException();
            }
    
    }
    

    这是新的部分类,由您创建

    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:
    }
    

    【讨论】:

    • 您的方法比公认的答案更好。非常感谢。但是,对于第 5 步,我为我的存储库方法添加了一个基类,并在其中提取了连接字符串并将该属性传递给 DBContext Tor。
    • @JawandSingh 实际上几乎是一回事。如果您使用的是 POCO 类,这样做更容易,但使用数据库生成的 Edmx 文件,我不想更改我的 DBContext,因为它可能会在以后被覆盖。因此我使用部分类来做到这一点
    • 嘿@batmaci 我在查看您的答案后回来。我只是想让其他人知道,这个答案和我的不同之处在于,这个答案假设您可以在代码中更新一个新的 dbcontext。在我的情况下,我没有这样做的奢侈,因为我正在重用已经编写的工作单元和服务。我的解决方案提供了一种继续使用无参数 dbcontexts 的方法,而无需在构造函数中指定连接字符串。只是为了澄清大家。
    • 不幸的是,这不适用于我自己的带有 testdatalayer 的项目 - 我开始认为它不适用于当前版本的 azure 函数!
    • @RichardGriffiths 你的意思是在单元测试项目中吗?也许您可以为您的问题创建另一张工单,提供更多详细信息。
    【解决方案2】:

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

    来自原始 git hub 讨论:
    https://github.com/Azure/azure-functions-cli/issues/46
    将提供程序名称显示为帕斯卡大小写

    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 语法的链接: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

    【讨论】:

    • 这究竟是如何绕过生产中的 providerName 问题的?我知道在本地您正在传递 ProviderName 但在产品中您仍然缺少它。您在 entityframeworkconnectionstring 中是否有任何特殊内容,或者它与您在本地设置文件 (connectionstring) 中的内容相同?
    • 实际上,如果您有 app.config 或 web.config,请您也分享一下 - 或者至少是相关位
    • 嘿 Mavi,因此构造函数正在读取的连接字符串与在 local.settings.json 中找到的完全相同的连接字符串。唯一的区别是ProviderName 被排除在外。无需在 db 上下文的构造函数中指定。我假设是因为如果您使用 dbcontext,它已经暗示提供者是实体框架。我根本没有任何app.configweb.config。我只是将上面提到的 2 个键添加到实际门户中的应用设置中。
    • 哦!发现我做错了什么!这是另一种在没有硬编码字符串的情况下初始化实体的方法(因此仍在文件中查找配置值)。这种方法有效。非常感谢阿德里安
    • 您也可以使用 ConfigurationManager 从 Azure Function 设置中获取连接字符串。
    【解决方案3】:

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

    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。

    【讨论】:

    • 感谢您的回复。这是我很乐意做的事情;但是,我正在尝试使用一堆现有的数据访问代码,这些代码使用无参数构造函数创建了 dbcontext。我试图避免编辑所有这些其他服务。我只是快速浏览了您提供的链接。这可能会有所帮助。
    • 我发现了问题。我会添加一个答案
    【解决方案4】:

    以下两种方法适合我:

    方法 1

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

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

    • 转到扩展 DbContext ("TestEntities") 的类并扩展构造函数以将连接字符串作为参数
    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
    }
    
    • 这种方法的问题是每次更新数据库时都需要更新“TestEntities”类,因为它被覆盖了

    方法 2

    • 这里的目标是保留“TestEntities”类,以避免出现方法 1 的问题

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

    • 保持 TestEntities 不变

    public partial class TestEntities : DbContext
        {
            public TestEntities ()
                : base("name=TestEntities")
            {
            }
    
    • 由于 TestEntities 是部分的,您可以通过在同一命名空间中创建另一个具有相同名称的部分来扩展该类。此类的目标是提供将连接字符串作为参数的构造函数
    
    public partial class TestEntities
    {
        public TestEntities(string connectionString)
            : base(connectionString)
        {
        }
    }
    
    • 然后您可以继续使用方法 1

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-16
      • 1970-01-01
      • 1970-01-01
      • 2014-03-29
      • 1970-01-01
      • 1970-01-01
      • 2013-09-05
      • 2017-12-03
      相关资源
      最近更新 更多