翻訳:MVC5のEntity Framework 6入門(4)-MVCプログラムでのEntity Frameworkの接続回復とコマンドインターセプト

MVCプログラムでのエンティティフレームワークの接続回復とコマンドインターセプト

これは、MVC 5シリーズを使用したMicrosoftの公式チュートリアルGetting Entity Framework 6 Code Firstの翻訳です。4番目の記事は、MVCプログラムでのEntity Frameworkの接続回復とコマンドインターセプトです。

原文:ASP.NET MVCアプリケーションでのエンティティフレームワークによる接続の復元力とコマンドのインターセプト


これまでのところ、アプリケーションはローカルマシンですでに正常に実行できます。しかし、より多くの人が使用できるようにインターネットで公開したい場合は、プログラムをWEBサーバーに、データベースをデータベースサーバーに展開する必要があります。

このチュートリアルでは、Entity Frameworkをクラウド環境にデプロイするときに、接続応答(一時的なエラーの自動再試行)とコマンドインターセプト(データベースに送信されたすべてのSQLクエリステートメントをキャプチャする)の2つの重要な機能を学びます。ログに記録するか、変更してください)。

注:このチュートリアルはオプションです。このセクションをスキップすると、後続のチュートリアルでいくつかの小さな調整が行われます。

接続回復を有効にする

アプリケーションをWindows Azureにデプロイするときは、データベースをWindows Azure SQLデータベース(クラウドデータベースサービス)にデプロイします。Webサーバーとデータベースを同じデータセンターに直接接続する場合と比較して、クラウドデータベースサービスに接続すると、一時的な接続エラーが発生する可能性が高くなります。クラウドWebサーバーとクラウドデータベースサーバーが同じデータセンターのコンピュータールームにある場合でも、それらのサーバー間に大量のデータ接続がある場合、負荷分散などのさまざまな問題が発生しやすくなります。

また、通常、クラウドサーバーは他のユーザーと共有されているため、他のユーザーの影響を受ける可能性があります。データベースへのアクセス権が制限されている可能性があります。また、データベースサーバーに頻繁にアクセスしようとすると、SLAベースの帯域幅制限が発生する場合もあります。ほとんどの接続の問題は、クラウドサーバーに接続するときに瞬時に発生し、短時間で自動的に問題の解決を試みます。したがって、データベースに接続しようとしてエラーが発生した場合、エラーは一時的なものである可能性が高く、試行を繰り返すと、エラーは存在しなくなる可能性があります。自動一時エラー再試行を使用して、カスタマーエクスペリエンスを向上させることができます。Entity Framework 6の接続回復では、誤ったSQLクエリを自動的に再試行できます。

接続回復機能は、特定のデータベースサービスを正しく構成した後でのみ使用できます。

  • これらの例外は一時的なものである可能性があることを知っておく必要があります。プログラミングのバグではなく、ネットワーク接続が原因のエラーを再試行したい場合。
  • 失敗した操作の間は、適切な時間待機する必要があります。オンラインユーザーは、バッチで再試行する場合、応答を受け取るまでに長い時間待たなければならない場合があります。
  • 適切な回数の再試行を設定する必要があります。オンラインアプリケーションでは、複数回再試行できます。

Entity Frameworkプロバイダーでサポートされているデータベース環境では、これらの設定を手動で構成できますが、Entity Frameworkは、Windows Azure SQLデータベースを使用するオンラインアプリケーションの既定の構成を既に作成しています。次に、これらの構成をContoso Universityに実装します。

接続回復を有効にする場合は、アセンブリ内のDbConfigurationから派生したクラスを作成する必要があります。これは、接続回復の再試行戦略を含むSQLデータベース実行戦略の構成に使用されます。

DALフォルダーに、SchoolConfiguration.csという新しいクラスを追加します。

クラス内のものを次のコードで置き換えます。

using System.Data.Entity;
using System.Data.Entity.SqlServer;

