Samouczek: informacje o zaawansowanych scenariuszach — ASP.NET MVC za pomocą polecenia EF Core

W poprzednim samouczku zaimplementowano dziedziczenie tabeli na hierarchię. 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 zostały wykonane następujące czynności:

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

Wymagania wstępne

Wykonywanie pierwotnych 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 wersji EF Core 1.0:

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

  • Użyj polecenia 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ływanie zapytania w celu zwrócenia jednostek

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 Details metodzie kontrolera działu.

W DepartmentsController.csmetodzie Details zastąp kod, który pobiera dział za pomocą FromSql wywołania metody, 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 pozycję Szczegóły dla jednego z działów.

Department Details

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. Dane z zestawu jednostek Students (_context.Students) i użyto linQ do projekcji EnrollmentDateGroup wyników na listę obiektów modelu widoku. 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 EF Core wersji 1.0 jednym ze sposobów jest napisanie kodu ADO.NET i pobranie połączenia z bazą danych z programu EF.

W HomeController.cspliku zastą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, które zostały wcześniej.

About page

Wywoływanie zapytania aktualizacji

Załóżmy, że administratorzy platformy Contoso University chcą wykonywać globalne zmiany w bazie danych, takie jak zmiana liczby środków na każdy kurs. 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, przez który należy zmienić liczbę środków dla wszystkich kursów, a następnie wprowadzisz zmianę, wykonując instrukcję SQL UPDATE. Strona internetowa będzie wyglądać podobnie do poniższej ilustracji:

Update Course Credits page

W CoursesController.cspliku dodaj metody UpdateCourseCredits dla metod 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, w widoku jest wyświetlane ViewData["RowsAffected"]puste pole tekstowe i przycisk przesyłania, jak pokazano na poprzedniej ilustracji.

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

W Eksplorator rozwiązań kliknij prawym przyciskiem myszy folder Widoki/kursy, a następnie kliknij polecenie 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 Wyświetl i nadaj nowej nazwie nowy widok UpdateCourseCredits.cshtml.

W Views/Courses/UpdateCourseCredits.cshtmlpliku zastą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ę Kursy , 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:

Update Course Credits page

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

Update Course Credits page rows affected

Kliknij przycisk Wstecz do listy , aby wyświetlić listę kursów z poprawioną liczbą kredytó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ę środków wystarczającą do zwiększenia liczby większej niż 5. (Właściwość Credits ma [Range(0, 5)] atrybut). 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 do EF Core 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 metodę 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 wyświetlonymi 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. Poniżej przedstawiono przyczyny:

  • 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ć dane wyjściowe rejestrowania 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 kontynuowane i trzeba przewinąć z powrotem, aby znaleźć interesujące Cię części.

Tworzenie warstwy abstrakcji

Wielu deweloperów pisze kod w celu zaimplementowania repozytorium i jednostki wzorców pracy jako otoki kodu, który współpracuje 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 implementowania repozytorium i jednostki wzorców pracy, zobacz program Entity Framework 5 w 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 (Testowanie za pomocą rozwiązania 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ą przechowywane po wysłaniu zapytania do jednostki lub dołączeniu. 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 ChangeTracker.AutoDetectChangesEnabled właściwości . Przykład:

_context.ChangeTracker.AutoDetectChangesEnabled = false;

EF Core kod źródłowy i plany programistyczne

Źródło platformy Entity Framework Core znajduje się pod adresem https://github.com/dotnet/efcore. Repozytorium EF Core zawiera nocne kompilacje, śledzenie problemów, specyfikacje funkcji, notatki ze spotkania projektowego i plan rozwoju w przyszłości. 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ół programu Microsoft Entity Framework kontroluje, które współtworzenie jest akceptowane, i testuje wszystkie zmiany kodu w celu zapewnienia jakości poszczególnych wersji.

Inżynieria odwrotna z istniejącej bazy danych

Aby odtworzyć model danych, w tym klasy jednostek z istniejącej bazy danych, użyj polecenia scaffold-dbcontext . Zobacz samouczek wprowadzający.

Używanie dynamicznych zapytań LINQ w celu uproszczenia kodu

Trzeci samouczek z tej serii pokazuje, jak napisać kod LINQ przez stałe kodowanie nazw kolumn w switch instrukcji . W przypadku wybrania dwóch kolumn działa to dobrze, ale jeśli masz wiele kolumn, kod może uzyskać pełne informacje. Aby rozwiązać ten problem, możesz użyć EF.Property metody , aby określić nazwę właściwości jako ciąg. Aby wypróbować to podejście, zastąp metodę Index w StudentsController pliku 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 pomagali w rozwiązywaniu problemów z debugowaniem, które pojawiły się podczas pisania kodu dla samouczków. John Parente i Paul Goldman pracowali nad aktualizacją samouczka dotyczącego ASP.NET Core 2.2.

Rozwiązywanie problemów z typowymi błędami

ContosoUniversity.dll używany przez inny proces

Komunikat o błędzie:

Nie można otworzyć "... bin\Debug\netcoreapp1.0\ContosoUniversity.dll" na potrzeby pisania — "Proces nie może uzyskać dostępu do pliku "...\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ź pozycję IIS Express i kliknij prawym przyciskiem myszy ikonę, wybierz witrynę Contoso University, a następnie kliknij pozycję Zatrzymaj witrynę.

Szkielet migracji bez kodu w metodach w górę i w dół

Możliwa przyczyna:

Polecenia interfejsu wiersza polecenia ef nie zamykają się automatycznie i nie zapisują plików kodu. Jeśli podczas uruchamiania migrations add polecenia nie zostaną zapisane zmiany, program EF nie znajdzie zmian.

Rozwiązanie:

migrations remove Uruchom polecenie , zapisz zmiany kodu i uruchom migrations add ponownie 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 parametry połączenia lub usunąć bazę danych. W przypadku nowej bazy danych nie ma danych do zmigrowania, a polecenie update-database jest znacznie bardziej prawdopodobne, aby ukończyć bez błędów.

Najprostszym podejściem jest zmiana nazwy bazy danych w pliku 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 polecenie Usuń, a następnie w oknie dialogowym Usuwanie bazy danych wybierz polecenie Zamknij istniejące połączenia i kliknij przycisk OK.

Aby usunąć bazę danych przy użyciu interfejsu wiersza polecenia, uruchom polecenie interfejsu database drop wiersza polecenia:

dotnet ef database drop

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

Komunikat o błędzie:

Podczas nawiązywania połączenia z serwerem SQL wystąpił błąd dotyczący sieci lub wystąpienia. Serwer nie został znaleziony lub był niedostępny. Sprawdź, czy nazwa wystąpienia jest prawidłowa oraz czy program SQL Server skonfigurowano 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, zmień nazwę bazy danych w ciągu konstrukcyjnym, aby rozpocząć od nowa baza danych.

Uzyskiwanie kodu

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

Dodatkowe zasoby

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

Aby uzyskać informacje na temat wdrażania aplikacji internetowej, zobacz Hostowanie 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 Omówienie ASP.NET Core.

Następne kroki

W tym samouczku zostały wykonane następujące 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
  • Informacje o EF Core kodzie źródłowym i planach programowania
  • 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 współpracowała z nową bazą danych; Alternatywą jest odwrócenie modelu z istniejącej bazy danych.