Samouczek: używanie odporności połączenia i przechwytywania poleceń za pomocą programu Entity Framework w aplikacji MVC ASP.NET

Do tej pory aplikacja była uruchomiona lokalnie w IIS Express na komputerze dewelopera. Aby udostępnić prawdziwą aplikację innym osobom do użycia przez Internet, musisz wdrożyć ją u dostawcy hostingu internetowego i musisz wdrożyć bazę danych na serwerze bazy danych.

Z tego samouczka dowiesz się, jak używać odporności połączenia i przechwytywania poleceń. Są to dwie ważne funkcje programu Entity Framework 6, które są szczególnie przydatne podczas wdrażania w środowisku chmury: odporność połączenia (automatyczne ponawianie prób w przypadku błędów przejściowych) i przechwytywanie poleceń (przechwycenie wszystkich zapytań SQL wysyłanych do bazy danych w celu ich zarejestrowania lub zmiany).

Ten samouczek dotyczący odporności połączenia i przechwytywania poleceń jest opcjonalny. Jeśli pominiesz ten samouczek, konieczne będzie wprowadzenie kilku drobnych korekt w kolejnych samouczkach.

W tym samouczku zostały wykonane następujące czynności:

  • Włączanie odporności połączenia
  • Włącz przechwytywanie poleceń
  • Testowanie nowej konfiguracji

Wymagania wstępne

Włączanie odporności połączenia

Podczas wdrażania aplikacji na platformie Windows Azure wdrożysz bazę danych w systemie Windows Azure SQL Database — usłudze bazy danych w chmurze. Błędy przejściowe połączenia są zwykle częstsze w przypadku nawiązywania połączenia z usługą bazy danych w chmurze niż wtedy, gdy serwer internetowy i serwer bazy danych są połączone bezpośrednio w tym samym centrum danych. Nawet jeśli serwer internetowy w chmurze i usługa bazy danych w chmurze są hostowane w tym samym centrum danych, istnieje więcej połączeń sieciowych między nimi, które mogą mieć problemy, takie jak moduły równoważenia obciążenia.

Ponadto usługa w chmurze jest zwykle udostępniana przez innych użytkowników, co oznacza, że jej czas odpowiedzi może mieć na nie wpływ. Dostęp do bazy danych może podlegać ograniczaniu przepustowości. Ograniczanie przepustowości oznacza, że usługa bazy danych zgłasza wyjątki, gdy próbujesz uzyskać do niej dostęp częściej niż jest to dozwolone w umowie dotyczącej poziomu usług (SLA).

Wiele lub większość problemów z połączeniem podczas uzyskiwania dostępu do usługi w chmurze jest przejściowych, czyli rozwiązuje się w krótkim czasie. Dlatego podczas próby wykonania operacji bazy danych i otrzymania typu błędu, który jest zazwyczaj przejściowy, możesz spróbować wykonać operację ponownie po krótkim oczekiwaniu, a operacja może zakończyć się pomyślnie. Możesz zapewnić użytkownikom znacznie lepsze środowisko obsługi, jeśli będziesz obsługiwać błędy przejściowe, automatycznie próbując ponownie, dzięki czemu większość z nich jest niewidoczna dla klienta. Funkcja odporności połączenia w programie Entity Framework 6 automatyzuje ten proces ponawiania nieudanych zapytań SQL.

Funkcja odporności połączenia musi być odpowiednio skonfigurowana dla określonej usługi bazy danych:

  • Musi wiedzieć, które wyjątki mogą być przejściowe. Chcesz ponowić próbę błędów spowodowanych tymczasową utratą łączności sieciowej, a nie błędami spowodowanymi na przykład usterkami programu.
  • Musi odczekać odpowiedni czas między ponowną próbą operacji, która zakończyła się niepowodzeniem. Możesz czekać dłużej między ponawianiami dla procesu wsadowego, niż w przypadku strony internetowej online, na której użytkownik czeka na odpowiedź.
  • Musi ponowić próbę odpowiedniej liczby razy, zanim się podsunie. Możesz ponowić próbę więcej razy w procesie wsadowym, który byłby używany w aplikacji online.