namespace ContosoUniversity.DAL
{
    public class SchoolConfiguration : DbConfiguration
    {
        public SchoolConfiguration()
        {
            SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy());
        }
    }
}

Entity Frameworkは、DbConfigurationクラスから派生したクラスにあるコードを自動的に実行します。また、Dbconfigurationクラスを使用して、web.configで構成することもできます。

学生コントローラーで、参照を追加します。

using System.Data.Entity.Infrastructure;

DataExceptionをキャッチするすべての例外コードブロックを変更し、RetryLimitExcededExceptionを使用します。

catch (RetryLimitExceededException /* dex */)
{
    //Log the error (uncomment dex variable name and add a line here to write a log.
    ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}

以前は、DataExceptionを使用していました。これは、一時的なエラーを含む可能性のある例外を見つけようとし、ユーザーにわかりやすい再試行プロンプトメッセージを返しますが、自動再試行戦略をオンにしたので、複数の再試行後にも失敗するエラーはRetryLimitExceededExceptionにラップされます。 。

詳細については、「エンティティフレームワークの接続の復元力/再試行ロジック」を参照してください。

コマンドインターセプトを有効にする

これで再試行戦略がオンになりましたが、それが期待どおりに機能していることを確認するためにどのようにテストしますか?特にローカルで実行している場合は、一時的なエラーを強制することは簡単ではありません。また、一時的なエラーを自動化された単体テストに統合することは困難です。接続回復機能をテストする場合は、Entity FrameworkからSQLデータベースに送信されたクエリを傍受し、SQLデータベースを置き換えて応答を返すことができるメソッドが必要です。

クラウドアプリケーションのベストプラクティスに従うこともできます。クエリインターセプトを実装するために、外部サービスへのすべての呼び出しのレイテンシと成功または失敗を記録します。Entity Framework 6は、ログを簡単に作成できるようにする専用のログAPIを提供します。ただし、このチュートリアルでは、Entity Frameworkのインターセプト機能を直接使用する方法(一時的なエラーのロギングとシミュレーションを含む)を学習します。

ロギングインターフェイスとクラスを作成する

ログのベストプラクティスは、ハードコーディングを使用する代わりに、インターフェイスを介してSystem.Diagnostice.Traceまたはログクラスを呼び出すことです。これにより、後で必要に応じてロギングメカニズムを簡単に変更できます。このセクションでは、インターフェースを作成して実装します。

プロジェクトにフォルダーを作成し、Loggingという名前を付けます。

Loggingフォルダーで、ILogger.csという名前のインターフェイスクラスを作成し、自動生成されたものを次のコードに置き換えます。

using System;

namespace ContosoUniversity.Logging
{
    public interface ILogger
    {
        void Information(string message);
        void Information(string fmt, params object[] vars);
        void Information(Exception exception, string fmt, params object[] vars);

        void Warning(string message);
        void Warning(string fmt, params object[] vars);
        void Warning(Exception exception, string fmt, params object[] vars);

        void Error(string message);
        void Error(string fmt, params object[] vars);
        void Error(Exception exception, string fmt, params object[] vars);

        void TraceApi(string componentName, string method, TimeSpan timespan);
        void TraceApi(string componentName, string method, TimeSpan timespan, string properties);
        void TraceApi(string componentName, string method, TimeSpan timespan, string fmt, params object[] vars);

    }
}

このインターフェースは、ログの相対的な重要性を示す3つのトレースレベルを提供し、外部サービスコール(データベースクエリなど)の待ち時間情報を提供するように設計されています。logメソッドは、例外を渡すことができるオーバーロードを提供します。このようにして、例外情報をスタックに含めることができ、内部の例外は、アプリケーションからの各ログメソッドを呼び出して記録するのではなく、インターフェイスによって実装されたクラスによって確実に記録できます。

