Руководство. Использование устойчивости подключения и перехвата команд с Entity Framework в приложении MVC ASP.NET

До сих пор приложение выполнялось локально в IIS Express на компьютере разработки. Чтобы сделать реальное приложение доступным для использования другими пользователями через Интернет, необходимо развернуть его у поставщика услуг веб-размещения, а базу данных — на сервере базы данных.

В этом руководстве вы узнаете, как использовать устойчивость подключения и перехват команд. Это две важные функции Entity Framework 6, которые особенно важны при развертывании в облачной среде: устойчивость подключения (автоматические повторные попытки при временных ошибках) и перехват команд (перехват всех SQL-запросов, отправленных в базу данных, для их регистрации или изменения).

Это руководство по устойчивости подключения и перехвату команд является необязательным. Если вы пропустите это руководство, в последующих руководствах потребуется внести несколько незначительных изменений.

Изучив это руководство, вы:

  • Включение устойчивости подключения
  • Включение перехвата команд
  • Тестирование новой конфигурации

Предварительные требования

Включение устойчивости подключения

При развертывании приложения в Windows Azure база данных будет развернута в windows Azure SQL Database, облачной службе базы данных. Временные ошибки подключения обычно возникают чаще при подключении к облачной службе базы данных, чем при непосредственном подключении веб-сервера и сервера базы данных в одном центре обработки данных. Даже если облачный веб-сервер и облачная служба базы данных размещены в одном центре обработки данных, между ними есть больше сетевых подключений, которые могут иметь проблемы, например подсистемы балансировки нагрузки.

Кроме того, облачная служба обычно используется другими пользователями, что означает, что они могут повлиять на ее скорость реагирования. Доступ к базе данных может регулироваться. Регулирование означает, что служба базы данных создает исключения, когда вы пытаетесь получить к ней доступ чаще, чем это разрешено в соглашении об уровне обслуживания (SLA).

Многие или большинство проблем с подключением при доступе к облачной службе являются временными, то есть устраняются сами за короткий период времени. Таким образом, при попытке операции с базой данных и получении типа ошибки, которая обычно является временной, вы можете повторить операцию после короткого ожидания, и операция может быть успешной. Вы можете значительно улучшить взаимодействие с пользователями, если вы обрабатываете временные ошибки, автоматически пытаясь повторить попытку, делая большинство из них невидимыми для клиента. Функция устойчивости подключений в Entity Framework 6 автоматизирует этот процесс повторных попыток неудачных SQL-запросов.

Функция устойчивости подключения должна быть настроена соответствующим образом для конкретной службы базы данных:

  • Он должен знать, какие исключения, скорее всего, будут временными. Вы хотите повторить ошибки, вызванные временной потерей сетевого подключения, а не ошибки, вызванные ошибками программы, например.
  • Он должен ждать соответствующее время между повторными попытками неудачной операции. Вы можете ждать дольше между повторными попытками пакетного процесса, чем для веб-страницы в Интернете, где пользователь ожидает ответа.
  • Он должен повторить соответствующее количество раз, прежде чем он сдается. Вы можете повторить несколько попыток в пакетном процессе, чем в онлайн-приложении.

Эти параметры можно настроить вручную для любой среды базы данных, поддерживаемой поставщиком Entity Framework, но значения по умолчанию, которые обычно хорошо работают для веб-приложения, использующего Базу данных Windows Azure SQL, уже настроены для вас. Это параметры, которые вы будете реализовывать для приложения Contoso University.