Te ustawienia można skonfigurować ręcznie dla dowolnego środowiska bazy danych obsługiwanego przez dostawcę programu Entity Framework, ale wartości domyślne, które zazwyczaj działają dobrze w przypadku aplikacji online korzystającej z usługi Windows Azure SQL Database, zostały już skonfigurowane. Są to ustawienia, które zostaną zaimplementowane dla aplikacji Contoso University.

Aby włączyć odporność połączenia, wystarczy utworzyć klasę w zestawie, która pochodzi z klasy DbConfiguration, a w tej klasie ustawić strategię wykonywania SQL Database, która w programie EF jest kolejnym terminem zasad ponawiania.

  1. W folderze DAL dodaj plik klasy o nazwie SchoolConfiguration.cs.

  2. Zastąp kod szablonu następującym kodem:

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

    Program Entity Framework automatycznie uruchamia kod, który znajduje w klasie pochodzącej z klasy DbConfiguration. Za pomocą DbConfiguration klasy można wykonywać zadania konfiguracyjne w kodzie, który w przeciwnym razie należy wykonać w pliku Web.config . Aby uzyskać więcej informacji, zobacz EntityFramework Code-Based Configuration.

  3. W pliku StudentController.cs dodaj instrukcję using dla elementu System.Data.Entity.Infrastructure.

    using System.Data.Entity.Infrastructure;
    
  4. Zmień wszystkie catch bloki, które przechwytują DataException wyjątki, aby przechwytywać RetryLimitExceededException wyjątki. Na przykład:

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

    Używano DataException polecenia , aby spróbować zidentyfikować błędy, które mogą być przejściowe, aby dać przyjazny komunikat "spróbuj ponownie". Ale teraz, gdy włączono zasady ponawiania, jedyne błędy, które prawdopodobnie będą przejściowe, zostały już wypróbowane i nie powiodły się kilka razy, a rzeczywisty zwrócony wyjątek zostanie opakowany w RetryLimitExceededException wyjątek.

Aby uzyskać więcej informacji, zobacz Entity Framework Connection Resiliency /Retry Logic (Odporność połączenia platformy Entity Framework/Logika ponawiania prób).

Włącz przechwytywanie poleceń

Po włączeniu zasad ponawiania prób możesz sprawdzić, czy działa ona zgodnie z oczekiwaniami? Nie jest tak łatwo wymusić wystąpienie błędu przejściowego, zwłaszcza w przypadku uruchamiania lokalnego i byłoby szczególnie trudne do zintegrowania rzeczywistych błędów przejściowych z automatycznym testem jednostkowym. Aby przetestować funkcję odporności połączenia, potrzebny jest sposób przechwytywania zapytań wysyłanych przez program Entity Framework do SQL Server i zastępowania odpowiedzi SQL Server typem wyjątku, który jest zwykle przejściowy.

Można również użyć przechwytywania zapytań w celu zaimplementowania najlepszych rozwiązań dla aplikacji w chmurze: rejestrowania opóźnienia i powodzenia lub niepowodzenia wszystkich wywołań usług zewnętrznych , takich jak usługi bazy danych. Program EF6 udostępnia dedykowany interfejs API rejestrowania , który może ułatwić rejestrowanie, ale w tej sekcji samouczka dowiesz się, jak bezpośrednio korzystać z funkcji przechwytywania programu Entity Framework, zarówno do rejestrowania, jak i symulowania błędów przejściowych.

Tworzenie interfejsu rejestrowania i klasy