TraceAPIメソッドを使用すると、外部サービス(SQL Serverなど)への各呼び出しの遅延時間をトレースできます。Loggingフォルダーで、Logger.csという名前のクラスを作成し、自動生成されたクラスを次のコードに置き換えます。

using System;
using System.Diagnostics;
using System.Text;

namespace ContosoUniversity.Logging
{
    public class Logger : ILogger
    {

        public void Information(string message)
        {
            Trace.TraceInformation(message);
        }

        public void Information(string fmt, params object[] vars)
        {
            Trace.TraceInformation(fmt, vars);
        }

        public void Information(Exception exception, string fmt, params object[] vars)
        {
            Trace.TraceInformation(FormatExceptionMessage(exception, fmt, vars));
        }

        public void Warning(string message)
        {
            Trace.TraceWarning(message);
        }

        public void Warning(string fmt, params object[] vars)
        {
            Trace.TraceWarning(fmt, vars);
        }

        public void Warning(Exception exception, string fmt, params object[] vars)
        {
            Trace.TraceWarning(FormatExceptionMessage(exception, fmt, vars));
        }

        public void Error(string message)
        {
            Trace.TraceError(message);
        }

        public void Error(string fmt, params object[] vars)
        {
            Trace.TraceError(fmt, vars);
        }

        public void Error(Exception exception, string fmt, params object[] vars)
        {
            Trace.TraceError(FormatExceptionMessage(exception, fmt, vars));
        }

        public void TraceApi(string componentName, string method, TimeSpan timespan)
        {
            TraceApi(componentName, method, timespan, ""); 
        }

        public void TraceApi(string componentName, string method, TimeSpan timespan, string fmt, params object[] vars)
        {
            TraceApi(componentName, method, timespan, string.Format(fmt, vars));
        }
        public void TraceApi(string componentName, string method, TimeSpan timespan, string properties)
        {
            string message = String.Concat("Component:", componentName, ";Method:", method, ";Timespan:", timespan.ToString(), ";Properties:", properties);
            Trace.TraceInformation(message);
        }

        private static string FormatExceptionMessage(Exception exception, string fmt, object[] vars)
        {
            // Simple exception formatting: for a more comprehensive version see 
            // http://code.msdn.microsoft.com/windowsazure/Fix-It-app-for-Building-cdd80df4
            var sb = new StringBuilder();
            sb.Append(string.Format(fmt, vars));
            sb.Append(" Exception: ");
            sb.Append(exception.ToString());
            return  sb.ToString();
        }
    }
}

追跡にはSystem.Diagnosticsを使用しました。これは.Netの組み込み機能で、追跡情報の生成と使用を簡単にします。System.Diagnosticsのさまざまなリスナーを使用して、ログファイルを追跡し、書き込むことができます。たとえば、それらをblobストレージに保存するか、Windows Azureに保存します。Visual StudioでのWindows Azure Webサイトのトラブルシューティングで、その他のオプションと関連情報を見つけることができます。このチュートリアルでは、VS出力ウィンドウにログのみが表示されます。運用環境では、System.Diagnosticsの代わりに追跡パッケージを使用することができます。必要な場合、ILoggerインターフェイスを使用すると、別の追跡メカニズムに比較的簡単に切り替えることができます。

インターセプタークラスを作成する

次に、Entity Frameworkがデータベースにクエリを実行するたびに呼び出されるいくつかのクラスを作成します。それらの1つは一時的なエラーと他のログをシミュレートします。これらのインターセプタークラスは、DbCommandInterceptorクラスから派生する必要があります。クエリの実行時に自動的に呼び出されるように、メソッドを書き直す必要があります。これらのメソッドでは、データベースに送信されたクエリを確認または記録したり、データベースに送信する前にクエリを変更したり、クエリのためにデータベースに送信せずにエンティティフレームワークに返すこともできます。

DALフォルダーにSchoolInterceptorLogging.csという名前のクラスを作成し、自動生成されたクラスを次のコードに置き換えます。

