チュートリアル: ASP.NET MVC アプリで Entity Framework で接続の回復性とコマンド インターセプトを使用する
これまで、アプリケーションは開発用コンピューター上の IIS Express でローカルに実行されています。 実際のアプリケーションを他のユーザーがインターネット経由で使用できるようにするには、それを Web ホスティング プロバイダーにデプロイする必要があり、データベース サーバーにデータベースをデプロイする必要があります。
このチュートリアルでは、接続の回復性とコマンドのインターセプトを使用する方法について説明します。 これらは Entity Framework 6 の 2 つの重要な機能であり、クラウド環境にデプロイするときに特に重要です: 接続の回復性 (一時的なエラーの自動再試行) とコマンド インターセプト (ログ記録または変更のためにデータベースに送信されたすべての SQL クエリをキャッチ) です。
この接続の回復性とコマンド インターセプトのチュートリアルは省略可能です。 このチュートリアルをスキップする場合は、後続のチュートリアルで若干の調整を行う必要があります。
このチュートリアルでは、次の作業を行いました。
- 接続の回復性を有効にする
- コマンド インターセプトを有効にする
- 新しい構成をテストする
前提条件
接続の回復性を有効にする
Windows Azure にアプリケーションをデプロイする場合は、クラウド データベース サービスである Windows Azure SQL Database にデータベースをデプロイします。 一時的な接続エラーは、通常、Web サーバーとデータベース サーバーが同じデータ センターで直接接続されている場合よりも、クラウド データベース サービスに接続しているときの方が頻繁に発生します。 クラウド Web サーバーとクラウド データベース サービスが同じデータ センターでホストされていても、ロード バランサーなどの問題が発生する可能性のあるネットワーク接続で接続されていることが多く見受けられます。
また、クラウド サービスは通常、他のユーザーによって共有されます。つまり、その応答性がユーザーによって影響を受ける可能性があります。 また、データベースへのアクセスは、スロットリングの対象になる可能性があります。 スロットリングとは、サービス レベル アグリーメント (SLA) で許可されているよりも頻繁にアクセスしようとすると、データベース サービスが例外をスローすることです。
クラウド サービスにアクセスするときの接続の問題の多くまたはほとんどは一時的なものであり、短時間で解決されます。 そのため、データベース操作を試して、通常は一時的に問題となるエラーが発生した場合は、少し待ってからもう一度操作を試すことで、操作が成功することもあります。 一時的なエラーを自動的にやり直すことで、ほとんどの場合、顧客には気付かれずに対処することができ、はるかに優れたエクスペリエンスをユーザーに提供できます。 Entity Framework 6 の接続の回復性機能では、失敗した SQL クエリを再試行するプロセスが自動化されます。
接続の回復性機能は、特定のデータベース サービスに対して適切に構成する必要があります。
- どの例外が一時的である可能性が高いかを把握する必要があります。 たとえば、プログラムのバグによって発生したエラーではなく、ネットワーク接続の一時的な損失によって発生したエラーを再試行する必要があります。
- 失敗した操作を再試行するまで、適切な時間を待機する必要があります。 バッチ プロセスの再試行の間隔は、ユーザーが応答を待機しているオンライン Web ページよりも長く待機できます。
- それを放棄する前に、適切な回数で再試行する必要があります。 オンライン アプリケーションで行うバッチ プロセスで再試行回数を増やしたいと思うことかもしれません。
これらの設定は、Entity Framework プロバイダーが環境サポート データベースに対して手動で構成できますが、Windows Azure SQL Database を使用するオンライン アプリケーションで通常適切に機能する既定値は既に構成されており、これらは Contoso University アプリケーションに実装する設定です。
接続の回復性を有効にするために必要なのは、DbConfiguration クラスから派生したクラスをアセンブリに作成することであり、そのクラスで SQL Database 実行戦略を設定することです。これは EF では "再試行ポリシー" と呼ばれます。
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 ファイルで実行する構成タスクをコードで実行できます。 詳細については、EntityFramework コードベースの構成に関するページを参照してください。StudentController.cs で、
System.Data.Entity.Infrastructure
のusing
ステートメントを追加します。using System.Data.Entity.Infrastructure;
代わりに
RetryLimitExceededException
例外をキャッチするように、DataException
例外をキャッチするすべてのcatch
ブロックを変更します。 次に例を示します。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 の接続の回復性/再試行ロジック」を参照してください。
コマンド インターセプトを有効にする
再試行ポリシーを有効にしましたが、期待どおりに動作していることをどのように確認できるでしょうか。 特にローカルで実行している場合、一時的なエラーを強制的に発生させるのは簡単ではありません。また、実際の一時的なエラーを自動単体テストに統合することはさらに困難です。 接続の回復性機能をテストするには、Entity Framework が SQL Server に送信するクエリをインターセプトし、SQL Server の応答を、通常は一時的である例外タイプに置き換える手段が必要です。
また、クエリ インターセプトを使用して、クラウド アプリケーションのベスト プラクティスを実装することもできます: データベース サービスなどの外部サービスへのすべての呼び出しの待機時間と成功または失敗をログに記録します。 EF6 には、ログ記録を容易にする専用のログ記録 API が用意されていますが、このチュートリアルのこのセクションでは、Entity Framework の インターセプト機能をログ記録と一時的なエラーのシミュレーションの両方に直接する方法について説明します。
ログ インターフェイスとクラスを作成する
ログ記録のベスト プラクティスは、System.Diagnostics.Trace またはログ クラスの呼び出しをハードコーディングするのではなく、インターフェイスを使用して実行することです。 これにより、ログ記録メカニズムを後で変更する必要が生じた場合に、簡単に変更できます。 そのため、このセクションでは、ログ インターフェイスとそれを実装するクラスを作成します。/p>
プロジェクトにフォルダーを作成し、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 つのトレース レベルと、データベース クエリなどの外部サービスの呼び出しにかかる待機時間情報を提供するように設計された機能が用意されています。 ログ メソッドには、例外で渡すことができるオーバーロードがあります。 これは、スタック トレースや内部例外を含む例外情報が、アプリケーション全体の各ログ メソッド呼び出しで行われることに依存するのではなく、インターフェイスを実装するクラスによって確実にログに記録されるようにするためです。
TraceApi メソッドを使用すると、SQL Database などの外部サービスへの各呼び出しの待機時間を追跡できます。
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 // https://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 トレースと一緒に使用できる "リスナー" が多数あり、たとえばログをファイルに書き込んだり、それらを Azure の BLOB ストレージに書き込んだりすることができます。 詳細については、Visual Studio での Windows Azure Web サイトのトラブルシューティングに関するページにある、いくつかのオプションと他のリソースへのリンクを参照してください。 このチュートリアルでは、Visual Studio の [出力] ウィンドウだけでログを確認します。
運用アプリケーションでは、System.Diagnostics 以外のトレース パッケージを検討する必要がある可能性があり、ILogger インターフェイスを使用すると、別のトレース メカニズムに簡単に切り替えることができます。
インターセプター クラスを作成する
次に、Entity Framework がデータベースにクエリを送信するたびに呼び出すクラスを作成します。1 つは一時的なエラーをシミュレートし、もう 1 つはログ記録を実行します。 これらのインターセプター クラスは、DbCommandInterceptor
クラスから派生させる必要があります。 その中で、クエリの実行時に自動的に呼び出されるメソッド オーバーライドを記述します。 これらのメソッドでは、データベースに送信されるクエリを確認またはログに記録できます。また、データベースに送信される前にクエリを変更したり、クエリをデータベースに渡すことなく、自分で Entity Framework に何かを返したりすることができます。
データベースに送信されるすべての SQL クエリをログに記録するインターセプター クラスを作成するには、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); } } }
クエリまたはコマンドを正常に実行するために、このコードは待機時間情報を含む情報ログを書き込みます。 例外の場合は、エラー ログが作成されます。
[検索] ボックスに「Throw」と入力したときにダミーの一時的なエラーを生成するインターセプター クラスを作成するには、DAL フォルダーに SchoolInterceptorTransientErrors.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 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; } } }
このコードは、複数行のデータを返すことができるクエリに対して呼び出される
ReaderExecuting
メソッドのみをオーバーライドします。 他の種類のクエリに対して接続の回復性をチェックする場合は、ログ インターセプターと同様に、NonQueryExecuting
とScalarExecuting
メソッドをオーバーライドすることもできます。Student ページを実行し、検索文字列として「Throw」と入力すると、このコードにより、エラー番号 20 に対してダミーの SQL Database 例外が作成されます。これは、一般的に一時的であることがわかっている型です。 現在一時的と認識されているその他のエラー番号は、64、233、10053、10054、10060、10928、10929、40197、40501、40613 ですが、これらは新しいバージョンの SQL Database で変更される可能性があります。
このコードは、クエリを実行してクエリ結果を返すのではなく、Entity Framework に例外を返します。 一時的な例外が 4 回返された後、コードはクエリをデータベースに渡す通常のプロシージャに戻ります。
すべてがログに記録されるため、最終的に成功する前に Entity Framework がクエリを 4 回実行しようとしていることがわかります。このアプリケーションの唯一の違いは、クエリ結果を含むページのレンダリングに時間がかかるということです。
Entity Framework が再試行する回数は構成可能です。コードでは 4 回が指定されていますが、これは SQL Database 実行ポリシーの既定値であるためです。 実行ポリシーを変更する場合は、ここで、一時的なエラーが生成される回数を指定するコードも変更します。 Entity Framework が
RetryLimitExceededException
例外をスローするように、コードを変更して例外が生成される回数を増やすこともできます。[検索] ボックスに入力する値は
command.Parameters[0]
とcommand.Parameters[1]
になります (1 つは名に使用され、1 つは姓に使用されます)。 値 "%Throw%" が見つかると、それらのパラメーターで "Throw" が "an" に置き換えられ、一部の学生が見つかり、返されます。これは、アプリケーション UI への入力の変更に基づいて接続の回復性をテストする便利な方法にすぎません。 DbInterception.Add メソッドに関するコメントで後述するように、すべてのクエリまたは更新に対して一時的なエラーを生成するコードを記述することもできます。
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()); }
これらのコード行によって、Entity Framework がクエリをデータベースに送信するときにインターセプター コードが実行されます。 一時的なエラー シミュレーションとログ記録用に個別のインターセプター クラスを作成したため、個別に有効または無効にすることができます。
コード内の任意の場所で
DbInterception.Add
メソッドを使用して、インターセプターを追加できます。とはいえ、必ずしもApplication_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 回追加すると、SQL クエリごとに 2 つのログが出力されます。インターセプターは、登録の順序 (
DbInterception.Add
メソッドが呼び出される順序) で実行されます。 順序は、インターセプターで何を実行するかによって異なる場合があります。 たとえば、インターセプターによって、CommandText
プロパティで取得する SQL コマンドが変更されることがあります。 SQL コマンドが変更された場合、次のインターセプターは、元の SQL コマンドではなく、変更された SQL コマンドを取得します。UI に別の値を入力して一時的なエラーを発生させる方法で、一時的なエラー シミュレーション コードを記述しました。 別の方法として、特定のパラメーター値をチェックせずに、常に一時的な例外のシーケンスを生成するインターセプター コードを記述することもできます。 一時的なエラーを生成する場合にのみ、インターセプターを追加できます。 ただし、これを行う場合は、データベースの初期化が完了するまでインターセプターを追加しないでください。 つまり、一時的なエラーを発生させる前に、エンティティ セットの 1 つに対するクエリなど、少なくとも 1 つのデータベース操作を実行します。 Entity Framework では、データベースの初期化中にいくつかのクエリが実行され、トランザクションでは実行されないため、初期化中にエラーが発生すると、コンテキストが不整合な状態になる可能性があります。
新しい構成をテストする
F5 キーを押してデバッグ モードでアプリケーションを実行し、[学生] タブをクリックします。
Visual Studio の [出力] ウィンドウを見て、トレース出力を確認します。 ロガーが書き込んだログを取得するには、JavaScript エラーを超えて上にスクロールする必要がある場合があります。
データベースに送信された実際の SQL クエリを確認できることに注目してください。 Entity Framework が開始するために行う最初のクエリとコマンドを確認し、データベースのバージョンと移行履歴テーブルを確認します (次のチュートリアルで移行について学習します)。 また、ページングのクエリが表示され、学生の数が確認され、最後に学生データを取得するクエリが表示されます。
[学生] ページで、検索文字列として「Throw」と入力し、[検索] をクリックします。
Entity Framework がクエリを複数回再試行している間、ブラウザーが数秒間ハングしているように見えます。 最初の再試行は非常に迅速に行われます。その後、各追加の再試行の前に待機が増加します。 再試行ごとにより長く待機するこのプロセスは、"エクスポネンシャル バックオフ" と呼ばれます。
ページが表示されると、名前に "an" を持つ学生が表示され、出力ウィンドウを確認すると、同じクエリが 5 回試行され、最初の 4 回は一時的な例外が返されたことがわかります。 一時的なエラーごとに、
SchoolInterceptorTransientErrors
クラスで一時的なエラーを生成するときに書き込むログ ("Returning transient error for command...") が表示され、SchoolInterceptorLogging
が例外を取得したときに書き込まれたログが表示されます。検索文字列を入力したので、学生データを返すクエリはパラメーター化されます:
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
オブジェクトのParameters
プロパティからパラメーター値を取得するログ コードを記述できます。アプリケーションを停止して再起動しない限り、このテストを繰り返すことはできません。 アプリケーションの 1 回の実行で接続の回復性を複数回テストできるようにする場合は、
SchoolInterceptorTransientErrors
でエラー カウンターをリセットするコードを記述できます。実行戦略 (再試行ポリシー) の違いを確認するには、SchoolConfiguration.cs の
SetExecutionStrategy
行をコメント アウトし、デバッグ モードで [学生] ページをもう一度実行し、もう一度 "Throw" を検索します。今回は、初めてクエリを実行しようとしたときに、最初に生成された例外でデバッガーが直ちに停止します。
SchoolConfiguration.cs で SetExecutionStrategy 行のコメントを解除します。
コードを取得する
その他のリソース
他の Entity Framework リソースへのリンクは、「ASP.NET データ アクセス - 推奨リソース」にあります。
次のステップ
このチュートリアルでは、次の作業を行いました。
- 接続の回復性を有効にしました
- コマンド インターセプトを有効にしました
- 新しい構成をテストしました
次の記事に進み、Code First の移行と Azure デプロイについて学習します。