Najlepszym rozwiązaniem w przypadku rejestrowania jest użycie interfejsu, a nie wywołań kodowania na stałe do klasy System.Diagnostics.Trace lub klasy rejestrowania. Ułatwia to zmianę mechanizmu rejestrowania później, jeśli kiedykolwiek będzie to konieczne. W tej sekcji utworzysz interfejs rejestrowania i klasę w celu zaimplementowania go./p>

  1. Utwórz folder w projekcie i nadaj mu nazwę Rejestrowanie.

  2. W folderze Logging utwórz plik klasy o nazwie ILogger.cs i zastąp kod szablonu następującym kodem:

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

    Interfejs udostępnia trzy poziomy śledzenia, aby wskazać względną ważność dzienników i jeden zaprojektowany w celu zapewnienia informacji o opóźnieniach dla wywołań usług zewnętrznych, takich jak zapytania bazy danych. Metody rejestrowania mają przeciążenia, które umożliwiają przekazanie wyjątku. Jest to tak, że informacje o wyjątkach, w tym ślad stosu i wyjątki wewnętrzne, są niezawodnie rejestrowane przez klasę, która implementuje interfejs, zamiast polegać na tym, że odbywa się to w każdym wywołaniu metody rejestrowania w całej aplikacji.

    Metody TraceApi umożliwiają śledzenie opóźnienia każdego wywołania do usługi zewnętrznej, takiej jak SQL Database.

  3. W folderze Logging utwórz plik klasy o nazwie Logger.cs i zastąp kod szablonu następującym kodem:

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

    Implementacja używa elementu System.Diagnostics do śledzenia. Jest to wbudowana funkcja platformy .NET, która ułatwia generowanie i używanie informacji śledzenia. Istnieje wiele "odbiorników", których można używać z funkcją śledzenia System.Diagnostics, aby zapisywać dzienniki w plikach, na przykład lub zapisywać je w magazynie obiektów blob na platformie Azure. Aby uzyskać więcej informacji, zobacz niektóre opcje i linki do innych zasobów w temacie Rozwiązywanie problemów z witrynami internetowymi platformy Azure w programie Visual Studio. W tym samouczku zapoznasz się tylko z dziennikami w oknie Dane wyjściowe programu Visual Studio.

    W aplikacji produkcyjnej warto rozważyć śledzenie pakietów innych niż System.Diagnostics, a interfejs ILogger ułatwia przełączanie się do innego mechanizmu śledzenia, jeśli zdecydujesz się to zrobić.

Tworzenie klas przechwytywania