using System;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure.Interception;
using System.Data.Entity.SqlServer;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Reflection;
using System.Linq;
using ContosoUniversity.Logging;

namespace ContosoUniversity.DAL
{
    public class SchoolInterceptorLogging : DbCommandInterceptor
    {
        private ILogger _logger = new Logger();
        private readonly Stopwatch _stopwatch = new Stopwatch();

        public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            base.ScalarExecuting(command, interceptionContext);
            _stopwatch.Restart();
        }

        public override void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            _stopwatch.Stop();
            if (interceptionContext.Exception != null)
            {
                _logger.Error(interceptionContext.Exception, "Error executing command: {0}", command.CommandText);
            }
            else
            {
                _logger.TraceApi("SQL Database", "SchoolInterceptor.ScalarExecuted", _stopwatch.Elapsed, "Command: {0}: ", command.CommandText);
            }
            base.ScalarExecuted(command, interceptionContext);
        }

        public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
            base.NonQueryExecuting(command, interceptionContext);
            _stopwatch.Restart();
        }

        public override void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
            _stopwatch.Stop();
            if (interceptionContext.Exception != null)
            {
                _logger.Error(interceptionContext.Exception, "Error executing command: {0}", command.CommandText);
            }
            else
            {
                _logger.TraceApi("SQL Database", "SchoolInterceptor.NonQueryExecuted", _stopwatch.Elapsed, "Command: {0}: ", command.CommandText);
            }
            base.NonQueryExecuted(command, interceptionContext);
        }

        public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            base.ReaderExecuting(command, interceptionContext);
            _stopwatch.Restart();
        }
        public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            _stopwatch.Stop();
            if (interceptionContext.Exception != null)
            {
                _logger.Error(interceptionContext.Exception, "Error executing command: {0}", command.CommandText);
            }
            else
            {
                _logger.TraceApi("SQL Database", "SchoolInterceptor.ReaderExecuted", _stopwatch.Elapsed, "Command: {0}: ", command.CommandText);
            }
            base.ReaderExecuted(command, interceptionContext);
        }
    }
}

正常に照会されたコマンドの場合、このコードは関連情報と遅延情報をログに書き込み、例外の場合はエラーログを作成します。

DALフォルダーにSchoolInterceptorTransientErrors.csという名前のクラスを作成します。このクラスは、検索ボックスに「Throw」と入力してクエリを実行すると、仮想一時エラーを生成します。自動生成されたコードを次のコードに置き換えます。

using System;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure.Interception;
using System.Data.Entity.SqlServer;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Reflection;
using System.Linq;
using ContosoUniversity.Logging;

namespace ContosoUniversity.DAL
{
    public class SchoolInterceptorTransientErrors : DbCommandInterceptor
    {
        private int _counter = 0;
        private ILogger _logger = new Logger();

        public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            bool throwTransientErrors = false;
            if (command.Parameters.Count > 0 && command.Parameters[0].Value.ToString() == "%Throw%")
            {
                throwTransientErrors = true;
                command.Parameters[0].Value = "%an%";
                command.Parameters[1].Value = "%an%";
            }

            if (throwTransientErrors && _counter < 4)
            {
                _logger.Information("Returning transient error for command: {0}", command.CommandText);
                _counter++;
                interceptionContext.Exception = CreateDummySqlException();
            }
        }

        private SqlException CreateDummySqlException()
        {
            // The instance of SQL Server you attempted to connect to does not support encryption
            var sqlErrorNumber = 20;

            var sqlErrorCtor = typeof(SqlError).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).Where(c => c.GetParameters().Count() == 7).Single();
            var sqlError = sqlErrorCtor.Invoke(new object[] { sqlErrorNumber, (byte)0, (byte)0, "", "", "", 1 });

            var errorCollection = Activator.CreateInstance(typeof(SqlErrorCollection), true);
            var addMethod = typeof(SqlErrorCollection).GetMethod("Add", BindingFlags.Instance | BindingFlags.NonPublic);
            addMethod.Invoke(errorCollection, new[] { sqlError });

