Tutorial: Pelajari tentang skenario tingkat lanjut - ASP.NET MVC dengan EF Core

Dalam tutorial sebelumnya, Anda menerapkan warisan tabel per hierarki. Tutorial ini memperkenalkan beberapa topik yang berguna untuk diperhatikan ketika Anda melampaui dasar-dasar pengembangan aplikasi web ASP.NET Core yang menggunakan Entity Framework Core.

Di tutorial ini, Anda akan:

  • Melakukan kueri SQL mentah
  • Memanggil kueri untuk mengembalikan entitas
  • Memanggil kueri untuk mengembalikan tipe lain
  • Memanggil kueri pembaruan
  • Memeriksa kueri SQL
  • Membuat lapisan abstraksi
  • Pelajari tentang Deteksi perubahan otomatis
  • Pelajari tentang EF Core kode sumber dan rencana pengembangan
  • Pelajari cara menggunakan LINQ dinamis untuk menyederhanakan kode

Prasyarat

Melakukan kueri SQL mentah

Salah satu keuntungan menggunakan Kerangka Kerja Entitas adalah menghindari pengikatan kode Anda terlalu dekat dengan metode penyimpanan data tertentu. Ini dilakukan dengan menghasilkan kueri dan perintah SQL untuk Anda, yang juga membebaskan Anda dari harus menulisnya sendiri. Tetapi ada skenario luar biasa ketika Anda perlu menjalankan kueri SQL tertentu yang telah Anda buat secara manual. Untuk skenario ini, API Pertama Kode Kerangka Kerja Entitas menyertakan metode yang memungkinkan Anda meneruskan perintah SQL langsung ke database. Anda memiliki opsi berikut di EF Core 1.0:

  • DbSet.FromSql Gunakan metode untuk kueri yang mengembalikan jenis entitas. Objek yang dikembalikan harus dari jenis yang diharapkan oleh DbSet objek, dan secara otomatis dilacak oleh konteks database kecuali Anda menonaktifkan pelacakan.

  • Database.ExecuteSqlCommand Gunakan untuk perintah non-kueri.

Jika Anda perlu menjalankan kueri yang mengembalikan tipe yang bukan entitas, Anda bisa menggunakan ADO.NET dengan koneksi database yang disediakan oleh EF. Data yang dikembalikan tidak dilacak oleh konteks database, bahkan jika Anda menggunakan metode ini untuk mengambil jenis entitas.

Seperti yang selalu benar ketika Anda menjalankan perintah SQL dalam aplikasi web, Anda harus mengambil tindakan pencegahan untuk melindungi situs Anda dari serangan injeksi SQL. Salah satu cara untuk melakukannya adalah dengan menggunakan kueri berparameter untuk memastikan bahwa string yang dikirimkan oleh halaman web tidak dapat ditafsirkan sebagai perintah SQL. Dalam tutorial ini Anda akan menggunakan kueri berparameter saat mengintegrasikan input pengguna ke dalam kueri.

Memanggil kueri untuk mengembalikan entitas

Kelas DbSet<TEntity> menyediakan metode yang dapat Anda gunakan untuk menjalankan kueri yang mengembalikan entitas jenis TEntity. Untuk melihat cara kerjanya, Anda akan mengubah kode dalam Details metode pengontrol Departemen.

Dalam DepartmentsController.cs, dalam Details metode , ganti kode yang mengambil departemen dengan panggilan metode, seperti yang FromSql ditunjukkan dalam kode yang disorot berikut:

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

Untuk memverifikasi bahwa kode baru berfungsi dengan benar, pilih tab Departemen lalu Detail untuk salah satu departemen.

Department Details

Memanggil kueri untuk mengembalikan tipe lain

Sebelumnya Anda membuat kisi statistik siswa untuk halaman Tentang yang menunjukkan jumlah siswa untuk setiap tanggal pendaftaran. Anda mendapatkan data dari kumpulan entitas Siswa (_context.Students) dan menggunakan LINQ untuk memproyeksikan hasilnya ke dalam daftar EnrollmentDateGroup objek model tampilan. Misalkan Anda ingin menulis SQL itu sendiri daripada menggunakan LINQ. Untuk melakukannya, Anda perlu menjalankan kueri SQL yang mengembalikan sesuatu selain objek entitas. Dalam EF Core 1.0, salah satu cara untuk melakukannya adalah dengan menulis kode ADO.NET dan mendapatkan koneksi database dari EF.