Następnie utworzysz klasy, do których program Entity Framework będzie wywoływany za każdym razem, gdy będzie wysyłać zapytanie do bazy danych, jeden do symulowania błędów przejściowych i jeden do rejestrowania. Te klasy przechwytywania muszą pochodzić z DbCommandInterceptor klasy . W nich piszesz przesłonięcia metod, które są automatycznie wywoływane, gdy zapytanie ma zostać wykonane. W tych metodach można zbadać lub zarejestrować zapytanie wysyłane do bazy danych, a zapytanie można zmienić przed wysłaniem do bazy danych lub wrócić coś do programu Entity Framework samodzielnie, nawet nie przekazując zapytania do bazy danych.

  1. Aby utworzyć klasę interceptor, która będzie rejestrować każde zapytanie SQL wysyłane do bazy danych, utwórz plik klasy o nazwie SchoolInterceptorLogging.cs w folderze DAL i zastąp kod szablonu następującym kodem:

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

    W przypadku pomyślnych zapytań lub poleceń ten kod zapisuje dziennik informacji z informacjami o opóźnieniu. W przypadku wyjątków tworzy dziennik błędów.

  2. Aby utworzyć klasę interceptor, która wygeneruje fikcyjne błędy przejściowe po wprowadzeniu ciągu "Throw" w polu Wyszukiwania , utwórz plik klasy o nazwie SchoolInterceptorTransientErrors.cs w folderze DAL i zastąp kod szablonu następującym kodem:

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

    Ten kod zastępuje tylko metodę ReaderExecuting , która jest wywoływana w przypadku zapytań, które mogą zwracać wiele wierszy danych. Jeśli chcesz sprawdzić odporność połączenia dla innych typów zapytań, możesz również zastąpić NonQueryExecuting metody i ScalarExecuting , tak jak przechwytujący rejestrowanie.

    Po uruchomieniu strony Student i wprowadzeniu ciągu wyszukiwania "Throw" ten kod tworzy fikcyjny wyjątek SQL Database dla błędu o numerze 20, typ znany jako zazwyczaj przejściowy. Inne numery błędów, które są obecnie rozpoznawane jako przejściowe, to 64, 233, 10053, 10054, 10060, 10928, 10929, 40197, 40501 i 40613, ale mogą ulec zmianie w nowych wersjach SQL Database.

    Kod zwraca wyjątek do programu Entity Framework zamiast uruchamiać zapytanie i przekazywać wyniki zapytania zwrotnego. Wyjątek przejściowy jest zwracany cztery razy, a następnie kod wraca do normalnej procedury przekazywania zapytania do bazy danych.

    Ponieważ wszystko jest rejestrowane, będzie można zobaczyć, że program Entity Framework próbuje wykonać zapytanie cztery razy przed powodzeniem, a jedyną różnicą w aplikacji jest to, że renderowanie strony z wynikami zapytania trwa dłużej.

    Liczba ponownych prób w strukturze Entity Framework jest konfigurowalna; kod określa cztery razy, ponieważ jest to wartość domyślna zasad wykonywania SQL Database. W przypadku zmiany zasad wykonywania należy również zmienić kod określający, ile razy są generowane błędy przejściowe. Możesz również zmienić kod w celu wygenerowania większej liczby wyjątków, aby program Entity Framework zgłosił RetryLimitExceededException wyjątek.

    Wartość wprowadzona w polu Wyszukiwania będzie znajdować się w command.Parameters[0] polu i command.Parameters[1] (jedna jest używana dla imienia i drugiego dla nazwiska). Gdy zostanie znaleziona wartość "%Throw%", wyrażenie "Throw" zostanie zastąpione tymi parametrami przez "an", tak aby niektórzy uczniowie znaleźli i zwrócili.

    Jest to po prostu wygodny sposób testowania odporności połączenia na podstawie zmiany niektórych danych wejściowych w interfejsie użytkownika aplikacji. Możesz również napisać kod, który generuje błędy przejściowe dla wszystkich zapytań lub aktualizacji, jak wyjaśniono w dalszej części komentarzy dotyczących metody DbInterception.Add .

  3. W pliku Global.asax dodaj następujące using instrukcje:

    using ContosoUniversity.DAL;
    using System.Data.Entity.Infrastructure.Interception;
    
  4. Dodaj wyróżnione wiersze do Application_Start metody :

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

    Te wiersze kodu są przyczyną uruchomienia kodu przechwytywania, gdy program Entity Framework wysyła zapytania do bazy danych. Należy zauważyć, że ponieważ utworzono oddzielne klasy przechwytywania na potrzeby symulacji błędów przejściowych i rejestrowania, można je niezależnie włączać i wyłączać.

    Przechwytywanie można dodawać przy użyciu DbInterception.Add metody w dowolnym miejscu w kodzie. Nie musi ona znajdować się w metodzie Application_Start . Inną opcją jest umieszczenie tego kodu w klasie DbConfiguration utworzonej wcześniej w celu skonfigurowania zasad wykonywania.

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

    Wszędzie tam, gdzie umieścisz ten kod, należy zachować ostrożność, aby nie wykonywać DbInterception.Add tego samego przechwytywania więcej niż raz lub uzyskasz dodatkowe wystąpienia przechwytywania. Jeśli na przykład dwa razy dodasz przechwytywanie rejestrowania, zobaczysz dwa dzienniki dla każdego zapytania SQL.

    Przechwytniki są wykonywane w kolejności rejestracji (kolejności, w której DbInterception.Add wywoływana jest metoda). Kolejność może mieć znaczenie w zależności od tego, co robisz w przechwytnika. Na przykład przechwytujący element może zmienić polecenie SQL, które pobiera we CommandText właściwości . Jeśli zmieni polecenie SQL, następny interceptor otrzyma zmienione polecenie SQL, a nie oryginalne polecenie SQL.

    Kod symulacji błędu przejściowego został napisany w sposób, który umożliwia generowanie błędów przejściowych przez wprowadzenie innej wartości w interfejsie użytkownika. Alternatywnie można napisać kod przechwytywania, aby zawsze generować sekwencję wyjątków przejściowych bez sprawdzania określonej wartości parametru. Następnie można dodać przechwytywanie tylko wtedy, gdy chcesz wygenerować błędy przejściowe. Jeśli jednak to zrobisz, nie dodawaj przechwytywania dopiero po zakończeniu inicjowania bazy danych. Innymi słowy, przed rozpoczęciem generowania błędów przejściowych wykonaj co najmniej jedną operację bazy danych, taką jak zapytanie w jednym z zestawów jednostek. Program Entity Framework wykonuje kilka zapytań podczas inicjowania bazy danych i nie są one wykonywane w transakcji, dlatego błędy podczas inicjowania mogą spowodować, że kontekst przechodzi w niespójny stan.

