web-dev-qa-db-ja.com

AzureFunctionのデバッグ時およびAzure関数のデプロイ時にProviderNameがありません

local.settings.jsonから接続文字列を正しくプルするためにDbContextを取得する際に問題が発生しました

環境:

  • これはAzure関数プロジェクトです
  • 主な問題コードはSystem.Data.Entity.Internal.AppConfigにあります
  • 私はlocal.settings.jsonファイルを持っていますが、これはドットネットコアではありません。 .net4.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トークン値に移動する:同じエラーが発生する
  • provider属性内のConnectionString属性をEntityClientに設定します:これは何もしませんでした
  • ShipBob_DevEntitiesを文字列値=からConnectionStringの値にする:これにより、次のような新しいエラーがスローされます。

    キーワードメタデータはサポートされていません

  • code firstアプローチで接続文字列が正しくない場合に発生するように見えるdatabase first例外をスローするADO接続文字列を使用してみました。

dotPeekを使用してEntityFramework.dllを逆コンパイルする自由を取り、問題をSystem.Data.Entity.Internal.LazyInternalConnection.TryInitializeFromAppConfigまで追跡しました。このメソッド内には、ConnectionStringSettings値がnullに設定されているProviderNameオブジェクトを吐き出すLazyInternalConnection.FindConnectionInConfigへの呼び出しがあります。残念ながら、この値を生成するために使用していると思われるAppConfig.csクラスをデバッグできないため、スタックします。

enter image description here

これまで、これら2つの記事を参照してきました。そのうちの1つは、プロバイダー名を独自のトークンとして配置することを示しています。ただし、これは機能していません。

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

Entity Framework接続のlocal.settings.jsonで使用する正しい形式を知っている人はいますか?

12
Adrian

したがって、解決策は簡単なものになりました。 local.settings.json[〜#〜]で指定されたProviderName属性は、キャメルケースである必要があります[〜#〜]

元のgitハブの議論から:
https://github.com/Azure/azure-functions-cli/issues/46
プロバイダー名をパスカルケースとして表示します

https://github.com/Azure/azure-functions-cli/issues/19
プロバイダー名がキャメルケースであることが疑似コードで表示されます見逃しがちでしたが、構成セクションは次のとおりである必要があります

"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のパラメーターなしのコンストラクターを使用しようとしていることを前提としています。新しいコードを作成している場合は、2番目の賛成の答えに従うことが簡単にできます

ポータル構成、つまりデプロイメントスロットの使用を維持しながら、プロバイダー名の問題を回避する方法を考え出しました。静的プロパティを使用してdbコンテキストのデフォルトの接続文字列を設定する必要があります

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ポータルに接続文字列を追加することも含まれます。それらmetadata情報を含み、プロバイダー名を含まない完全な接続文字列を使用してください!

編集

最初にdbを使用している場合、このコードがオーバーライドされないように、自動生成された.ttファイルt4テンプレートを編集する必要があります。

T4構文に関するリンクは次のとおりです。 https://docs.Microsoft.com/en-us/visualstudio/modeling/writing-a-t4-text-template

EFT4テンプレートの説明は次のとおりです。 https://msdn.Microsoft.com/en-us/library/jj613116(v = vs.113).aspx#1159a805-1bcf-4700-9e99-86d182f143fe

11
Adrian

私はここでいくつかの同様の質問と回答を経験しました。それらの多くは誤解を招くか、全員が同じレベルにあり、Azureの機能がどのように機能しているかを理解していると想定しています。私のような初心者には答えがありません。ここで、私のソリューションを段階的に要約したいと思います。自動生成されたedmxファイルを変更する必要があるため、提供された回答が最善の選択肢であるとは思いません。誤って上書きしたり、データベースからedmxを次に更新したりする可能性があります。また、ここでの最良のオプションは、私の意見では、アプリ設定の代わりに接続文字列を使用することです。

  1. 最も重要なことは、local.settings.jsonファイルを理解していることですIS Azure用ではありません。名前が明確に示しているように、ローカルでアプリを実行することです。したがって、解決策は何の関係もありません。このファイル。

  2. App.ConfigまたはWeb.Configは、Azure関数の接続文字列では機能しません。データベースレイヤーライブラリがある場合、Asp.Netアプリケーションで行うように、これらのいずれかを使用して接続文字列を上書きすることはできません。

  3. を使用するには、Azureポータルの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;永続的なセキュリティ情報= True;ユーザーID = xxxx;パスワード= 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設定から接続文字列を取得できます。AzureFunctionプロジェクトで、以下のコードを使用してDbContextに提供します。myDBEntitiesは、Azureポータルで接続文字列に付けた名前です。
var connString = ConfigurationManager.ConnectionStrings["myDBEntities"].ConnectionString;


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

これが私のために働く2つのアプローチです:

アプローチ1

  • 次の形式で接続文字列をアプリ設定(それぞれlocal.settings.json)に追加します。

メタデータ= res:/// xxx.csdl | res:/// xxx.ssdl | res:// */xxx.msl; provider = System.Data.SqlClient;プロバイダー接続文字列= 'データソース= xxx.database.windows.net;初期カタログ= xxx;ユーザーID = xxx;パスワード= 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

  • ここでの目標は、アプローチ1の問題を回避するために、クラス「TestEntities」をそのままにすることです。

  • アプローチ1のように、接続文字列をアプリ設定(それぞれlocal.settings.json)に追加します

  • TestEntitiesはそのままにします

public partial class TestEntities : DbContext
    {
        public TestEntities ()
            : base("name=TestEntities")
        {
        }
  • TestEntitiesは部分的であるため、同じ名前空間に同じ名前で部分的である別のクラスを作成することにより、そのクラスを拡張できます。このクラスの目的は、接続文字列を引数として受け取るコンストラクターを提供することです。

public partial class TestEntities
{
    public TestEntities(string connectionString)
        : base(connectionString)
    {
    }
}
  • その後、アプローチ1のように続けることができます
0
quervernetzt

以前に同様の問題が発生しました。目的を達成するために次のアプローチを使用します。参照してください。

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

また、Azure Functionsについては、 ローカル設定ファイル を参照してください。

0
Bruce Chen