            var sqlExceptionCtor = typeof(SqlException).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).Where(c => c.GetParameters().Count() == 4).Single();
            var sqlException = (SqlException)sqlExceptionCtor.Invoke(new object[] { "Dummy", errorCollection, null, Guid.NewGuid() });

            return sqlException;
        }
    }
}

このコードは、クエリ結果データの複数の行を返すために使用されるReaderExcutingメソッドのみを書き換えます。他の種類の接続回復を確認する場合は、ログインターセプターで行うのと同じように、NonQueryExecutingやScalarExecutingなどのメソッドをオーバーライドできます。生徒のページを実行して検索文字列として「Throw」と入力すると、コードはエラー番号20の仮想SQLデータベースを作成し、一時的なエラータイプとして扱われます。現在認識されている一時的なエラー番号は64,233,10053,10060,10928,10929,40197,40501および40613などです。SQLデータベースの新しいバージョンをチェックして、この情報を確認できます。

このコードは、クエリを実行してクエリ結果を返す代わりに、Entity Frameworkに例外を返します。一時的な例外が4回返された後、コードは正常に実行され、クエリ結果を返します。すべてのログレコードがあるので、Entity Frameworkが正常に実行される前に4つのクエリを実行したことがわかります。アプリケーションでは、唯一の違いは、ページのレンダリングに必要なイベントが長くなることです。Entity Frameworkの再試行回数は構成可能ですこのコードでは、SQLデータベース実行戦略のデフォルト値であるため、4を設定しています。実行戦略を変更する場合は、既存のコードを変更して、生成される一時エラーの数を指定する必要もあります。Entity FrameworkのRetryLimitExceededExceptionを引き起こすために、より多くの例外を生成するようにコードを変更することもできます。検索ボックスに入力した値は、command.Parameters [0]およびcommand.Parameters [1]に保存されます(1つは姓、もう1つは名)。入力値が「Throw」であることが判明した場合、パラメータは「an」に置き換えられ、一部の生徒にクエリを実行して返されます。これは、アプリケーションのUIを介して接続回復をテストする方法にすぎません。更新用のコードを記述して一時的なエラーを生成することもできます。

Global.asaxで、次のusingステートメントを追加します。

using ContosoUniversity.DAL;
using System.Data.Entity.Infrastructure.Interception;

強調表示された行をApplication_Startメソッドに追加します。

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
    DbInterception.Add(new SchoolInterceptorTransientErrors());
    DbInterception.Add(new SchoolInterceptorLogging());
}

これらのコードは、エンティティフレームワークがクエリをデータベースに送信するときにインターセプターを開始します。インターセプタークラスの一時的なエラーとロギングを個別に作成したため、それらを個別に無効化および有効化できることに注意してください。

アプリケーションの任意の場所でDbInterception.Addメソッドを使用してインターセプターを追加できます。これは、Applicetion_Startで行う必要はありません。別のオプションは、このコードを、以前に実行戦略を作成したDbConfigurationクラスに配置することです。

public class SchoolConfiguration : DbConfiguration
{
    public SchoolConfiguration()
    {
        SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy());
        DbInterception.Add(new SchoolInterceptorTransientErrors());
        DbInterception.Add(new SchoolInterceptorLogging());
    }
}

コードをどこに置いても、一度は超えないように注意してください。同じインターセプターに対してDbInterception.Addを実行すると、追加のインターセプターインスタンスが取得される可能性があります。たとえば、ロギングインターセプターを2回追加すると、クエリが両方のログに記録されます。インターセプターは、Addメソッドの登録順に実行されます。何をしたいかによっては、順序が重要になる場合があります。たとえば、最初のインターセプターはCommandTextプロパティを変更し、次のインターセプターは変更されたプロパティを取得します。