Testowanie nowej konfiguracji

  1. Naciśnij klawisz F5 , aby uruchomić aplikację w trybie debugowania, a następnie kliknij kartę Uczniowie .

  2. Przejrzyj okno Dane wyjściowe programu Visual Studio, aby wyświetlić dane wyjściowe śledzenia. Może być konieczne przewinięcie w górę niektórych błędów języka JavaScript, aby przejść do dzienników zapisanych przez rejestrator.

    Zwróć uwagę, że można zobaczyć rzeczywiste zapytania SQL wysyłane do bazy danych. Zobaczysz kilka początkowych zapytań i poleceń, które program Entity Framework wykonuje, aby rozpocząć pracę, sprawdzając tabelę wersji bazy danych i historii migracji (dowiesz się więcej na temat migracji w następnym samouczku). Zostanie wyświetlone zapytanie dotyczące stronicowania, aby dowiedzieć się, ilu jest uczniów, i na koniec zobaczysz zapytanie, które pobiera dane ucznia.

    Rejestrowanie dla normalnego zapytania

  3. Na stronie Uczniowie wprowadź ciąg wyszukiwania "Throw", a następnie kliknij przycisk Wyszukaj.

    Rzutuj ciąg wyszukiwania

    Zauważysz, że przeglądarka wydaje się zawieszać się przez kilka sekund, podczas gdy program Entity Framework ponawia próbę zapytania kilka razy. Pierwsze ponowienie próby odbywa się bardzo szybko, a następnie przed zwiększeniem oczekiwania przed każdym dodatkowym ponowieniu próby. Ten proces oczekiwania dłużej przed każdym ponowieniem jest nazywany wycofywaniem wykładniczym.

    Gdy zostanie wyświetlona strona przedstawiająca uczniów, którzy mają "an" w swoich nazwach, przyjrzyj się oknie danych wyjściowych i zobaczysz, że to samo zapytanie zostało podjęta pięć razy, co pierwsze cztery razy zwraca wyjątki przejściowe. Dla każdego błędu przejściowego zobaczysz dziennik, który zapisujesz podczas generowania błędu przejściowego w SchoolInterceptorTransientErrors klasie ("Zwracanie błędu przejściowego dla polecenia...") i zobaczysz dziennik zapisany podczas SchoolInterceptorLogging pobierania wyjątku.

    Dane wyjściowe rejestrowania przedstawiające ponawianie prób

    Po wprowadzeniu ciągu wyszukiwania zapytanie zwracające dane ucznia jest sparametryzowane:

    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:
    

    Nie rejestrujesz wartości parametrów, ale możesz to zrobić. Jeśli chcesz zobaczyć wartości parametrów, możesz napisać kod rejestrowania, aby pobrać wartości parametrów DbCommand z Parameters właściwości obiektu, który uzyskujesz w metodach przechwytywania.

    Pamiętaj, że nie można powtórzyć tego testu, chyba że zatrzymasz aplikację i uruchomisz ją ponownie. Jeśli chcesz mieć możliwość wielokrotnego testowania odporności połączenia w jednym uruchomieniu aplikacji, możesz napisać kod resetowania licznika błędów w pliku SchoolInterceptorTransientErrors.

  4. Aby zobaczyć różnicę w strategii wykonywania (zasady ponawiania), oznacz jako komentarz SetExecutionStrategy wiersz w pliku SchoolConfiguration.cs, ponownie uruchom stronę Uczniowie w trybie debugowania i wyszukaj ponownie ciąg "Throw".

    Tym razem debuger zatrzymuje się na pierwszym wygenerowany wyjątek natychmiast, gdy próbuje wykonać zapytanie po raz pierwszy.

    Fikcyjny wyjątek

  5. Usuń komentarz z wiersza SetExecutionStrategy w pliku SchoolConfiguration.cs.

Uzyskiwanie kodu

Pobieranie ukończonego projektu

Dodatkowe zasoby

Linki do innych zasobów programu Entity Framework można znaleźć w ASP.NET Dostęp do danych — zalecane zasoby.

Następne kroki

W tym samouczku zostały wykonane następujące czynności:

  • Włączona odporność połączenia
  • Przechwytywanie poleceń z włączoną obsługą
  • Przetestowano nową konfigurację

Przejdź do następnego artykułu, aby dowiedzieć się więcej o migracjach code first i wdrażaniu platformy Azure.