Udostępnij za pośrednictwem


Samouczek: poznaj zaawansowane scenariusze — ASP.NET MVC z EF Core

W poprzednim samouczku zaimplementowano dziedziczenie według tabeli na hierarchii. W tym samouczku przedstawiono kilka tematów, o których warto wiedzieć, gdy wykraczasz poza podstawy tworzenia aplikacji internetowych platformy ASP.NET Core korzystających z platformy Entity Framework Core.

W tym samouczku wykonasz następujące elementy:

  • Wykonywanie nieprzetworzonych zapytań SQL
  • Wywoływanie zapytania, aby zwrócić jednostki
  • Wywołaj zapytanie, aby zwrócić inne typy
  • Wywoływanie zapytania aktualizacji
  • Badanie zapytań SQL
  • Tworzenie warstwy abstrakcji
  • Dowiedz się więcej o automatycznym wykrywaniu zmian
  • Dowiedz się więcej o kodzie źródłowym i planach programowania EF Core
  • Dowiedz się, jak używać dynamicznego LINQ w celu uproszczenia kodu

Warunki wstępne

Wykonywanie nieprzetworzonych zapytań SQL

Jedną z zalet korzystania z platformy Entity Framework jest unikanie zbyt ścisłego wiązania kodu z określoną metodą przechowywania danych. Robi to przez wygenerowanie zapytań SQL i poleceń dla Ciebie, co pozwala również uwolnić cię od konieczności samodzielnego pisania ich. Istnieją jednak wyjątkowe scenariusze, w których należy uruchamiać określone zapytania SQL utworzone ręcznie. W tych scenariuszach interfejs API Entity Framework Code First zawiera metody, które umożliwiają przekazywanie poleceń SQL bezpośrednio do bazy danych. Dostępne są następujące opcje w EF Core 1.0:

  • Użyj metody DbSet.FromSql dla zapytań, które zwracają typy jednostek. Zwrócone obiekty muszą być typu oczekiwanego przez obiekt DbSet i są one automatycznie śledzone przez kontekst bazy danych, chyba że wyłączysz śledzenie.

  • Użyj Database.ExecuteSqlCommand dla poleceń innych niż zapytania.

Jeśli musisz uruchomić zapytanie zwracające typy, które nie są jednostkami, możesz użyć ADO.NET z połączeniem bazy danych dostarczonym przez platformę EF. Zwrócone dane nie są śledzone przez kontekst bazy danych, nawet jeśli używasz tej metody do pobierania typów jednostek.

Tak jak zawsze w przypadku wykonywania poleceń SQL w aplikacji internetowej, należy podjąć środki ostrożności, aby chronić witrynę przed atakami polegającymi na wstrzyknięciu kodu SQL. Jednym ze sposobów na to jest użycie sparametryzowanych zapytań w celu upewnienia się, że ciągi przesłane przez stronę internetową nie mogą być interpretowane jako polecenia SQL. W tym samouczku użyjesz sparametryzowanych zapytań podczas integrowania danych wejściowych użytkownika z zapytaniem.

Wywołaj zapytanie, aby zwrócić jednostki

Klasa DbSet<TEntity> udostępnia metodę, której można użyć do wykonania zapytania zwracającego jednostkę typu TEntity. Aby zobaczyć, jak to działa, zmienisz kod w metodzie Details kontrolera działu.

W DepartmentsController.csw metodzie Details zastąp kod pobierający dział wywołaniem metody FromSql, jak pokazano w poniższym wyróżnionym kodzie:

public async Task<IActionResult> Details(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    string query = "SELECT * FROM Department WHERE DepartmentID = {0}";
    var department = await _context.Departments
        .FromSql(query, id)
        .Include(d => d.Administrator)
        .AsNoTracking()
        .FirstOrDefaultAsync();

    if (department == null)
    {
        return NotFound();
    }

    return View(department);
}