Все, что нужно сделать для обеспечения устойчивости подключения, — это создать в сборке класс, производный от класса DbConfiguration, и задать в этом классе стратегию выполнения База данных SQL, которая в EF является еще одним термином для политики повтора.

  1. В папке DAL добавьте файл класса SchoolConfiguration.cs.

  2. Замените код шаблона следующим кодом:

    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 Code-Based Configuration.

  3. В Файле StudentController.cs добавьте using оператор для System.Data.Entity.Infrastructure.

    using System.Data.Entity.Infrastructure;
    
  4. Измените все блоки catch , которые перехватывают DataException исключения, чтобы вместо них перехватывались RetryLimitExceededException исключения. Пример:

    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>

  1. Создайте папку в проекте и назовите ее Logging.

  2. В папке Ведение журнала создайте файл класса 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);
        }
    }
    

    Интерфейс предоставляет три уровня трассировки для указания относительной важности журналов, а один — для предоставления сведений о задержке для вызовов внешних служб, таких как запросы базы данных. Методы ведения журнала имеют перегрузки, позволяющие передавать исключение. Это делается для того, чтобы сведения об исключениях, включая трассировку стека и внутренние исключения, надежно регистрировались классом, реализующим интерфейс, вместо того, чтобы полагаться на это в каждом вызове метода ведения журнала в приложении.

    Методы TraceApi позволяют отслеживать задержку каждого вызова внешней службы, например База данных SQL.

  3. В папке Ведение журнала создайте файл класса с именем 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, например, для записи журналов в файлы или их записи в хранилище BLOB-объектов в Azure. Дополнительные сведения см. в разделе Устранение неполадок с веб-сайтами Azure в Visual Studio, а также ссылки на другие ресурсы. В этом руководстве вы будете просматривать журналы только в окне Вывода Visual Studio.

    В рабочем приложении может потребоваться рассмотреть возможность трассировки пакетов, отличных от System.Diagnostics, а интерфейс ILogger позволяет относительно легко переключиться на другой механизм трассировки, если вы решите сделать это.

Создание классов перехватчика

Далее вы создадите классы, которые Entity Framework будет вызывать каждый раз, когда она будет отправлять запрос в базу данных, один для имитации временных ошибок, а другой для ведения журнала. Эти классы перехватчика должны быть производными DbCommandInterceptor от класса . В них вы пишете переопределения методов, которые автоматически вызываются при выполнении запроса. В этих методах можно проверять или регистрировать запрос, отправляемый в базу данных, а также изменять запрос перед его отправкой в базу данных или возвращать что-либо в Entity Framework самостоятельно, даже не передавая запрос в базу данных.

  1. Чтобы создать класс перехватчика, который будет регистрировать каждый SQL-запрос, отправляемый в базу данных, создайте файл класса SchoolInterceptorLogging.cs в папке DAL и замените код шаблона следующим кодом:

    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);
            }
        }
    }
    

    Для успешных запросов или команд этот код записывает журнал сведений с информацией о задержке. Для исключений создается журнал ошибок.

  2. Чтобы создать класс перехватчика, который будет создавать фиктивные временные ошибки при вводе "Throw" в поле поиска , создайте файл класса SchoolInterceptorTransientErrors.cs в папке DAL и замените код шаблона следующим кодом:

    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" в качестве строки поиска, этот код создает фиктивное База данных SQL исключение для ошибки с номером 20, тип, который обычно является временным. Другие номера ошибок, которые в настоящее время распознаются как временные: 64, 233, 10053, 10054, 10060, 10928, 10929, 40197, 40501 и 40613, но они могут быть изменены в новых версиях База данных SQL.

    Код возвращает исключение в Entity Framework вместо выполнения запроса и обратной передачи результатов запроса. Временное исключение возвращается четыре раза, а затем код возвращается к обычной процедуре передачи запроса в базу данных.

    Так как регистрируются все данные, вы увидите, что Entity Framework пытается выполнить запрос четыре раза, прежде чем, наконец, будет выполнен успешно, и единственное отличие в приложении заключается в том, что отрисовка страницы с результатами запроса занимает больше времени.

    Количество повторных попыток Entity Framework настраивается; код указывает четыре раза, так как это значение по умолчанию для политики выполнения База данных SQL. При изменении политики выполнения вы также измените код, указывающий, сколько раз создаются временные ошибки. Можно также изменить код для создания дополнительных исключений, чтобы Entity Framework создавала RetryLimitExceededException исключение.

    Значение, введенное в поле Поиск, будет находиться в command.Parameters[0] и command.Parameters[1] (один используется для имени, а один — для фамилии). При обнаружении значения "%Throw%" в этих параметрах заменяется на "an", чтобы некоторые учащиеся были найдены и возвращены.

    Это просто удобный способ проверки устойчивости подключения на основе изменения некоторых входных данных в пользовательском интерфейсе приложения. Вы также можете написать код, который создает временные ошибки для всех запросов или обновлений, как описано далее в комментариях к методу DbInterception.Add .

  3. В Global.asax добавьте следующие using операторы:

    using ContosoUniversity.DAL;
    using System.Data.Entity.Infrastructure.Interception;
    
  4. Добавьте выделенные строки в 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 для одного перехватчика несколько раз, иначе вы получите дополнительные экземпляры перехватчика. Например, если вы добавите перехватчик ведения журнала дважды, вы увидите два журнала для каждого SQL-запроса.

    Перехватчики выполняются в порядке регистрации (в порядке вызова DbInterception.Add метода). Порядок может иметь значение в зависимости от того, что вы делаете в перехватчике. Например, перехватчик может изменить команду SQL, которую он получает в свойстве CommandText . Если команда SQL изменится, следующий перехватчик получит измененную команду SQL, а не исходную команду SQL.

    Вы написали код моделирования временных ошибок таким образом, чтобы вызывать временные ошибки, вводя другое значение в пользовательском интерфейсе. В качестве альтернативы можно написать код перехватчика, чтобы всегда создавать последовательность временных исключений без проверки конкретного значения параметра. Затем можно добавить перехватчик только в том случае, если требуется создать временные ошибки. Однако в этом случае не добавляйте перехватчик до завершения инициализации базы данных. Иными словами, выполните хотя бы одну операцию базы данных, например запрос к одному из наборов сущностей, прежде чем создавать временные ошибки. Entity Framework выполняет несколько запросов во время инициализации базы данных, и они не выполняются в транзакции, поэтому ошибки во время инициализации могут привести к тому, что контекст переходит в несогласованное состояние.