Di HomeController.cs, ganti About metode dengan kode berikut:

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

Tambahkan pernyataan penggunaan:

using System.Data.Common;

Jalankan aplikasi dan buka halaman Tentang. Ini menampilkan data yang sama seperti sebelumnya.

About page

Memanggil kueri pembaruan

Misalkan administrator Contoso University ingin melakukan perubahan global dalam database, seperti mengubah jumlah kredit untuk setiap kursus. Jika universitas memiliki sejumlah besar kursus, tidak efisien untuk mengambil semuanya sebagai entitas dan mengubahnya satu per satu. Di bagian ini Anda akan menerapkan halaman web yang memungkinkan pengguna menentukan faktor untuk mengubah jumlah kredit untuk semua kursus, dan Anda akan membuat perubahan dengan menjalankan pernyataan PEMBARUAN SQL. Halaman web akan terlihat seperti ilustrasi berikut:

Update Course Credits page

Di CoursesController.cs, tambahkan metode UpdateCourseCredits untuk HttpGet dan 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();
}

Ketika pengontrol memproses permintaan HttpGet, tidak ada yang dikembalikan dalam ViewData["RowsAffected"], dan tampilan menampilkan kotak teks kosong dan tombol kirim, seperti yang ditunjukkan dalam ilustrasi sebelumnya.

Saat tombol Perbarui diklik, metode HttpPost dipanggil, dan pengali memiliki nilai yang dimasukkan dalam kotak teks. Kode kemudian menjalankan SQL yang memperbarui kursus dan mengembalikan jumlah baris yang terpengaruh ke tampilan di ViewData. Saat tampilan mendapatkan RowsAffected nilai, tampilan akan menampilkan jumlah baris yang diperbarui.

Di Penjelajah Solusi, klik kanan folder Tampilan/Kursus, lalu klik Tambahkan > Item Baru.

Dalam dialog Tambahkan Item Baru, klik ASP.NET Core di bawah Terinstal di panel kiri, klik Razor Tampilan, dan beri nama tampilan UpdateCourseCredits.cshtmlbaru .

Di Views/Courses/UpdateCourseCredits.cshtml, ganti kode templat dengan kode berikut:

@{
    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>

UpdateCourseCredits Jalankan metode dengan memilih tab Kursus, lalu tambahkan "/UpdateCourseCredits" ke akhir URL di bilah alamat browser (misalnya: http://localhost:5813/Courses/UpdateCourseCredits). Masukkan angka dalam kotak teks:

Update Course Credits page

Klik Perbarui. Anda melihat jumlah baris yang terpengaruh:

Update Course Credits page rows affected

Klik Kembali ke Daftar untuk melihat daftar kursus dengan jumlah kredit yang direvisi.

Perhatikan bahwa kode produksi akan memastikan bahwa pembaruan selalu menghasilkan data yang valid. Kode yang disederhanakan yang ditunjukkan di sini dapat mengalikan jumlah kredit yang cukup untuk menghasilkan angka yang lebih besar dari 5. (Properti Credits memiliki [Range(0, 5)] atribut.) Kueri pembaruan akan berfungsi tetapi data yang tidak valid dapat menyebabkan hasil yang tidak terduga di bagian lain dari sistem yang mengasumsikan jumlah kredit adalah 5 atau kurang.

Untuk informasi selengkapnya tentang kueri SQL mentah, lihat Kueri SQL Mentah.

Memeriksa kueri SQL

Terkadang sangat membantu untuk dapat melihat kueri SQL aktual yang dikirim ke database. Fungsionalitas pengelogan bawaan untuk ASP.NET Core secara otomatis digunakan oleh EF Core untuk menulis log yang berisi SQL untuk kueri dan pembaruan. Di bagian ini Anda akan melihat beberapa contoh pengelogan SQL.

Buka StudentsController.cs dan dalam Details metode atur titik henti pada if (student == null) pernyataan .

Jalankan aplikasi dalam mode debug, dan buka halaman Detail untuk siswa.

Buka jendela Output yang memperlihatkan output debug, dan Anda melihat kueri:

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]

Anda akan melihat sesuatu di sini yang mungkin mengejutkan Anda: SQL memilih hingga 2 baris (TOP(2)) dari tabel Orang. Metode SingleOrDefaultAsync ini tidak mengatasi 1 baris di server. Berikut alasannya:

  • Jika kueri akan mengembalikan beberapa baris, metode mengembalikan null.
  • Untuk menentukan apakah kueri akan mengembalikan beberapa baris, EF harus memeriksa apakah kueri mengembalikan setidaknya 2.

Perhatikan bahwa Anda tidak perlu menggunakan mode debug dan berhenti di titik henti untuk mendapatkan output pengelogan di jendela Output . Ini hanyalah cara mudah untuk menghentikan pengelogan pada titik yang ingin Anda lihat outputnya. Jika Anda tidak melakukannya, pengelogan berlanjut dan Anda harus menggulir kembali untuk menemukan bagian yang Anda minati.

Membuat lapisan abstraksi

Banyak pengembang menulis kode untuk mengimplementasikan repositori dan unit pola kerja sebagai pembungkus di sekitar kode yang berfungsi dengan Kerangka Kerja Entitas. Pola-pola ini dimaksudkan untuk membuat lapisan abstraksi antara lapisan akses data dan lapisan logika bisnis aplikasi. Menerapkan pola ini dapat membantu mengisolasi aplikasi Anda dari perubahan di penyimpanan data dan dapat memfasilitasi pengujian unit otomatis atau pengembangan berbasis pengujian (TDD). Namun, menulis kode tambahan untuk mengimplementasikan pola-pola ini tidak selalu menjadi pilihan terbaik untuk aplikasi yang menggunakan EF, karena beberapa alasan:

  • Kelas konteks EF itu sendiri mengisolasi kode Anda dari kode khusus penyimpanan data.

  • Kelas konteks EF dapat bertindak sebagai kelas unit kerja untuk pembaruan database yang Anda lakukan menggunakan EF.

  • EF mencakup fitur untuk menerapkan TDD tanpa menulis kode repositori.

Untuk informasi tentang cara mengimplementasikan repositori dan unit pola kerja, lihat versi Entity Framework 5 dari seri tutorial ini.

Entity Framework Core mengimplementasikan penyedia database dalam memori yang dapat digunakan untuk pengujian. Untuk informasi selengkapnya, lihat Menguji dengan InMemory.

Deteksi perubahan otomatis

Kerangka Kerja Entitas menentukan bagaimana entitas telah berubah (dan oleh karena itu pembaruan mana yang perlu dikirim ke database) dengan membandingkan nilai entitas saat ini dengan nilai asli. Nilai asli disimpan saat entitas dikueri atau dilampirkan. Beberapa metode yang menyebabkan deteksi perubahan otomatis adalah sebagai berikut:

  • DbContext.SaveChanges

  • DbContext.Entry

  • ChangeTracker.Entries

Jika Anda melacak sejumlah besar entitas dan memanggil salah satu metode ini berkali-kali dalam perulangan, Anda mungkin mendapatkan peningkatan performa yang signifikan dengan menonaktifkan deteksi perubahan otomatis untuk sementara waktu menggunakan ChangeTracker.AutoDetectChangesEnabled properti . Contohnya:

_context.ChangeTracker.AutoDetectChangesEnabled = false;

EF Core kode sumber dan rencana pengembangan

Sumber Entity Framework Core berada di https://github.com/dotnet/efcore. EF Core Repositori berisi build malam hari, pelacakan masalah, spesifikasi fitur, catatan rapat desain, dan peta jalan untuk pengembangan di masa mendatang. Anda dapat mengajukan atau menemukan bug, dan berkontribusi.

Meskipun kode sumber terbuka, Entity Framework Core sepenuhnya didukung sebagai produk Microsoft. Tim Microsoft Entity Framework menjaga kontrol atas kontribusi mana yang diterima dan menguji semua perubahan kode untuk memastikan kualitas setiap rilis.

Reverse engineer dari database yang sudah ada

Untuk merekayasa balik model data termasuk kelas entitas dari database yang ada, gunakan perintah scaffold-dbcontext . Lihat tutorial memulai.

Menggunakan LINQ dinamis untuk menyederhanakan kode

Tutorial ketiga dalam seri ini menunjukkan cara menulis kode LINQ dengan nama kolom hard-coding dalam pernyataan switch . Dengan dua kolom untuk dipilih, ini berfungsi dengan baik, tetapi jika Anda memiliki banyak kolom kode bisa mendapatkan verbose. Untuk mengatasi masalah tersebut EF.Property , Anda dapat menggunakan metode untuk menentukan nama properti sebagai string. Untuk mencoba pendekatan ini, ganti Index metode dalam StudentsController dengan kode berikut.

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

Ucapan terima kasih

Tom Dykstra dan Rick Anderson (twitter @RickAndMSFT) menulis tutorial ini. Rowan Miller, Diego Vega, dan anggota lain dari tim Entity Framework dibantu dengan ulasan kode dan membantu masalah debug yang muncul saat kami menulis kode untuk tutorial. John Parente dan Paul Goldman bekerja untuk memperbarui tutorial untuk ASP.NET Core 2.2.

Pemecahan masalah kesalahan umum

ContosoUniversity.dll digunakan oleh proses lain

Pesan kesalahan:

Tidak dapat membuka '... bin\Debug\netcoreapp1.0\ContosoUniversity.dll' untuk menulis -- 'Proses tidak dapat mengakses file '...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll' karena sedang digunakan oleh proses lain.

Solusi:

Hentikan situs di IIS Express. Buka Baki Sistem Windows, temukan IIS Express dan klik kanan ikonnya, pilih situs Universitas Contoso, lalu klik Hentikan Situs.

Perancah migrasi tanpa kode dalam metode Naik dan Turun

Kemungkinan penyebabnya:

Perintah EF CLI tidak secara otomatis menutup dan menyimpan file kode. Jika Anda memiliki perubahan yang belum disimpan saat menjalankan migrations add perintah, EF tidak akan menemukan perubahan Anda.

Solusi:

Jalankan migrations remove perintah , simpan perubahan kode Anda dan jalankan migrations add ulang perintah .

Kesalahan saat menjalankan pembaruan database

Dimungkinkan untuk mendapatkan kesalahan lain saat membuat perubahan skema dalam database yang memiliki data yang ada. Jika Anda mendapatkan kesalahan migrasi yang tidak bisa Anda atasi, Anda bisa mengubah nama database di string koneksi atau menghapus database. Dengan database baru, tidak ada data untuk dimigrasikan, dan perintah update-database jauh lebih mungkin diselesaikan tanpa kesalahan.

Pendekatan paling sederhana adalah mengganti nama database di appsettings.json. Saat berikutnya Anda menjalankan database update, database baru akan dibuat.

Untuk menghapus database di SSOX, klik kanan database, klik Hapus, lalu dalam kotak dialog Hapus Database pilih Tutup koneksi yang sudah ada dan klik OK.

Untuk menghapus database dengan menggunakan CLI, jalankan database drop perintah CLI:

dotnet ef database drop

Kesalahan menemukan instans SQL Server

Pesan Kesalahan:

Timbul kesalahan terkait jaringan atau spesifik instans saat membuat sambungan ke SQL Server. Server tak ditemukan atau tak bisa diakses. Verifikasi bahwa nama instans sudah benar dan SQL Server dikonfigurasi untuk memungkinkan koneksi jarak jauh. (penyedia: Antarmuka Jaringan SQL Server, kesalahan: 26 - Kesalahan Menemukan Server/instans yang ditentukan)

Solusi:

Periksa string koneksi. Jika Anda telah menghapus file database secara manual, ubah nama database dalam string konstruksi untuk memulai kembali dengan database baru.

Mendapatkan kode

Unduh atau lihat aplikasi yang telah selesai.

Sumber Daya Tambahan:

Untuk informasi selengkapnya tentang EF Core, lihat dokumentasi Entity Framework Core. Buku juga tersedia: Entity Framework Core in Action.

Untuk informasi tentang cara menyebarkan aplikasi web, lihat Menghosting dan menyebarkan ASP.NET Core.

Untuk informasi tentang topik lain yang terkait dengan ASP.NET Core MVC, seperti autentikasi dan otorisasi, lihat Gambaran Umum ASP.NET Core.

Langkah berikutnya

Di tutorial ini, Anda akan:

  • Kueri SQL mentah yang dilakukan
  • Memanggil kueri untuk mengembalikan entitas
  • Memanggil kueri untuk mengembalikan tipe lain
  • Disebut kueri pembaruan
  • Kueri SQL yang diperiksa
  • Membuat lapisan abstraksi
  • Dipelajari tentang Deteksi perubahan otomatis
  • Mempelajari tentang EF Core kode sumber dan rencana pengembangan
  • Mempelajari cara menggunakan LINQ dinamis untuk menyederhanakan kode

Ini menyelesaikan rangkaian tutorial ini tentang menggunakan Entity Framework Core dalam aplikasi MVC Core ASP.NET. Seri ini bekerja dengan database baru; alternatifnya adalah merekayasa balik model dari database yang ada.