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

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

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

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

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

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

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

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

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

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

  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 повторяет запрос несколько раз. Первая повторная попытка происходит очень быстро, а затем ожидание перед увеличением перед каждой дополнительной попыткой. Этот процесс ожидания дольше, прежде чем каждая повторная попытка называется экспоненциальной задержкой.

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

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

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

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

Получение кода

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

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

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

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

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

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

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