Тестирование новой конфигурации

  1. Нажмите клавишу F5 , чтобы запустить приложение в режиме отладки, а затем откройте вкладку Учащиеся .

  2. Просмотрите окно Вывода Visual Studio, чтобы увидеть выходные данные трассировки. Возможно, вам придется прокрутить страницу до некоторых ошибок JavaScript, чтобы перейти к журналам, записанным вашим средством ведения журнала.

    Обратите внимание, что вы можете увидеть фактические SQL-запросы, отправленные в базу данных. Вы увидите некоторые начальные запросы и команды, выполняемые Entity Framework для начала работы, проверяя версию базы данных и таблицу журнала миграции (вы узнаете о миграциях в следующем руководстве). Вы увидите запрос на разбиение по страницам, чтобы узнать, сколько учащихся, и, наконец, вы увидите запрос, который получает данные учащегося.

    Ведение журнала для обычного запроса

  3. На странице Учащиеся введите "Throw" в качестве строки поиска и нажмите кнопку Поиск.

    Создание строки поиска

    Вы заметите, что браузер зависает в течение нескольких секунд, пока Entity Framework повторяет запрос несколько раз. Первая повторная попытка происходит очень быстро, а затем ожидание перед каждой дополнительной попыткой увеличивается. Этот процесс ожидания дольше перед каждой повторной попыткой называется экспоненциальной задержкой.

    Когда откроется страница с учащимися, у которых в именах есть "an", посмотрите на окно вывода, и вы увидите, что один и тот же запрос был выполнен пять раз, причем первые четыре раза возвращали временные исключения. Для каждой временной ошибки вы увидите журнал, который вы записываете при создании временной ошибки в SchoolInterceptorTransientErrors классе ("Возврат временной ошибки для команды..."), и вы увидите журнал, записанный при 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:
    

    Вы не регистрите значение параметров, но это можно сделать. Если вы хотите просмотреть значения параметров, можно написать код ведения журнала, чтобы получить значения параметров из Parameters свойства объекта, полученного DbCommand в методах перехватчика.

    Обратите внимание, что вы не сможете повторить этот тест, пока не остановите приложение и не перезапустите его. Если вы хотите проверить устойчивость подключения несколько раз за один запуск приложения, можно написать код для сброса счетчика ошибок в SchoolInterceptorTransientErrors.

  4. Чтобы увидеть разницу в стратегии выполнения (политике повторных попыток), закомментируйте SetExecutionStrategy строку в SchoolConfiguration.cs, снова запустите страницу Students в режиме отладки и выполните поиск по запросу "Throw" еще раз.

    На этот раз отладчик останавливается на первом созданном исключении сразу при первой попытке выполнить запрос.

    Фиктивный исключение

  5. Раскомментируйте строку SetExecutionStrategy в SchoolConfiguration.cs.

Получите код

Скачивание завершенного проекта

Дополнительные ресурсы

Ссылки на другие ресурсы Entity Framework можно найти в ASP.NET Доступ к данным — рекомендуемые ресурсы.

Дальнейшие действия

Изучив это руководство, вы:

  • Включенная устойчивость подключения
  • Включен перехват команд
  • Тестирование новой конфигурации

Перейдите к следующей статье, чтобы узнать о миграции Code First и развертывании Azure.