Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом руководстве вы измените пример приложения Wingtip Toys для поддержки устойчивости подключения и перехвата команд. Включив устойчивость подключения, пример приложения Wingtip Toys автоматически повторяет вызовы данных при возникновении временных ошибок в облачной среде. Кроме того, путем реализации перехвата команд пример приложения Wingtip Toys перехватит все запросы SQL, отправленные в базу данных, чтобы регистрировать или изменять их.
Примечание.
Это учебное руководство по веб-формам было основано на следующем руководстве по MVC от Тома Дайкстры.
Устойчивость подключения и перехват команд с помощью Entity Framework в приложении MVC ASP.NET
Что вы узнаете:
- Как обеспечить устойчивость подключения.
- Как реализовать перехват команд.
Требования
Прежде чем начать, убедитесь, что на компьютере установлено следующее программное обеспечение:
Microsoft Visual Studio 2013 или Microsoft Visual Studio Express 2013 для Интернета. Платформа .NET Framework устанавливается автоматически.
Пример проекта Wingtip Toys, чтобы реализовать функциональные возможности, упомянутые в этом руководстве, в проекте Wingtip Toys. Следующая ссылка содержит сведения о скачивании:
Прежде чем завершите этот учебник, рассмотрите возможность изучения связанного учебного курса ASP.NET 4.5 Web Forms и Visual Studio 2013. Серия учебников поможет вам ознакомиться с проектом и кодом WingtipToys .
Устойчивость подключения
При развертывании приложения в Windows Azure можно рассмотреть возможность развертывания базы данных в WindowsБаза данных SQL Azure облачной службы баз данных. Временные ошибки подключения обычно чаще возникают при подключении к облачной службе базы данных, чем при подключении веб-сервера и сервера базы данных напрямую к одному центру обработки данных. Даже если облачный веб-сервер и облачная служба баз данных размещаются в одном центре обработки данных, между ними существует множество сетевых соединений, которые могут привести к проблемам, например, балансировщики нагрузки.
Кроме того, облачная служба обычно предоставляется другим пользователям, что означает, что ее скорость реагирования может повлиять на них. Доступ к базе данных может быть подвержен регулированию. Ограничение означает, что служба базы данных создает исключения при попытке доступа к ней чаще, чем разрешено в соглашении об уровне обслуживания (SLA).
Многие или большинство проблем с подключением, возникающие при доступе к облачной службе, являются временными, то есть они устраняют себя в течение короткого периода времени. Поэтому, если при попытке выполнить операцию с базой данных вы получаете переходящую ошибку, можно повторить операцию после короткого ожидания, и она может завершиться успешно. Вы можете обеспечить гораздо лучший интерфейс для пользователей, если вы обрабатываете временные ошибки, автоматически пытаясь повторить попытку, делая большинство из них невидимыми для клиента. Функция устойчивости соединения в Entity Framework 6 автоматизирует процесс повторного выполнения неудавшихся SQL-запросов.
Для конкретной службы базы данных необходимо настроить функцию устойчивости подключения:
- Он должен знать, какие исключения, скорее всего, будут временными. Вы хотите повторить ошибки, вызванные временной потерей в сетевом подключении, а не ошибками, вызванными ошибками программы, например.
- Он должен ждать соответствующее время между повторными попытками неудачной операции. Вы можете ждать больше времени между повторными попытками пакетного процесса, чем для веб-страницы в Интернете, где пользователь ожидает ответа.
- Она должна повторить соответствующее количество раз, прежде чем она откажется. В пакетной обработке вам может потребоваться больше повторных попыток, чем в онлайн-приложении.
Эти параметры можно настроить вручную для любой базы данных в среде, поддерживаемой провайдером Entity Framework.
Все, что необходимо сделать, чтобы обеспечить устойчивость подключений, — создать класс в сборке, которая является производным от DbConfiguration класса, и в этом классе задается стратегия выполнения База данных SQL, которая в Entity Framework является еще одним термином для политики повторных попыток.
Реализация устойчивости подключения
Скачайте и откройте в Visual Studio пример приложения веб-форм WingtipToys.
В папке логикиприложения WingtipToys добавьте файл класса с именем WingtipToysConfiguration.cs.
Замените существующий код следующим кодом:
using System.Data.Entity; using System.Data.Entity.SqlServer; namespace WingtipToys.Logic { public class WingtipToysConfiguration : DbConfiguration { public WingtipToysConfiguration() { SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy()); } } }
Entity Framework автоматически запускает код, который он находит в классе, наследуемом от DbConfiguration. Вы можете использовать класс DbConfiguration для выполнения задач конфигурации в коде, которые в противном случае вы бы выполняли в файле Web.config. Дополнительные сведения см. в разделе "Конфигурация на основе кода EntityFramework".
В папке логики откройте файл AddProducts.cs.
usingДобавьте оператор дляSystem.Data.Entity.Infrastructure, как показано выделенным желтым цветом:using System; using System.Collections.Generic; using System.Linq; using System.Web; using WingtipToys.Models; using System.Data.Entity.Infrastructure;Добавьте блок
catchв методAddProduct, чтобыRetryLimitExceededExceptionрегистрировался, как выделенный желтым цветом:public bool AddProduct(string ProductName, string ProductDesc, string ProductPrice, string ProductCategory, string ProductImagePath) { var myProduct = new Product(); myProduct.ProductName = ProductName; myProduct.Description = ProductDesc; myProduct.UnitPrice = Convert.ToDouble(ProductPrice); myProduct.ImagePath = ProductImagePath; myProduct.CategoryID = Convert.ToInt32(ProductCategory); using (ProductContext _db = new ProductContext()) { // Add product to DB. _db.Products.Add(myProduct); try { _db.SaveChanges(); } catch (RetryLimitExceededException ex) { // Log the RetryLimitExceededException. WingtipToys.Logic.ExceptionUtility.LogException(ex, "Error: RetryLimitExceededException -> RemoveProductButton_Click in AdminPage.aspx.cs"); } } // Success. return true; }
RetryLimitExceededException Добавив исключение, вы можете предоставить более эффективное ведение журнала или отобразить пользователю сообщение об ошибке, где он может повторить попытку процесса. Перехватив RetryLimitExceededException исключение, единственные ошибки, которые, вероятно, будут временными, уже неоднократно пытались устранить, но безуспешно. Фактическое возвращаемое исключение будет упаковано в исключение RetryLimitExceededException. Кроме того, вы добавили общий блок catch. Дополнительные сведения об исключении RetryLimitExceededException см. в разделе Entity Framework Connection Resiliency /Retry Logic.
Перехват команд
Теперь, когда вы включили политику повторных попыток, как проверить, работает ли она должным образом? Иногда непросто вызвать возникновение временных ошибок, особенно в локальной среде, а интеграция симулированных временных ошибок в автоматизированный модульный тест может быть особенно сложной. Чтобы проверить функцию устойчивости подключений, вам потребуется способ перехвата запросов, которые Entity Framework отправляет в SQL Server и заменить ответ SQL Server типом исключения, который обычно является временным.
Вы также можете использовать перехват запросов, чтобы реализовать рекомендации по облачным приложениям: регистрировать задержку и успешность или сбой всех вызовов внешних служб, таких как службы баз данных.
В этом разделе руководства вы будете использовать функцию перехвата в Entity Framework как для ведения журнала, так и для имитации временных ошибок.
Создание интерфейса ведения журнала и класса
Лучше всего выполнять ведение журнала с помощью interface, а не жестко заданных вызовов System.Diagnostics.Trace или класса ведения журнала. Это упрощает изменение механизма ведения журнала позже, если это нужно сделать. Поэтому в этом разделе вы создадите интерфейс ведения журнала и класс для его реализации.
На основе приведенной выше процедуры вы скачали и открыли пример приложения WingtipToys в Visual Studio.
Создайте папку в проекте WingtipToys и присвойте ей имя ведения журнала.
В папке ведения журнала создайте файл класса с именем ILogger.cs и замените код по умолчанию следующим кодом:
using System; namespace WingtipToys.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.В папке ведения журнала создайте файл класса с именем Logger.cs и замените код по умолчанию следующим кодом:
using System; using System.Diagnostics; using System.Text; namespace WingtipToys.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) { 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. Ознакомьтесь с некоторыми параметрами и ссылками на другие ресурсы, чтобы получить дополнительные сведения об устранении неполадок с веб-сайтами Windows Azure в Visual Studio. В этом руководстве вы увидите только журналы в окне вывода Visual Studio.
В производственном приложении вы, возможно, захотите рассмотреть возможность использования платформ трассировки, отличных от System.Diagnostics, и интерфейс ILogger упрощает переход на другой механизм трассировки, если вы решите это сделать.
Создайте классы перехватчика
Затем вы создадите классы, которые Entity Framework будет вызывать каждый раз при отправке запроса в базу данных, один для имитации временных ошибок и один для ведения журнала. Эти классы перехватчика должны быть производными от DbCommandInterceptor класса. В них вы пишете переопределения методов, которые автоматически вызываются, когда запрос собирается выполняться. В этих методах можно проверить или записать запрос, отправляемый в базу данных, и вы можете изменить запрос перед отправкой в базу данных или вернуть что-то в Entity Framework самостоятельно, даже не передавая запрос в базу данных.
Чтобы создать класс перехватчика, который будет регистрировать каждый SQL-запрос перед отправкой в базу данных, создайте файл класса с именем InterceptorLogging.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 WingtipToys.Logging; namespace WingtipToys.Logic { public class InterceptorLogging : 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", "Interceptor.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", "Interceptor.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", "Interceptor.ReaderExecuted", _stopwatch.Elapsed, "Command: {0}: ", command.CommandText); } base.ReaderExecuted(command, interceptionContext); } } }Для успешных запросов или команд этот код записывает журнал сведений с сведениями о задержке. Для исключений создается журнал ошибок.
Чтобы создать класс перехватчика, который создаст фиктивные временные ошибки при вводе "Throw" в текстовом поле "Имя" на странице с именем AdminPage.aspx, создайте файл класса с именем InterceptorTransientErrors.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 WingtipToys.Logging; namespace WingtipToys.Logic { public class InterceptorTransientErrors : 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 = "TransientErrorExample"; command.Parameters[1].Value = "TransientErrorExample"; } 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, как это делает перехватчик ведения журнала.Позже вы войдете в систему в качестве администратора и выберите ссылку "Администратор " на верхней панели навигации. Затем на странице AdminPage.aspx вы добавите продукт с именем Throw. Код создает макетное SQL-исключение базы данных для номера ошибки 20, тип которого обычно является временным. Другие номера ошибок, которые в настоящее время распознаются как временные: 64, 233, 10053, 10054, 10060, 10928, 10929, 40197, 40501 и 40613, но они могут изменяться в новых версиях База данных SQL. Продукт будет переименован в TransientErrorExample, изменения можно отследить в кодовом файле InterceptorTransientErrors.cs.
Код возвращает исключение в Entity Framework вместо выполнения запроса и передачи результатов обратно. Временное исключение возвращается четыре раза, а затем код возвращается к обычной процедуре передачи запроса в базу данных.
Так как все регистрируется в журнале, вы сможете увидеть, что Entity Framework пытается выполнить запрос четыре раза, прежде чем завершиться успешно, и единственное различие в приложении заключается в том, что для отрисовки страницы с результатами запроса требуется больше времени.
Количество повторных попыток конфигурируемо в Entity Framework, в коде указано четыре раза, так как это значение по умолчанию для политики выполнения SQL-базы данных. При изменении политики выполнения вы также измените код здесь, указывающий, сколько раз создаются временные ошибки. Вы также можете изменить код, чтобы создать больше исключений, и тогда Entity Framework будет выбрасывать
RetryLimitExceededExceptionисключение.В Global.asax добавьте инструкции using, перечисленные ниже:
using System.Data.Entity.Infrastructure.Interception;Затем добавьте выделенные строки в
Application_Startметод:void Application_Start(object sender, EventArgs e) { // Code that runs on application startup RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); // Initialize the product database. Database.SetInitializer(new ProductDatabaseInitializer()); // Create administrator role and user. RoleActions roleActions = new RoleActions(); roleActions.createAdmin(); // Add Routes. RegisterRoutes(RouteTable.Routes); // Logging. DbInterception.Add(new InterceptorTransientErrors()); DbInterception.Add(new InterceptorLogging()); }
Эти строки кода вызывают выполнение кода перехватчика, когда Entity Framework отправляет запросы в базу данных. Обратите внимание, что поскольку вы создали отдельные классы перехватчика для моделирования временных ошибок и ведения журнала, вы можете независимо включить и отключить их.
Вы можете добавлять перехватчики с помощью DbInterception.Add метода в любом месте кода. Он не должен находиться в методе Application_Start . Другим вариантом, если перехватчики не были добавлены в метод Application_Start, стоит обновить либо добавить класс с именем WingtipToysConfiguration.cs и поместить приведенный выше код в конец конструктора класса WingtipToysConfiguration.
Куда бы вы ни поместили этот код, будьте осторожны, чтобы не выполнять DbInterception.Add на одном перехватчике больше одного раза, иначе вы получите дополнительные экземпляры перехватчика. Например, если добавить перехватчик ведения журнала дважды, вы увидите два журнала для каждого запроса SQL.
Перехватчики выполняются согласно порядку их регистрации (в том порядке, в каком вызывается метод DbInterception.Add). Порядок может иметь значение в зависимости от того, что вы делаете в перехватчике. Например, перехватчик может изменить команду SQL, которую он получает в свойстве CommandText . При изменении команды SQL следующий перехватчик получит измененную команду SQL, а не исходную команду SQL.
Вы написали временный код моделирования ошибок таким образом, что позволяет вызывать временные ошибки, вводя другое значение в пользовательском интерфейсе. В качестве альтернативы можно написать код перехватчика, чтобы всегда создавать последовательность временных исключений без проверки определенного значения параметра. Затем можно добавить перехватчик только в том случае, если требуется создать временные ошибки. Однако при этом не добавляйте перехватчик до завершения инициализации базы данных. Другими словами, совершите как минимум одну операцию с базой данных, например, запрос к одному из ваших наборов сущностей, прежде чем столкнуться с временными ошибками. Entity Framework выполняет несколько запросов во время инициализации базы данных, и они не выполняются в транзакции, поэтому ошибки во время инициализации могут привести к возникновению несогласованного состояния контекста.
Проверка устойчивости логирования и соединения
В Visual Studio нажмите клавишу F5 , чтобы запустить приложение в режиме отладки, а затем войдите в систему в качестве администратора с помощью pa$$word в качестве пароля.
Выберите "Администратор" в верхней части панели навигации.
Введите новый продукт с именем Throw с соответствующим описанием, ценой и файлом изображения.
Нажмите кнопку "Добавить продукт ".
Вы заметите, что браузер, кажется, зависает в течение нескольких секунд, пока Entity Framework повторяет запрос несколько раз. Первая повторная попытка происходит очень быстро, затем время ожидания увеличивается перед каждой дополнительной попыткой. Этот процесс длительного ожидания перед каждой повторной попыткой называется экспоненциальным откатом.Подождите, пока страница больше не пытается загружаться.
Остановите проект и просмотрите окно вывода Visual Studio, чтобы увидеть результаты трассировки. Вы можете найти окно Вывод, выбрав Отладка ->Окна ->Вывод. Возможно, вам придется прокручивать несколько других журналов, написанных вашим логгером.
Обратите внимание, что можно увидеть фактические запросы SQL, отправленные в базу данных. Вы видите некоторые начальные запросы и команды, которые Entity Framework выполняет для начала работы, проверяя версию базы данных и таблицу журнала миграции.
Обратите внимание, что этот тест нельзя повторить, если вы не остановите приложение и не перезапустите его. Если вы хотите проверить устойчивость подключений несколько раз в одном запуске приложения, можно написать код для сброса счетчика ошибок.InterceptorTransientErrorsЧтобы увидеть разницу в стратегии выполнения (политики повторных попыток), закомментируйте
SetExecutionStrategyстроку в WingtipToysConfiguration.cs файле в папке логики , снова запустите страницу администрирования в режиме отладки и снова добавьте продукт с именем "Throw".На этот раз отладчик останавливается на первом созданном исключении сразу при попытке выполнить запрос в первый раз.
Раскомментируйте строку
SetExecutionStrategyв файле WingtipToysConfiguration.cs.
Итоги
В этом руководстве вы узнали, как изменить пример приложения веб-формы для поддержки устойчивости подключений и перехвата команд.
Дальнейшие шаги
После проверки устойчивости подключения и перехвата команд в ASP.NET Web Forms, ознакомьтесь с темой асинхронные методы в ASP.NET Web Forms в ASP.NET 4.5. В этом разделе описаны основы создания асинхронного приложения ASP.NET веб-формы с помощью Visual Studio.