一時的なエラーをシミュレートするコードを記述しました。これで、ユーザーインターフェイスに別の値を入力してテストできます。別の方法として、インターセプターの特定のパラメーター値をチェックせずに、一時的なエラーを直接生成するコードを作成することもできます。インターセプターは、一時的なエラーをテストする場合にのみ追加してください。

テストログと接続回復

F5キーを押してプログラムをデバッグモードで実行し、[生徒]タブをクリックします。

VS出力ウィンドウをチェックしてトレース出力を表示します。ウィンドウの内容を上にスクロールしたい場合があります。

実際にデータベースに送信されたSQLクエリを確認できます。

ログイン出力ウィンドウ

参照ページで、「Throw」と入力してクエリを実行します。

スローサーチ

ブラウザが数秒間ハングすることに気付くでしょう。明らかに、Entity Frameworkはクエリを再試行しています。最初の再試行はすぐに発生し、次に各再試行クエリは少しの待機イベントを追加します。ページの実行が完了したら、出力ウィンドウを確認します。同じクエリが5回試行され、最初の4回すべてが一時的なエラー例外を返したことがわかります。一時的なエラーごとに、ログに異常な情報が表示されます。

一時的なエラー出力ウィンドウ

生データのクエリはパラメーター化されています。

SELECT TOP (3) 
    [Project1].[ID] AS [ID], 
    [Project1].[LastName] AS [LastName], 
    [Project1].[FirstMidName] AS [FirstMidName], 
    [Project1].[EnrollmentDate] AS [EnrollmentDate]
    FROM ( SELECT [Project1].[ID] AS [ID], [Project1].[LastName] AS [LastName], [Project1].[FirstMidName] AS [FirstMidName], [Project1].[EnrollmentDate] AS [EnrollmentDate], row_number() OVER (ORDER BY [Project1].[LastName] ASC) AS [row_number]
        FROM ( SELECT 
            [Extent1].[ID] AS [ID], 
            [Extent1].[LastName] AS [LastName], 
            [Extent1].[FirstMidName] AS [FirstMidName], 
            [Extent1].[EnrollmentDate] AS [EnrollmentDate]
            FROM [dbo].[Student] AS [Extent1]
            WHERE ([Extent1].[LastName] LIKE @p__linq__0 ESCAPE N'~') OR ([Extent1].[FirstMidName] LIKE @p__linq__1 ESCAPE N'~')
        )  AS [Project1]
    )  AS [Project1]
    WHERE [Project1].[row_number] > 0
    ORDER BY [Project1].[LastName] ASC:

ログに値を記録するパラメーターはありません。もちろん、記録することもできます。プロパティ値は、インターセプターメソッドのDbCommandオブジェクトのパラメータープロパティから取得できます。

アプリケーション全体を停止して再起動しない限り、このテストを繰り返すことはできません。単一のアプリケーションの実行で複数のテストを実行できるようにする場合は、SchoolInterceptorTransientErrorsのエラーカウンターをリセットするコードを記述できます。実行戦略の違いを確認するには、SchoolConfiguration.csをコメント化し、アプリケーションを閉じてデバッグを再開し、生徒のインデックスページを実行して、「Throw」と入力して検索します。

        public SchoolConfiguration()
        {
            //SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy());
        }

今回は、最初のクエリが試行されると、デバッガーはすぐに例外を出して停止します。

ダミー

コメントを外して、違いを理解するためにもう一度試してください。

まとめ

このセクションでは、Entity Frameworkの接続回復を有効にし、データベースに送信されたSQLクエリコマンドを記録する方法について説明しました。次のセクションでは、Code First Migrationsを使用してそれをインターネットに展開します。

著者情報

トム・ディクストラ Tom Dykstra -Tom Dykstraは、Microsoft Web Platform and Toolsチームのシニアプログラマー兼ライターです。

元の記事を40件公開 賞賛を25件 100,000回以上の閲覧

おすすめ

転載: blog.csdn.net/yym373872996/article/details/52951168