Aby sprawdzić, czy nowy kod działa poprawnie, wybierz kartę Działy, a następnie Szczegóły dla jednego z działów.

Szczegóły Działu

Wywoływanie zapytania w celu zwrócenia innych typów

Wcześniej utworzono siatkę statystyk uczniów dla strony Informacje, która pokazała liczbę uczniów dla każdej daty rejestracji. Pobrałeś dane z zestawu encji Students (_context.Students) i użyłeś LINQ do przekładającej wyniki na listę obiektów modelu widoku EnrollmentDateGroup. Załóżmy, że chcesz napisać sam kod SQL zamiast używać linQ. Aby to zrobić, należy uruchomić zapytanie SQL zwracające coś innego niż obiekty jednostki. W wersji EF Core 1.0 jednym ze sposobów jest napisanie kodu ADO.NET i pobranie połączenia z bazą danych z Entity Framework.

W HomeController.cszastąp metodę About następującym kodem:

public async Task<ActionResult> About()
{
    List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
    var conn = _context.Database.GetDbConnection();
    try
    {
        await conn.OpenAsync();
        using (var command = conn.CreateCommand())
        {
            string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
                + "FROM Person "
                + "WHERE Discriminator = 'Student' "
                + "GROUP BY EnrollmentDate";
            command.CommandText = query;
            DbDataReader reader = await command.ExecuteReaderAsync();

            if (reader.HasRows)
            {
                while (await reader.ReadAsync())
                {
                    var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount = reader.GetInt32(1) };
                    groups.Add(row);
                }
            }
            reader.Dispose();
        }
    }
    finally
    {
        conn.Close();
    }
    return View(groups);
}

Dodaj instrukcję using:

using System.Data.Common;

Uruchom aplikację i przejdź do strony Informacje. Wyświetla te same dane, co wcześniej.

strona Informacje

Wywoływanie zapytania aktualizacji

Załóżmy, że administratorzy Uniwersytetu Contoso chcą wykonywać globalne zmiany w bazie danych, takie jak zmiana liczby punktów kredytowych dla każdego kursu. Jeśli uczelnia ma dużą liczbę kursów, byłoby nieefektywne pobranie ich wszystkich jako jednostek i zmiana ich indywidualnie. W tej sekcji zaimplementujesz stronę internetową, która umożliwia użytkownikowi określenie współczynnika, według którego należy zmienić liczbę kredytów dla wszystkich kursów, a następnie wprowadzisz tę zmianę, wykonując polecenie SQL UPDATE. Strona internetowa będzie wyglądać podobnie do poniższej ilustracji:

stronę Aktualizuj kredyty kursu

W CoursesController.csdodaj metody UpdateCourseCredits dla HttpGet i HttpPost.

public IActionResult UpdateCourseCredits()
{
    return View();
}
[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
    if (multiplier != null)
    {
        ViewData["RowsAffected"] = 
            await _context.Database.ExecuteSqlCommandAsync(
                "UPDATE Course SET Credits = Credits * {0}",
                parameters: multiplier);
    }
    return View();
}

Gdy kontroler przetwarza żądanie HttpGet, nic nie jest zwracane w ViewData["RowsAffected"], a widok wyświetla puste pole tekstowe i przycisk przesyłania, jak pokazano na poprzedniej ilustracji.

Po kliknięciu przycisku Update wywoływana jest metoda HttpPost, a mnożnik ma wartość wprowadzoną w polu tekstowym. Następnie kod wykonuje SQL, który aktualizuje kursy i zwraca liczbę zaktualizowanych wierszy do widoku w ViewData. Gdy widok pobiera wartość RowsAffected, wyświetla liczbę zaktualizowanych wierszy.

W Eksploratorze rozwiązań kliknij prawym przyciskiem myszy folder Views/Courses, a następnie kliknij Dodaj > Nowy element.

W oknie dialogowym Dodawanie nowego elementu kliknij pozycję ASP.NET Core w obszarze Zainstalowane w okienku po lewej stronie kliknij pozycję Razor Widoki nadaj nowej nazwie nowy widok UpdateCourseCredits.cshtml.

W Views/Courses/UpdateCourseCredits.cshtmlzastąp kod szablonu następującym kodem:

@{
    ViewBag.Title = "UpdateCourseCredits";
}

<h2>Update Course Credits</h2>

@if (ViewData["RowsAffected"] == null)
{
    <form asp-action="UpdateCourseCredits">
        <div class="form-actions no-color">
            <p>
                Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
            </p>
            <p>
                <input type="submit" value="Update" class="btn btn-default" />
            </p>
        </div>
    </form>
}
@if (ViewData["RowsAffected"] != null)
{
    <p>
        Number of rows updated: @ViewData["RowsAffected"]
    </p>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Uruchom metodę UpdateCourseCredits, wybierając kartę Courses, a następnie dodając ciąg "/UpdateCourseCredits" na końcu adresu URL na pasku adresu przeglądarki (na przykład: http://localhost:5813/Courses/UpdateCourseCredits). Wprowadź liczbę w polu tekstowym:

strona Aktualizacja kredytów kursu

Kliknij Aktualizuj. Zostanie wyświetlona liczba wierszy, których dotyczy problem:

Zaktualizowanie wierszy na stronie kredytów kursu

Kliknij Wróć do listy, aby wyświetlić listę kursów ze zmienioną liczbą punktów.

Należy pamiętać, że kod produkcyjny gwarantuje, że aktualizacje zawsze powodują prawidłowe dane. Uproszczony kod przedstawiony w tym miejscu może pomnożyć liczbę kredytów wystarczającą do wyników większych niż 5. (Właściwość Credits ma atrybut [Range(0, 5)]). Zapytanie aktualizacji działałoby, ale nieprawidłowe dane mogą spowodować nieoczekiwane wyniki w innych częściach systemu, które zakładają, że liczba kredytów wynosi 5 lub mniej.

Aby uzyskać więcej informacji na temat nieprzetworzonych zapytań SQL, zobacz Nieprzetworzone zapytania SQL.

Badanie zapytań SQL

Czasami warto zobaczyć rzeczywiste zapytania SQL wysyłane do bazy danych. Wbudowana funkcja rejestrowania dla platformy ASP.NET Core jest automatycznie używana przez EF Core do zapisywania dzienników zawierających język SQL dla zapytań i aktualizacji. W tej sekcji przedstawiono kilka przykładów rejestrowania SQL.

Otwórz StudentsController.cs i w metodzie Details ustaw punkt przerwania w instrukcji if (student == null).

Uruchom aplikację w trybie debugowania i przejdź do strony Szczegóły dla ucznia.

Przejdź do okna Dane wyjściowe z danymi wyjściowymi debugowania i zostanie wyświetlone zapytanie:

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (56ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [s].[ID], [s].[Discriminator], [s].[FirstName], [s].[LastName], [s].[EnrollmentDate]
FROM [Person] AS [s]
WHERE ([s].[Discriminator] = N'Student') AND ([s].[ID] = @__id_0)
ORDER BY [s].[ID]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (122ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT [s.Enrollments].[EnrollmentID], [s.Enrollments].[CourseID], [s.Enrollments].[Grade], [s.Enrollments].[StudentID], [e.Course].[CourseID], [e.Course].[Credits], [e.Course].[DepartmentID], [e.Course].[Title]
FROM [Enrollment] AS [s.Enrollments]
INNER JOIN [Course] AS [e.Course] ON [s.Enrollments].[CourseID] = [e.Course].[CourseID]
INNER JOIN (
    SELECT TOP(1) [s0].[ID]
    FROM [Person] AS [s0]
    WHERE ([s0].[Discriminator] = N'Student') AND ([s0].[ID] = @__id_0)
    ORDER BY [s0].[ID]
) AS [t] ON [s.Enrollments].[StudentID] = [t].[ID]
ORDER BY [t].[ID]

Zauważysz tutaj coś, co może cię zaskoczyć: program SQL wybiera maksymalnie 2 wiersze (TOP(2)) z tabeli Person. Metoda SingleOrDefaultAsync nie jest rozpoznawana jako 1 wiersz na serwerze. Oto dlaczego:

  • Jeśli zapytanie zwróci wiele wierszy, metoda zwraca wartość null.
  • Aby określić, czy zapytanie zwróci wiele wierszy, program EF musi sprawdzić, czy zwraca co najmniej 2.

Pamiętaj, że nie musisz używać trybu debugowania i zatrzymywać się w punkcie przerwania, aby uzyskać wyniki logowania w oknie dane wyjściowe. Jest to po prostu wygodny sposób zatrzymania rejestrowania w momencie, w którym chcesz przyjrzeć się danym wyjściowym. Jeśli tego nie zrobisz, rejestrowanie będzie trwało nadal i trzeba przewijać wstecz, aby znaleźć interesujące Cię części.

Tworzenie warstwy abstrakcji

Wielu deweloperów pisze kod w celu zaimplementowania wzorca repozytorium i wzorca jednostki pracy jako powłoki kodu współpracującego z platformą Entity Framework. Te wzorce mają na celu utworzenie warstwy abstrakcji między warstwą dostępu do danych a warstwą logiki biznesowej aplikacji. Zaimplementowanie tych wzorców może pomóc odizolować aplikację od zmian w magazynie danych i ułatwić zautomatyzowane testowanie jednostkowe lub programowanie oparte na testach (TDD). Jednak pisanie dodatkowego kodu w celu zaimplementowania tych wzorców nie zawsze jest najlepszym wyborem dla aplikacji korzystających z platformy EF z kilku powodów:

  • Sama klasa kontekstu EF izoluje kod od kodu specyficznego dla magazynu danych.

  • Klasa kontekstowa EF może działać jako jednostka pracy dla aktualizacji bazy danych, które są wykonywane przy użyciu programu EF.

  • Program EF zawiera funkcje implementowania funkcji TDD bez pisania kodu repozytorium.

Aby uzyskać informacje na temat implementacji wzorców repozytorium i jednostki pracy, zobacz wersję 5 programu Entity Framework z tej serii samouczków.

Program Entity Framework Core implementuje dostawcę bazy danych w pamięci, który może służyć do testowania. Aby uzyskać więcej informacji, zobacz Test with InMemory.

Automatyczne wykrywanie zmian

Program Entity Framework określa, w jaki sposób jednostka uległa zmianie (i w związku z tym które aktualizacje muszą być wysyłane do bazy danych), porównując bieżące wartości jednostki z oryginalnymi wartościami. Oryginalne wartości są zapisane, gdy obiekt jest zapytany lub dołączony. Niektóre metody, które powodują automatyczne wykrywanie zmian, są następujące:

  • DbContext.SaveChanges

  • DbContext.Entry

  • ChangeTracker.Entries

Jeśli śledzisz dużą liczbę jednostek i wielokrotnie wywołujesz jedną z tych metod w pętli, możesz uzyskać znaczne ulepszenia wydajności, tymczasowo wyłączając automatyczne wykrywanie zmian przy użyciu właściwości ChangeTracker.AutoDetectChangesEnabled. Na przykład:

_context.ChangeTracker.AutoDetectChangesEnabled = false;

EF Core kodu źródłowego i planów programowania

Źródło platformy Entity Framework Core znajduje się w https://github.com/dotnet/efcore. Repozytorium EF Core zawiera dzienne kompilacje, śledzenie błędów, specyfikacje funkcji, notatki ze spotkań projektowych i harmonogram przyszłego rozwoju. Możesz zgłosić lub znaleźć usterki i współtworzyć.

Mimo że kod źródłowy jest otwarty, platforma Entity Framework Core jest w pełni obsługiwana jako produkt firmy Microsoft. Zespół Microsoft Entity Framework kontroluje, które wkłady są akceptowane, i testuje wszystkie zmiany kodu, aby zapewnić jakość każdej wersji.

Inżynieria odwrotna z istniejącej bazy danych

Aby odtworzyć model danych, w tym klasy encji z istniejącej bazy danych, użyj polecenia scaffold-dbcontext. Zapoznaj się z samouczkiem wprowadzającym .

Używanie dynamicznego LINQ w celu uproszczenia kodu

W trzecim samouczku z tej serii pokazano, jak napisać kod LINQ przez ręczne zakodowanie nazw kolumn w zapytaniu switch. Przy wyborze dwóch kolumn, działa to dobrze, ale jeśli masz wiele kolumn, kod może stać się zbyt rozbudowany. Aby rozwiązać ten problem, możesz użyć metody EF.Property, aby określić nazwę właściwości jako ciąg. Aby wypróbować to podejście, zastąp metodę Index w StudentsController poniższym kodem.

 public async Task<IActionResult> Index(
     string sortOrder,
     string currentFilter,
     string searchString,
     int? pageNumber)
 {
     ViewData["CurrentSort"] = sortOrder;
     ViewData["NameSortParm"] = 
         String.IsNullOrEmpty(sortOrder) ? "LastName_desc" : "";
     ViewData["DateSortParm"] = 
         sortOrder == "EnrollmentDate" ? "EnrollmentDate_desc" : "EnrollmentDate";

     if (searchString != null)
     {
         pageNumber = 1;
     }
     else
     {
         searchString = currentFilter;
     }

     ViewData["CurrentFilter"] = searchString;

     var students = from s in _context.Students
                    select s;
     
     if (!String.IsNullOrEmpty(searchString))
     {
         students = students.Where(s => s.LastName.Contains(searchString)
                                || s.FirstMidName.Contains(searchString));
     }

     if (string.IsNullOrEmpty(sortOrder))
     {
         sortOrder = "LastName";
     }

     bool descending = false;
     if (sortOrder.EndsWith("_desc"))
     {
         sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
         descending = true;
     }

     if (descending)
     {
         students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
     }
     else
     {
         students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
     }

     int pageSize = 3;
     return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), 
         pageNumber ?? 1, pageSize));
 }

Podziękowania

Tom Dykstra i Rick Anderson (twitter @RickAndMSFT) napisał ten samouczek. Rowan Miller, Diego Vega i inni członkowie zespołu Entity Framework pomagali w przeglądach kodu i rozwiązywali błędy, które pojawiły się podczas pisania kodu do samouczków. John Parente i Paul Goldman pracowali nad aktualizacją samouczka dotyczącego ASP.NET Core 2.2.

Rozwiązywanie typowych błędów

ContosoUniversity.dll używane przez inny proces

Komunikat o błędzie:

Nie można otworzyć "... bin\Debug\netcoreapp1.0\ContosoUniversity.dll' for writing -- 'The process cannot access the file '...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll" ponieważ jest używany przez inny proces.

Rozwiązanie:

Zatrzymaj witrynę w programie IIS Express. Przejdź do obszaru powiadomień systemu Windows, znajdź element IIS Express i kliknij prawym przyciskiem myszy jego ikonę, wybierz witrynę Contoso University, a następnie kliknij pozycję Zatrzymaj witrynę.

Migracja z szablonem bez kodu w metodach Up i Down

Możliwa przyczyna:

Polecenia EF CLI nie zamykają ani nie zapisują automatycznie plików kodu. Jeśli podczas uruchamiania polecenia migrations add wprowadzono niezapisane zmiany, program EF nie znajdzie zmian.

Rozwiązanie:

Uruchom polecenie migrations remove, zapisz zmiany kodu i uruchom ponownie migrations add polecenie.

Błędy podczas uruchamiania aktualizacji bazy danych

Podczas wprowadzania zmian schematu w bazie danych z istniejącymi danymi można uzyskać inne błędy. Jeśli wystąpią błędy migracji, których nie możesz rozwiązać, możesz zmienić nazwę bazy danych w parametrach połączenia lub usunąć bazę danych. W przypadku nowej bazy danych nie ma danych do migracji, a polecenie update-database ma znacznie większe prawdopodobieństwo zakończenia bez błędów.

Najprostszym podejściem jest zmiana nazwy bazy danych w appsettings.json. Przy następnym uruchomieniu database updatezostanie utworzona nowa baza danych.

Aby usunąć bazę danych w programie SSOX, kliknij prawym przyciskiem myszy bazę danych, kliknij Usuń, a następnie w oknie dialogowym Usuń bazę danych wybierz pozycję Zamknij istniejące połączenia i kliknij przycisk OK.

Aby usunąć bazę danych przy użyciu polecenia CLI database drop, uruchom je:

dotnet ef database drop

Błąd podczas lokalizowania wystąpienia programu SQL Server

Komunikat o błędzie:

Wystąpił błąd związany z siecią lub specyficzny dla instancji podczas nawiązywania połączenia z serwerem SQL. Serwer nie został znaleziony lub nie był dostępny. Sprawdź, czy nazwa wystąpienia jest poprawna i czy program SQL Server jest skonfigurowany tak, aby zezwalał na połączenia zdalne. (dostawca: Interfejsy sieciowe SQL, błąd: 26 — błąd podczas lokalizowania określonego serwera/wystąpienia)

Rozwiązanie:

Sprawdź parametry połączenia. Jeśli plik bazy danych został ręcznie usunięty przez Ciebie, zmień nazwę bazy danych w ciągu połączeniowym, aby rozpocząć od nowa z nową bazą danych.

Pobieranie kodu

Pobierz lub wyświetl ukończoną aplikację.

Dodatkowe zasoby

Aby uzyskać więcej informacji na temat EF Core, zobacz dokumentację Entity Framework Core. Dostępna jest również książka: Entity Framework Core w praktyce.

Aby uzyskać informacje na temat wdrażania aplikacji internetowej, zobacz host i wdrażanie ASP.NET Core.

Aby uzyskać informacje na temat innych tematów związanych z ASP.NET Core MVC, takich jak uwierzytelnianie i autoryzacja, zobacz Overview of ASP.NET Core.

Aby uzyskać wskazówki dotyczące tworzenia niezawodnej, bezpiecznej, wydajnej, testowalnej i skalowalnej aplikacji ASP.NET Core, zobacz wzorce aplikacji internetowych Enterprise. Dostępna jest kompletna przykładowa aplikacja internetowa jakości produkcyjnej, która implementuje wzorce.

Następne kroki

W tym samouczku zostaniesz poprowadzony do następujących czynności:

  • Wykonywanie nieprzetworzonych zapytań SQL
  • Wywoływanie zapytania w celu zwrócenia jednostek
  • Wywoływanie zapytania w celu zwrócenia innych typów
  • Wywoływanie zapytania aktualizacji
  • Zbadane zapytania SQL
  • Tworzenie warstwy abstrakcji
  • Informacje o automatycznym wykrywaniu zmian
  • Dowiedziono się o EF Core kodzie źródłowym i planach rozwoju
  • Dowiedz się, jak używać dynamicznego LINQ w celu uproszczenia kodu

Ta seria samouczków zawiera instrukcje dotyczące korzystania z platformy Entity Framework Core w aplikacji ASP.NET Core MVC. Ta seria wykorzystała nową bazę danych; alternatywą jest inżynieria odwrotna modelu z istniejącej bazy danych.