Razor Pagine con Entity Framework Core in ASP.NET Core - Esercitazione 1 di 8

Di Tom Dykstra, Jeremy Likness e Jon P Smith

Questa è la prima di una serie di esercitazioni che illustrano come usare Entity Framework (EF) Core in un'app ASP.NET Core Razor Pages . Con queste esercitazioni viene creato un sito Web per una fittizia Contoso University. Il sito include funzionalità, come ad esempio l'ammissione di studenti, la creazione di corsi e le assegnazioni degli insegnati. L'esercitazione usa l'approccio code first. Per informazioni su come seguire questa esercitazione usando il primo approccio al database, vedere questo problema di GitHub.

Scaricare o visualizzare l'app completata.Istruzioni per il download.

Prerequisiti

Motori di database

Le istruzioni per Visual Studio usano SQL Server Local DB, una versione di SQL Server Express eseguita solo in Windows.

Risoluzione dei problemi

Se si verifica un problema che non è possibile risolvere, confrontare il codice con il progetto completato. Un buon modo per ottenere assistenza consiste nel pubblicare una domanda per StackOverflow.com, usando il tag ASP.NET Core o il EF Core tag .

App di esempio

L'applicazione compilata in queste esercitazioni è il sito Web di base di un'università. Gli utenti possono visualizzare e aggiornare le informazioni che riguardano studenti, corsi e insegnanti. Di seguito sono disponibili alcune schermate dell'esercitazione.

Students Index page

Students Edit page

Lo stile dell'interfaccia utente del sito è basato sui modelli di progetto predefiniti. L'obiettivo dell'esercitazione è come usare EF Core con ASP.NET Core, non su come personalizzare l'interfaccia utente.

Facoltativo: compilare il download dell'esempio

Questo passaggio è facoltativo. La compilazione dell'app completata è consigliata quando si verificano problemi che non è possibile risolvere. Se si verifica un problema che non è possibile risolvere, confrontare il codice con il progetto completato. Istruzioni per il download.

Selezionare questa opzione ContosoUniversity.csproj per aprire il progetto.

  • Compilare il progetto.

  • Nella console di Gestione pacchetti eseguire il comando seguente:

    Update-Database
    

Eseguire il progetto per il seeding del database.

Creare il progetto di app Web

  1. Avviare Visual Studio 2022 e selezionare Crea un nuovo progetto.

    Create a new project from the start window

  2. Nella finestra di dialogo Crea un nuovo progetto selezionare ASP.NET Core Web App e quindi selezionare Avanti.

    Create an ASP.NET Core Web App

  3. Nella finestra di dialogo Configura il nuovo progetto immettere ContosoUniversity per Nome progetto. È importante denominare il progetto ContosoUniversity, inclusa la corrispondenza con la maiuscola, in modo che gli spazi dei nomi corrispondano quando si copia e incolla il codice di esempio.

  4. Selezionare Avanti.

  5. Nella finestra di dialogo Informazioni aggiuntive selezionare .NET 6.0 (supporto a lungo termine) e quindi selezionare Crea.

    Additional information

Impostare lo stile del sito

Copiare e incollare il codice seguente nel Pages/Shared/_Layout.cshtml file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/ContosoUniversity.styles.css" asp-append-version="true" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                    <ul class="navbar-nav flex-grow-1">                        
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

Il file di layout imposta l'intestazione, il piè di pagina e il menu del sito. Il codice precedente apporta le modifiche seguenti:

  • Ogni occorrenza di "ContosoUniversity" in "Contoso University". Le occorrenze sono tre.
  • Le voci di Home menu e Privacy vengono eliminate.
  • Le voci vengono aggiunte per About, Students, Courses, Instructors e Departments.

In Pages/Index.cshtmlsostituire il contenuto del file con il codice seguente:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
@*                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
*@                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
@*                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
*@                </p>
            </div>
        </div>
    </div>
</div>

Il codice precedente sostituisce il testo relativo a ASP.NET Core con testo su questa app.

Eseguire l'app per verificare che venga visualizzata la home page.

Modello di dati

Nelle sezioni seguenti viene descritto come creare un modello di dati:

Course-Enrollment-Student data model diagram

Uno studente può iscriversi a un numero qualsiasi di corsi e un corso può avere un numero qualsiasi di studenti iscritti.

Entità Student (Studente)

Student entity diagram

  • Creare una cartella Models nella cartella del progetto.
  • Creare Models/Student.cs con il codice seguente:
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

La proprietà ID diventa la colonna chiave primaria della tabella del database che corrisponde a questa classe. Per impostazione predefinita, EF Core interpreta una proprietà denominata ID o classnameID come chiave primaria. Il nome alternativo riconosciuto automaticamente per la chiave primaria della classe Student è quindi StudentID. Per altre informazioni, vedere EF Core - Chiavi.

La proprietà Enrollments rappresenta una proprietà di navigazione. Le proprietà di navigazione contengono altre entità correlate a questa entità. In questo caso la proprietà Enrollments di un'entità Student contiene tutte le entità Enrollment correlate a tale studente. Ad esempio, se una riga Student nel database presenta due righe Enrollment correlate, la proprietà di navigazione Enrollments contiene questi due entità Enrollment.

Nel database una riga Enrollment è correlata a una riga Student se la relativa StudentID colonna contiene il valore ID dello studente. Si supponga, ad esempio, che una riga Student abbia ID=1. Le righe di registrazione correlate avranno StudentID = 1. StudentID è una chiave esterna nella tabella Enrollment.

La proprietà Enrollments è definita come ICollection<Enrollment> perché potrebbero essere presenti più entità Enrollment correlate. È possibile usare altri tipi di raccolta, ad esempio List<Enrollment> o HashSet<Enrollment>. Quando ICollection<Enrollment> viene utilizzato, EF Core crea una HashSet<Enrollment> raccolta per impostazione predefinita.

Entità Enrollment (Iscrizione)

Enrollment entity diagram

Creare Models/Enrollment.cs con il codice seguente:

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

La proprietà EnrollmentID è la chiave primaria. Questa entità usa il modello classnameID anziché ID da solo. Per un modello di dati di produzione, molti sviluppatori scelgono un modello e lo usano in modo coerente. Questa esercitazione usa entrambi solo per illustrare che entrambi funzionano. L'uso di ID senza classname rende più semplice l'implementazione di alcuni tipi di modifiche al modello di dati.

La proprietà Grade è un oggettoenum. Il punto interrogativo dopo la dichiarazione del tipo Grade indica che la proprietà Gradeammette i valori Null. Un grado null è diverso da un grado zero. Null significa che un voto non è noto o non è ancora stato assegnato.

La proprietà StudentID è una chiave esterna e la proprietà di navigazione corrispondente è Student. Un'entità Enrollment è associata a un'entità Student, pertanto la proprietà contiene un'unica entità Student.

La proprietà CourseID è una chiave esterna e la proprietà di navigazione corrispondente è Course. Un'entità Enrollment è associata a un'entità Course.

EF Core interpreta una proprietà come chiave esterna se è denominata <navigation property name><primary key property name>. Ad esempio, StudentID è la chiave esterna per la proprietà di navigazione Student, dato che la chiave primaria Student dell'entità è ID. Le proprietà di chiave esterna possono anche essere denominate <primary key property name>. Ad esempio, CourseID poiché la chiave primaria Course dell'entità è CourseID.

Entità Course (Corso)

Course entity diagram

Creare Models/Course.cs con il codice seguente:

using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

La proprietà Enrollments rappresenta una proprietà di navigazione. È possibile correlare un'entità Course a un numero qualsiasi di entità Enrollment.

L'attributo DatabaseGenerated consente all'app di specificare la chiave primaria anziché chiedere al database di generarla.

Compilazione dell'app. Il compilatore genera diversi avvisi sulla modalità null di gestione dei valori. Per altre informazioni, vedere questo problema di GitHub, i tipi di riferimento Nullable e Esercitazione: Esprimere più chiaramente la finalità di progettazione con tipi riferimento nullable e non nullable.

Per eliminare gli avvisi dai tipi riferimento nullable, rimuovere la riga seguente dal ContosoUniversity.csproj file:

<Nullable>enable</Nullable>

Il motore di scaffolding attualmente non supporta tipi riferimento nullable, pertanto i modelli usati nello scaffolding non possono neanche.

Rimuovere l'annotazione del ? tipo riferimento nullable da public string? RequestId { get; set; } in Pages/Error.cshtml.cs in modo che il progetto venga compilato senza avvisi del compilatore.

Scaffolding delle pagine Student

In questa sezione viene usato lo strumento di scaffolding ASP.NET Core per generare:

  • Classe EF CoreDbContext. Il contesto è la classe principale che coordina le funzionalità di Entity Framework per un determinato modello di dati. Deriva dalla classe Microsoft.EntityFrameworkCore.DbContext.
  • Razor pagine che gestiscono le operazioni Create, Read, Update e Delete (CRUD) per l'entità Student .
  • Creare una cartella Pages/Students.
  • In Esplora soluzioni fare clic con il pulsante destro del mouse sulla cartella Pages/Students e quindi scegliere Aggiungi>Nuovo elemento di scaffolding.
  • Nella finestra di dialogo Aggiungi nuovo elemento di scaffolding:
    • Nella scheda a sinistra selezionare Pagine comuni >Razor installate >
    • Selezionare Razor Pagine con Entity Framework (CRUD)>ADD.
  • Nella finestra di dialogo Aggiungi Razor pagine con Entity Framework (CRUD):
    • Nell'elenco a discesa Classe modello selezionare Student (ContosoUniversity.Models).
    • Nella riga Classe contesto di dati selezionare il segno più +.
      • Modificare il nome del contesto dati in modo da terminare invece SchoolContext di ContosoUniversityContext. Nome del contesto aggiornato: ContosoUniversity.Data.SchoolContext
      • Selezionare Aggiungi per completare l'aggiunta della classe contesto dati.
      • Selezionare Aggiungi per completare la finestra di dialogo Aggiungi Razor pagine .

Vengono installati automaticamente i pacchetti seguenti:

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.VisualStudio.Web.CodeGeneration.Design

Se il passaggio precedente ha esito negativo, compilare il progetto e ripetere il passaggio di scaffolding.

Il processo di scaffolding:

  • Crea Razor pagine nella cartella Pages/Students :
    • Create.cshtml e Create.cshtml.cs
    • Delete.cshtml e Delete.cshtml.cs
    • Details.cshtml e Details.cshtml.cs
    • Edit.cshtml e Edit.cshtml.cs
    • Index.cshtml e Index.cshtml.cs
  • Crea Data/SchoolContext.cs.
  • Aggiunge il contesto all'inserimento delle dipendenze in Program.cs.
  • Aggiunge un stringa di connessione di database a appsettings.json.

Stringa di connessione al database

Lo strumento di scaffolding genera un stringa di connessione nel appsettings.json file.

L'stringa di connessione specifica sql server Local DB:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext-0e9;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

Local DB è una versione leggera del motore di database di SQL Server Express appositamente pensato per lo sviluppo di app e non per la produzione. Per impostazione predefinita, Local DB crea file con estensione mdf nella directory C:/Users/<user>.

Aggiornare la classe del contesto di database

La classe principale che coordina la EF Core funzionalità per un determinato modello di dati è la classe di contesto del database. Il contesto è derivato da Microsoft.EntityFrameworkCore.DbContext. Il contesto specifica le entità incluse nel modello di dati. In questo progetto la classe è denominata SchoolContext.

Eseguire l'aggiornamento Data/SchoolContext.cs con il codice seguente:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

Il codice precedente passa dal singolare DbSet<Student> Student al plurale DbSet<Student> Students. Per fare in modo che il Razor codice pages corrisponda al nuovo DBSet nome, apportare una modifica globale da: _context.Student. A: _context.Students.

Sono presenti 8 occorrenze.

Poiché un set di entità contiene più entità, molti sviluppatori preferiscono che i nomi delle DBSet proprietà siano plurali.

Il codice evidenziato:

  • Crea una DbSet<TEntity> proprietà per ogni set di entità. Nella EF Core terminologia:
    • Un set di entità corrisponde in genere alla tabella di un database.
    • Un'entità corrisponde a una riga nella tabella.
  • Chiama OnModelCreating. OnModelCreating:
    • Viene chiamato quando SchoolContext è stato inizializzato, ma prima che il modello sia stato bloccato e usato per inizializzare il contesto.
    • È obbligatorio perché più avanti nell'esercitazione l'entità Student avrà riferimenti alle altre entità.

Ci auguriamo di risolvere questo problema in una versione futura.

Program.cs

ASP.NET Core viene compilato con l'inserimento di dipendenze. I servizi, ad esempio , vengono registrati con l'inserimento SchoolContext delle dipendenze durante l'avvio dell'app. I componenti che richiedono questi servizi, ad esempio Razor Pages, vengono forniti tramite parametri del costruttore. Più avanti nell'esercitazione viene illustrato il codice del costruttore che ottiene un'istanza del contesto di database.

Lo strumento di scaffolding ha registrato automaticamente la classe di contesto nel contenitore di inserimento delle dipendenze.

Le righe evidenziate seguenti sono state aggiunte dallo scaffolder:

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

Il nome della stringa di connessione viene passato al contesto chiamando un metodo in un oggetto DbContextOptions. Per lo sviluppo locale, il sistema di configurazione ASP.NET Core legge il stringa di connessione dal appsettings.json file o appsettings.Development.json .

Aggiungere il filtro eccezioni del database

Aggiungere AddDatabaseDeveloperPageExceptionFilter e UseMigrationsEndPoint come illustrato nel codice seguente:

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseMigrationsEndPoint();
}

Aggiungere il pacchetto NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore .

Nella console di Gestione pacchetti immettere quanto segue per aggiungere il pacchetto NuGet:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

Il Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pacchetto NuGet fornisce ASP.NET middleware Core per le pagine di errore di Entity Framework Core. Questo middleware consente di rilevare e diagnosticare gli errori con le migrazioni di Entity Framework Core.

fornisce AddDatabaseDeveloperPageExceptionFilter informazioni utili sull'errore nell'ambientedi sviluppo per gli errori delle migrazioni di Entity Framework.

Creare il database

Aggiornare Program.cs per creare il database, se non esiste:

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseMigrationsEndPoint();
}

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<SchoolContext>();
    context.Database.EnsureCreated();
    // DbInitializer.Initialize(context);
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Il EnsureCreated metodo non esegue alcuna azione se esiste un database per il contesto. Se non esiste alcun database, vengono creati il database e lo schema. EnsureCreated abilita il flusso di lavoro seguente per la gestione delle modifiche al modello di dati:

  • Eliminare il database. Tutti i dati esistenti andranno perduti.
  • Modificare il modello di dati. Ad esempio, aggiungere un campo EmailAddress.
  • Eseguire l'app.
  • EnsureCreated crea un database con il nuovo schema.

Questo flusso di lavoro funziona all'inizio dello sviluppo quando lo schema è in rapida evoluzione, purché i dati non debbano essere mantenuti. La situazione è diversa quando è necessario mantenere i dati immessi nel database. In tal caso, usare le migrazioni.

Più avanti nella serie di esercitazioni viene eliminato il database creato da EnsureCreated e vengono usate le migrazioni. Non è possibile aggiornare un database creato da EnsureCreated usando le migrazioni.

Testare l'app

  • Eseguire l'app.
  • Selezionare il collegamento Students (Studenti) e quindi Crea nuovo.
  • Eseguire il test dei collegamenti Edit (Modifica), Details (Dettagli) e Delete (Elimina).

Specificare il valore di inizializzazione del database

Il metodo EnsureCreated crea un database vuoto. In questa sezione viene aggiunto il codice che popola il database con dati di test.

Creare Data/DbInitializer.cs con il codice seguente:

using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

Il codice controlla se esistono studenti nel database. Se non sono presenti studenti, aggiunge i dati di test al database. I dati di test vengono creati in matrici anziché in raccolte List<T> per ottimizzare le prestazioni.

  • In Program.csrimuovere // dalla DbInitializer.Initialize riga:
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<SchoolContext>();
    context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
}
  • Arrestare l'app se è in esecuzione ed eseguire il comando seguente nella console di Gestione pacchetti:

    Drop-Database -Confirm
    
    
  • Rispondere con Y per eliminare il database.

  • Riavviare l'app.
  • Selezionare la pagina Students per visualizzare i dati di seeding.

Visualizzare il database

  • Aprire Esplora oggetti di SQL Server dal menu Visualizza in Visual Studio.
  • In SSOX selezionare (localdb)\MSSQL Local DB > Databases > SchoolContext-{GUID}. Il nome del database viene generato dal nome del contesto specificato in precedenza più un trattino e un GUID.
  • Espandere il nodo Tabelle.
  • Fare clic con il pulsante destro del mouse sulla tabella Student (Studente) e fare clic su Visualizza dati per visualizzare le colonne create e le righe inserite nella tabella.
  • Fare clic con il pulsante destro del mouse sulla tabella Student e scegliere Visualizza codice per vedere il mapping tra il modello Student e lo schema della tabella Student.

Metodi ef asincroni nelle app Web di ASP.NET Core

La programmazione asincrona è la modalità predefinita per ASP.NET Core e EF Core.

Per un server Web è disponibile un numero limitato di thread e in situazioni di carico elevato tutti i thread disponibili potrebbero essere in uso. In queste circostanze il server non può elaborare nuove richieste finché i thread non saranno liberi. Con il codice sincrono, molti thread possono essere collegati mentre non stanno eseguendo operazioni perché sono in attesa del completamento dell'I/O. Con il codice asincrono, se un processo è in attesa del completamento dell'operazione I/O, il thread viene liberato e il server lo può usare per l'elaborazione di altre richieste. Di conseguenza, il codice asincrono consente un uso più efficiente delle risorse del server e il server può gestire una maggiore quantità di traffico senza ritardi.

Il codice asincrono comporta un minimo sovraccarico in fase di esecuzione. In caso di traffico ridotto, il calo delle prestazioni è irrilevante, mentre nelle situazioni di traffico elevato, è essenziale ottimizzare le prestazioni potenziali.

Nel codice seguente la parola chiave async, il valore restituito Task, la parola chiave await e il metodo ToListAsync consentono di eseguire il codice in modo asincrono.

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • La async parola chiave indica al compilatore di:
    • Generare callback per parti del corpo del metodo.
    • Creare l'oggetto Task restituito.
  • Il tipo restituito Task rappresenta il lavoro in corso.
  • La parola chiave await indica al compilatore di suddividere il metodo in due parti. La prima parte termina con l'operazione avviata in modo asincrono. La seconda parte viene inserita in un metodo di callback che viene chiamato al termine dell'operazione.
  • ToListAsync è la versione asincrona del metodo di estensione ToList.

Alcuni aspetti da tenere presenti durante la scrittura di codice asincrono che usa EF Core:

  • Solo le istruzioni che generano query o comandi da inviare al database vengono eseguite in modo asincrono, Sono incluse ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync e SaveChangesAsync. Non sono incluse le istruzioni che modificano solo un oggetto IQueryable, ad esempio var students = context.Students.Where(s => s.LastName == "Davolio").
  • Un EF Core contesto non è thread-safe: non provare a eseguire più operazioni in parallelo.
  • Per sfruttare i vantaggi delle prestazioni del codice asincrono, verificare che i pacchetti di libreria (ad esempio per il paging) usino asincrona se chiamano EF Core metodi che inviano query al database.

Per altre informazioni sulla programmazione asincrona in .NET, vedere Panoramica della programmazione asincrona e Programmazione asincrona con async e await.

Avviso

L'implementazione asincrona di Microsoft.Data.SqlClient presenta alcuni problemi noti (#593, #601 e altri). Se si verificano problemi di prestazioni imprevisti, provare a usare l'esecuzione del comando di sincronizzazione, soprattutto quando si usano valori binari o di testo di grandi dimensioni.

Considerazioni sulle prestazioni

In generale, una pagina Web non deve caricare un numero arbitrario di righe. Una query deve usare il paging o un approccio di limitazione. Ad esempio, la query precedente può usare Take per limitare le righe restituite:

public async Task OnGetAsync()
{
    Student = await _context.Students.Take(10).ToListAsync();
}

L'enumerazione di una tabella di grandi dimensioni in una vista potrebbe restituire una risposta HTTP 200 parzialmente costruita se si verifica un'eccezione di database in modo parziale tramite l'enumerazione .

Il paging viene illustrato più avanti nell'esercitazione.

Per altre informazioni, vedere Considerazioni sulle prestazioni.For more information, see Performance considerations (EF).

Passaggi successivi

Usare SQLite per lo sviluppo, SQL Server per la produzione

Questa è la prima di una serie di esercitazioni che illustrano come usare Entity Framework (EF) Core in un'app ASP.NET Core Razor Pages . Con queste esercitazioni viene creato un sito Web per una fittizia Contoso University. Il sito include funzionalità, come ad esempio l'ammissione di studenti, la creazione di corsi e le assegnazioni degli insegnati. L'esercitazione usa l'approccio code first. Per informazioni su come seguire questa esercitazione usando il primo approccio al database, vedere questo problema di GitHub.

Scaricare o visualizzare l'app completata.Istruzioni per il download.

Prerequisiti

Motori di database

Le istruzioni per Visual Studio usano SQL Server Local DB, una versione di SQL Server Express eseguita solo in Windows.

Risoluzione dei problemi

Se si verifica un problema che non è possibile risolvere, confrontare il codice con il progetto completato. Un buon modo per ottenere assistenza consiste nel pubblicare una domanda per StackOverflow.com, usando il tag ASP.NET Core o il EF Core tag .

App di esempio

L'applicazione compilata in queste esercitazioni è il sito Web di base di un'università. Gli utenti possono visualizzare e aggiornare le informazioni che riguardano studenti, corsi e insegnanti. Di seguito sono disponibili alcune schermate dell'esercitazione.

Students Index page

Students Edit page

Lo stile dell'interfaccia utente del sito è basato sui modelli di progetto predefiniti. L'obiettivo dell'esercitazione è come usare EF Core con ASP.NET Core, non su come personalizzare l'interfaccia utente.

Facoltativo: compilare il download dell'esempio

Questo passaggio è facoltativo. La compilazione dell'app completata è consigliata quando si verificano problemi che non è possibile risolvere. Se si verifica un problema che non è possibile risolvere, confrontare il codice con il progetto completato. Istruzioni per il download.

Selezionare questa opzione ContosoUniversity.csproj per aprire il progetto.

  • Compilare il progetto.
  • Nella console di Gestione pacchetti eseguire il comando seguente:
Update-Database

Eseguire il progetto per il seeding del database.

Creare il progetto di app Web

  1. Avviare Visual Studio e selezionare Crea un nuovo progetto.
  2. Nella finestra di dialogo Crea un nuovo progetto selezionare ASP.NET Applicazione>Web principale Avanti.
  3. Nella finestra di dialogo Configura il nuovo progetto immettere ContosoUniversity per Nome progetto. È importante usare questo nome esatto, inclusa la combinazione di maiuscole e minuscole, quindi ogni namespace corrispondenza viene eseguita quando viene copiato il codice.
  4. Seleziona Crea.
  5. Nella finestra di dialogo Crea una nuova applicazione Web ASP.NET Core selezionare:
    1. .NET Core e ASP.NET Core 5.0 negli elenchi a discesa.
    2. ASP.NET'app Web core.
    3. CreaNew ASP.NET Core Project dialog

Impostare lo stile del sito

Copiare e incollare il codice seguente nel Pages/Shared/_Layout.cshtml file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

Il file di layout imposta l'intestazione, il piè di pagina e il menu del sito. Il codice precedente apporta le modifiche seguenti:

  • Ogni occorrenza di "ContosoUniversity" in "Contoso University". Le occorrenze sono tre.
  • Le voci di Home menu e Privacy vengono eliminate.
  • Le voci vengono aggiunte per About, Students, Courses, Instructors e Departments.

In Pages/Index.cshtmlsostituire il contenuto del file con il codice seguente:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
                </p>
            </div>
        </div>
    </div>
</div>

Il codice precedente sostituisce il testo relativo a ASP.NET Core con testo su questa app.

Eseguire l'app per verificare che venga visualizzata la home page.

Modello di dati

Nelle sezioni seguenti viene descritto come creare un modello di dati:

Course-Enrollment-Student data model diagram

Uno studente può iscriversi a un numero qualsiasi di corsi e un corso può avere un numero qualsiasi di studenti iscritti.

Entità Student (Studente)

Student entity diagram

  • Creare una cartella Models nella cartella del progetto.

  • Creare Models/Student.cs con il codice seguente:

    using System;
    using System.Collections.Generic;
    
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

La proprietà ID diventa la colonna chiave primaria della tabella del database che corrisponde a questa classe. Per impostazione predefinita, EF Core interpreta una proprietà denominata ID o classnameID come chiave primaria. Il nome alternativo riconosciuto automaticamente per la chiave primaria della classe Student è quindi StudentID. Per altre informazioni, vedere EF Core - Chiavi.

La proprietà Enrollments rappresenta una proprietà di navigazione. Le proprietà di navigazione contengono altre entità correlate a questa entità. In questo caso la proprietà Enrollments di un'entità Student contiene tutte le entità Enrollment correlate a tale studente. Ad esempio, se una riga Student nel database presenta due righe Enrollment correlate, la proprietà di navigazione Enrollments contiene questi due entità Enrollment.

Nel database una riga Enrollment è correlata a una riga Student se la relativa StudentID colonna contiene il valore ID dello studente. Si supponga, ad esempio, che una riga Student abbia ID=1. Le righe di registrazione correlate avranno StudentID = 1. StudentID è una chiave esterna nella tabella Enrollment.

La proprietà Enrollments è definita come ICollection<Enrollment> perché potrebbero essere presenti più entità Enrollment correlate. È possibile usare altri tipi di raccolta, ad esempio List<Enrollment> o HashSet<Enrollment>. Quando ICollection<Enrollment> viene utilizzato, EF Core crea una HashSet<Enrollment> raccolta per impostazione predefinita.

Entità Enrollment (Iscrizione)

Enrollment entity diagram

Creare Models/Enrollment.cs con il codice seguente:

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

La proprietà EnrollmentID è la chiave primaria. Questa entità usa il modello classnameID anziché ID da solo. Per un modello di dati di produzione, molti sviluppatori scelgono un modello e lo usano in modo coerente. Questa esercitazione usa entrambi solo per illustrare che entrambi funzionano. L'uso di ID senza classname rende più semplice l'implementazione di alcuni tipi di modifiche al modello di dati.

La proprietà Grade è un oggettoenum. Il punto interrogativo dopo la dichiarazione del tipo Grade indica che la proprietà Gradeammette i valori Null. Un grado null è diverso da un grado zero. Null significa che un voto non è noto o non è ancora stato assegnato.

La proprietà StudentID è una chiave esterna e la proprietà di navigazione corrispondente è Student. Un'entità Enrollment è associata a un'entità Student, pertanto la proprietà contiene un'unica entità Student.

La proprietà CourseID è una chiave esterna e la proprietà di navigazione corrispondente è Course. Un'entità Enrollment è associata a un'entità Course.

EF Core interpreta una proprietà come chiave esterna se è denominata <navigation property name><primary key property name>. Ad esempio, StudentID è la chiave esterna per la proprietà di navigazione Student, dato che la chiave primaria Student dell'entità è ID. Le proprietà di chiave esterna possono anche essere denominate <primary key property name>. Ad esempio, CourseID poiché la chiave primaria Course dell'entità è CourseID.

Entità Course (Corso)

Course entity diagram

Creare Models/Course.cs con il codice seguente:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

La proprietà Enrollments rappresenta una proprietà di navigazione. È possibile correlare un'entità Course a un numero qualsiasi di entità Enrollment.

L'attributo DatabaseGenerated consente all'app di specificare la chiave primaria anziché chiedere al database di generarla.

Compilare il progetto per verificare che non siano presenti errori del compilatore.

Scaffolding delle pagine Student

In questa sezione viene usato lo strumento di scaffolding ASP.NET Core per generare:

  • Classe EF CoreDbContext. Il contesto è la classe principale che coordina le funzionalità di Entity Framework per un determinato modello di dati. Deriva dalla classe Microsoft.EntityFrameworkCore.DbContext.
  • Razor pagine che gestiscono le operazioni Create, Read, Update e Delete (CRUD) per l'entità Student .
  • Creare una cartella Pages/Students.
  • In Esplora soluzioni fare clic con il pulsante destro del mouse sulla cartella Pages/Students e quindi scegliere Aggiungi>Nuovo elemento di scaffolding.
  • Nella finestra di dialogo Aggiungi nuovo elemento di scaffolding:
    • Nella scheda a sinistra selezionare Pagine comuni >Razor installate >
    • Selezionare Razor Pagine con Entity Framework (CRUD)>ADD.
  • Nella finestra di dialogo Aggiungi Razor pagine con Entity Framework (CRUD):
    • Nell'elenco a discesa Classe modello selezionare Student (ContosoUniversity.Models).
    • Nella riga Classe contesto di dati selezionare il segno più +.
      • Modificare il nome del contesto dati in modo da terminare invece SchoolContext di ContosoUniversityContext. Nome del contesto aggiornato: ContosoUniversity.Data.SchoolContext
      • Selezionare Aggiungi per completare l'aggiunta della classe contesto dati.
      • Selezionare Aggiungi per completare la finestra di dialogo Aggiungi Razor pagine .

Se lo scaffolding non riesce con l'errore 'Install the package Microsoft.VisualStudio.Web.CodeGeneration.Design and try again.', eseguire di nuovo lo strumento di scaffolding o vedere questo problema di GitHub.

Vengono installati automaticamente i pacchetti seguenti:

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.VisualStudio.Web.CodeGeneration.Design

Se il passaggio precedente ha esito negativo, compilare il progetto e ripetere il passaggio di scaffolding.

Il processo di scaffolding:

  • Crea Razor pagine nella cartella Pages/Students :
    • Create.cshtml e Create.cshtml.cs
    • Delete.cshtml e Delete.cshtml.cs
    • Details.cshtml e Details.cshtml.cs
    • Edit.cshtml e Edit.cshtml.cs
    • Index.cshtml e Index.cshtml.cs
  • Crea Data/SchoolContext.cs.
  • Aggiunge il contesto all'inserimento delle dipendenze in Startup.cs.
  • Aggiunge un stringa di connessione di database a appsettings.json.

Stringa di connessione al database

Lo strumento di scaffolding genera un stringa di connessione nel appsettings.json file.

L'stringa di connessione specifica sql server Local DB:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=CU-1;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

Local DB è una versione leggera del motore di database di SQL Server Express appositamente pensato per lo sviluppo di app e non per la produzione. Per impostazione predefinita, Local DB crea file con estensione mdf nella directory C:/Users/<user>.

Aggiornare la classe del contesto di database

La classe principale che coordina la EF Core funzionalità per un determinato modello di dati è la classe di contesto del database. Il contesto è derivato da Microsoft.EntityFrameworkCore.DbContext. Il contesto specifica le entità incluse nel modello di dati. In questo progetto la classe è denominata SchoolContext.

Eseguire l'aggiornamento Data/SchoolContext.cs con il codice seguente:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

Il codice precedente passa dal singolare DbSet<Student> Student al plurale DbSet<Student> Students. Per fare in modo che il Razor codice pages corrisponda al nuovo DBSet nome, apportare una modifica globale da: _context.Student. A: _context.Students.

Sono presenti 8 occorrenze.

Poiché un set di entità contiene più entità, molti sviluppatori preferiscono che i nomi delle DBSet proprietà siano plurali.

Il codice evidenziato:

  • Crea una DbSet<TEntity> proprietà per ogni set di entità. Nella EF Core terminologia:
    • Un set di entità corrisponde in genere alla tabella di un database.
    • Un'entità corrisponde a una riga nella tabella.
  • Chiama OnModelCreating. OnModelCreating:
    • Viene chiamato quando SchoolContext è stato inizializzato, ma prima che il modello sia stato bloccato e usato per inizializzare il contesto.
    • È obbligatorio perché più avanti nell'esercitazione l'entità Student avrà riferimenti alle altre entità.

Compilare il progetto per verificare che non siano presenti errori di compilazione.

Startup.cs

ASP.NET Core viene compilato con l'inserimento di dipendenze. I servizi, ad esempio , vengono registrati con l'inserimento SchoolContext delle dipendenze durante l'avvio dell'app. I componenti che richiedono questi servizi, ad esempio Razor Pages, vengono forniti tramite parametri del costruttore. Più avanti nell'esercitazione viene illustrato il codice del costruttore che ottiene un'istanza del contesto di database.

Lo strumento di scaffolding ha registrato automaticamente la classe di contesto nel contenitore di inserimento delle dipendenze.

Le righe evidenziate seguenti sono state aggiunte dallo scaffolder:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
}

Il nome della stringa di connessione viene passato al contesto chiamando un metodo in un oggetto DbContextOptions. Per lo sviluppo locale, il sistema di configurazione ASP.NET Core legge il stringa di connessione dal appsettings.json file.

Aggiungere il filtro eccezioni del database

Aggiungere AddDatabaseDeveloperPageExceptionFilter e UseMigrationsEndPoint come illustrato nel codice seguente:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
       options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));

    services.AddDatabaseDeveloperPageExceptionFilter();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseMigrationsEndPoint();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Aggiungere il pacchetto NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore .

Nella console di Gestione pacchetti immettere quanto segue per aggiungere il pacchetto NuGet:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

Il Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pacchetto NuGet fornisce ASP.NET middleware Core per le pagine di errore di Entity Framework Core. Questo middleware consente di rilevare e diagnosticare gli errori con le migrazioni di Entity Framework Core.

fornisce AddDatabaseDeveloperPageExceptionFilter informazioni utili sull'errore nell'ambientedi sviluppo per gli errori delle migrazioni di Entity Framework.

Creare il database

Aggiornare Program.cs per creare il database, se non esiste:

using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            CreateDbIfNotExists(host);

            host.Run();
        }

        private static void CreateDbIfNotExists(IHost host)
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    context.Database.EnsureCreated();
                    // DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

Il EnsureCreated metodo non esegue alcuna azione se esiste un database per il contesto. Se non esiste alcun database, vengono creati il database e lo schema. EnsureCreated abilita il flusso di lavoro seguente per la gestione delle modifiche al modello di dati:

  • Eliminare il database. Tutti i dati esistenti andranno perduti.
  • Modificare il modello di dati. Ad esempio, aggiungere un campo EmailAddress.
  • Eseguire l'app.
  • EnsureCreated crea un database con il nuovo schema.

Questo flusso di lavoro funziona all'inizio dello sviluppo quando lo schema è in rapida evoluzione, purché i dati non debbano essere mantenuti. La situazione è diversa quando è necessario mantenere i dati immessi nel database. In tal caso, usare le migrazioni.

Più avanti nella serie di esercitazioni viene eliminato il database creato da EnsureCreated e vengono usate le migrazioni. Non è possibile aggiornare un database creato da EnsureCreated usando le migrazioni.

Testare l'app

  • Eseguire l'app.
  • Selezionare il collegamento Students (Studenti) e quindi Crea nuovo.
  • Eseguire il test dei collegamenti Edit (Modifica), Details (Dettagli) e Delete (Elimina).

Specificare il valore di inizializzazione del database

Il metodo EnsureCreated crea un database vuoto. In questa sezione viene aggiunto il codice che popola il database con dati di test.

Creare Data/DbInitializer.cs con il codice seguente:

using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

Il codice controlla se esistono studenti nel database. Se non sono presenti studenti, aggiunge i dati di test al database. I dati di test vengono creati in matrici anziché in raccolte List<T> per ottimizzare le prestazioni.

  • In Program.csrimuovere // dalla DbInitializer.Initialize riga:

      context.Database.EnsureCreated();
      DbInitializer.Initialize(context);
    
  • Arrestare l'app se è in esecuzione ed eseguire il comando seguente nella console di Gestione pacchetti:

    Drop-Database -Confirm
    
    
  • Rispondere con Y per eliminare il database.

  • Riavviare l'app.
  • Selezionare la pagina Students per visualizzare i dati di seeding.

Visualizzare il database

  • Aprire Esplora oggetti di SQL Server dal menu Visualizza in Visual Studio.
  • In SSOX selezionare (localdb)\MSSQL Local DB > Databases > SchoolContext-{GUID}. Il nome del database viene generato dal nome del contesto specificato in precedenza più un trattino e un GUID.
  • Espandere il nodo Tabelle.
  • Fare clic con il pulsante destro del mouse sulla tabella Student (Studente) e fare clic su Visualizza dati per visualizzare le colonne create e le righe inserite nella tabella.
  • Fare clic con il pulsante destro del mouse sulla tabella Student e scegliere Visualizza codice per vedere il mapping tra il modello Student e lo schema della tabella Student.

Codice asincrono

La programmazione asincrona è la modalità predefinita per ASP.NET Core e EF Core.

Per un server Web è disponibile un numero limitato di thread e in situazioni di carico elevato tutti i thread disponibili potrebbero essere in uso. In queste circostanze il server non può elaborare nuove richieste finché i thread non saranno liberi. Con il codice sincrono, molti thread possono essere collegati mentre non stanno eseguendo operazioni perché sono in attesa del completamento dell'I/O. Con il codice asincrono, se un processo è in attesa del completamento dell'operazione I/O, il thread viene liberato e il server lo può usare per l'elaborazione di altre richieste. Di conseguenza, il codice asincrono consente un uso più efficiente delle risorse del server e il server può gestire una maggiore quantità di traffico senza ritardi.

Il codice asincrono comporta un minimo sovraccarico in fase di esecuzione. In caso di traffico ridotto, il calo delle prestazioni è irrilevante, mentre nelle situazioni di traffico elevato, è essenziale ottimizzare le prestazioni potenziali.

Nel codice seguente la parola chiave async, il valore restituito Task, la parola chiave await e il metodo ToListAsync consentono di eseguire il codice in modo asincrono.

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • La async parola chiave indica al compilatore di:
    • Generare callback per parti del corpo del metodo.
    • Creare l'oggetto Task restituito.
  • Il tipo restituito Task rappresenta il lavoro in corso.
  • La parola chiave await indica al compilatore di suddividere il metodo in due parti. La prima parte termina con l'operazione avviata in modo asincrono. La seconda parte viene inserita in un metodo di callback che viene chiamato al termine dell'operazione.
  • ToListAsync è la versione asincrona del metodo di estensione ToList.

Alcuni aspetti da tenere presenti durante la scrittura di codice asincrono che usa EF Core:

  • Solo le istruzioni che generano query o comandi da inviare al database vengono eseguite in modo asincrono, Sono incluse ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync e SaveChangesAsync. Non sono incluse le istruzioni che modificano solo un oggetto IQueryable, ad esempio var students = context.Students.Where(s => s.LastName == "Davolio").
  • Un EF Core contesto non è thread-safe: non provare a eseguire più operazioni in parallelo.
  • Per sfruttare i vantaggi delle prestazioni del codice asincrono, verificare che i pacchetti di libreria (ad esempio per il paging) usino asincrona se chiamano EF Core metodi che inviano query al database.

Per altre informazioni sulla programmazione asincrona in .NET, vedere Panoramica della programmazione asincrona e Programmazione asincrona con async e await.

Considerazioni sulle prestazioni

In generale, una pagina Web non deve caricare un numero arbitrario di righe. Una query deve usare il paging o un approccio di limitazione. Ad esempio, la query precedente può usare Take per limitare le righe restituite:

public async Task OnGetAsync()
{
    Student = await _context.Students.Take(10).ToListAsync();
}

L'enumerazione di una tabella di grandi dimensioni in una vista potrebbe restituire una risposta HTTP 200 parzialmente costruita se si verifica un'eccezione di database in modo parziale tramite l'enumerazione .

MaxModelBindingCollectionSize il valore predefinito è 1024. Il codice seguente imposta MaxModelBindingCollectionSize:

public void ConfigureServices(IServiceCollection services)
{
    var myMaxModelBindingCollectionSize = Convert.ToInt32(
                Configuration["MyMaxModelBindingCollectionSize"] ?? "100");

    services.Configure<MvcOptions>(options =>
           options.MaxModelBindingCollectionSize = myMaxModelBindingCollectionSize);

    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
          options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));

    services.AddDatabaseDeveloperPageExceptionFilter();
}

Vedere Configurazione per informazioni sulle impostazioni di configurazione, ad esempio MyMaxModelBindingCollectionSize.

Il paging viene illustrato più avanti nell'esercitazione.

Per altre informazioni, vedere Considerazioni sulle prestazioni.For more information, see Performance considerations (EF).

Registrazione SQL di Entity Framework Core

La configurazione di registrazione viene comunemente fornita dalla sezione Logging dei file appsettings.{Environment}.json. Per registrare istruzioni SQL, aggiungere "Microsoft.EntityFrameworkCore.Database.Command": "Information" al appsettings.Development.json file:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyDB-2;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
     ,"Microsoft.EntityFrameworkCore.Database.Command": "Information"
    }
  },
  "AllowedHosts": "*"
}

Con l'opzione ON precedente JS, le istruzioni SQL vengono visualizzate nella riga di comando e nella finestra di output di Visual Studio.

Per altre informazioni, vedere Registrazione in .NET Core e ASP.NET Core e questo problema di GitHub.

Passaggi successivi

Usare SQLite per lo sviluppo, SQL Server per la produzione

Questa è la prima di una serie di esercitazioni che illustrano come usare Entity Framework (EF) Core in un'app ASP.NET Core Razor Pages . Con queste esercitazioni viene creato un sito Web per una fittizia Contoso University. Il sito include funzionalità, come ad esempio l'ammissione di studenti, la creazione di corsi e le assegnazioni degli insegnati. L'esercitazione usa l'approccio code first. Per informazioni su come seguire questa esercitazione usando il primo approccio al database, vedere questo problema di GitHub.

Scaricare o visualizzare l'app completata.Istruzioni per il download.

Prerequisiti

Motori di database

Le istruzioni per Visual Studio usano SQL Server Local DB, una versione di SQL Server Express eseguita solo in Windows.

Le istruzioni per Visual Studio Code usano SQLite, un motore di database multipiattaforma.

Se si sceglie di usare SQLite, scaricare e installare uno strumento di terze parti per la gestione e la visualizzazione di un database SQLite, ad esempio DB Browser per SQLite.

Risoluzione dei problemi

Se si verifica un problema che non è possibile risolvere, confrontare il codice con il progetto completato. Un buon modo per ottenere assistenza consiste nel pubblicare una domanda per StackOverflow.com, usando il tag ASP.NET Core o il EF Core tag .

App di esempio

L'applicazione compilata in queste esercitazioni è il sito Web di base di un'università. Gli utenti possono visualizzare e aggiornare le informazioni che riguardano studenti, corsi e insegnanti. Di seguito sono disponibili alcune schermate dell'esercitazione.

Students Index page

Students Edit page

Lo stile dell'interfaccia utente del sito è basato sui modelli di progetto predefiniti. L'obiettivo dell'esercitazione è come usare EF Core, non su come personalizzare l'interfaccia utente.

Seguire il collegamento nella parte superiore della pagina per ottenere il codice sorgente per il progetto completato. La cartella cu30 contiene il codice per la versione di ASP.NET Core 3.0 dell'esercitazione. I file che riflettono lo stato del codice per le esercitazioni 1-7 si trovano nella cartella cu30snapshots.

Per eseguire l'app dopo il download del progetto completato:

  • Compilare il progetto.

  • Nella console di Gestione pacchetti eseguire il comando seguente:

    Update-Database
    
  • Eseguire il progetto per il seeding del database.

Creare il progetto di app Web

  • Dal menu File di Visual Studio selezionare Nuovo>Progetto.
  • Selezionare Applicazione Web ASP.NET Core.
  • Denominare il progetto ContosoUniversity. È importante usare questo nome esatto, incluse le maiuscole, in modo che gli spazi dei nomi corrispondano quando il codice viene copiato e incollato.
  • Selezionare .NET Core e ASP.NET Core 3.0 nell'elenco a discesa, quindi selezionare Applicazione Web.

Impostare lo stile del sito

Configurare l'intestazione, il piè di pagina e il menu del sito aggiornando Pages/Shared/_Layout.cshtml:

  • Modificare tutte le occorrenze di "ContosoUniversity" in "Contoso University". Le occorrenze sono tre.

  • Eliminare le voci di Home menu e Privacy e aggiungere voci per About, Students, Courses, Instructors e Departments.

Le modifiche sono evidenziate.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2019 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

In Pages/Index.cshtmlsostituire il contenuto del file con il codice seguente per sostituire il testo relativo a ASP.NET Core con testo sull'app:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
                </p>
            </div>
        </div>
    </div>
</div>

Eseguire l'app per verificare che venga visualizzata la home page.

Modello di dati

Nelle sezioni seguenti viene descritto come creare un modello di dati:

Course-Enrollment-Student data model diagram

Uno studente può iscriversi a un numero qualsiasi di corsi e un corso può avere un numero qualsiasi di studenti iscritti.

Entità Student (Studente)

Student entity diagram

  • Creare una cartella Models nella cartella del progetto.

  • Creare Models/Student.cs con il codice seguente:

    using System;
    using System.Collections.Generic;
    
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

La proprietà ID diventa la colonna chiave primaria della tabella del database che corrisponde a questa classe. Per impostazione predefinita, EF Core interpreta una proprietà denominata ID o classnameID come chiave primaria. Il nome alternativo riconosciuto automaticamente per la chiave primaria della classe Student è quindi StudentID. Per altre informazioni, vedere EF Core - Chiavi.

La proprietà Enrollments rappresenta una proprietà di navigazione. Le proprietà di navigazione contengono altre entità correlate a questa entità. In questo caso la proprietà Enrollments di un'entità Student contiene tutte le entità Enrollment correlate a tale studente. Ad esempio, se una riga Student nel database presenta due righe Enrollment correlate, la proprietà di navigazione Enrollments contiene questi due entità Enrollment.

Nel database, una riga Enrollment è correlata a una riga Student se la relativa colonna StudentID contiene il valore ID dello studente. Si supponga, ad esempio, che una riga Student abbia ID=1. Le righe Enrollment correlate avranno StudentID = 1. StudentID è una chiave esterna nella tabella Enrollment.

La proprietà Enrollments è definita come ICollection<Enrollment> perché potrebbero essere presenti più entità Enrollment correlate. È possibile usare altri tipi di raccolta, ad esempio List<Enrollment> o HashSet<Enrollment>. Quando ICollection<Enrollment> viene utilizzato, EF Core crea una HashSet<Enrollment> raccolta per impostazione predefinita.

Entità Enrollment (Iscrizione)

Enrollment entity diagram

Creare Models/Enrollment.cs con il codice seguente:

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

La proprietà EnrollmentID è la chiave primaria. Questa entità usa il modello classnameID anziché ID da solo. Per un modello di dati di produzione, scegliere un modello e usarlo in modo coerente. Questa esercitazione usa entrambi solo per illustrare che entrambi funzionano. L'uso di ID senza classname rende più semplice l'implementazione di alcuni tipi di modifiche al modello di dati.

La proprietà Grade è un oggettoenum. Il punto interrogativo dopo la dichiarazione del tipo Grade indica che la proprietà Gradeammette i valori Null. Un grado null è diverso da un grado zero. Null significa che un voto non è noto o non è ancora stato assegnato.

La proprietà StudentID è una chiave esterna e la proprietà di navigazione corrispondente è Student. Un'entità Enrollment è associata a un'entità Student, pertanto la proprietà contiene un'unica entità Student.

La proprietà CourseID è una chiave esterna e la proprietà di navigazione corrispondente è Course. Un'entità Enrollment è associata a un'entità Course.

EF Core interpreta una proprietà come chiave esterna se è denominata <navigation property name><primary key property name>. Ad esempio, StudentID è la chiave esterna per la proprietà di navigazione Student, dato che la chiave primaria Student dell'entità è ID. Le proprietà di chiave esterna possono anche essere denominate <primary key property name>. Ad esempio, CourseID poiché la chiave primaria Course dell'entità è CourseID.

Entità Course (Corso)

Course entity diagram

Creare Models/Course.cs con il codice seguente:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

La proprietà Enrollments rappresenta una proprietà di navigazione. È possibile correlare un'entità Course a un numero qualsiasi di entità Enrollment.

L'attributo DatabaseGenerated consente all'app di specificare la chiave primaria anziché chiedere al database di generarla.

Compilare il progetto per verificare che non siano presenti errori del compilatore.

Scaffolding delle pagine Student

In questa sezione si userà lo strumento di scaffolding di ASP.NET Core per generare:

  • Classe EF Coredi contesto . Il contesto è la classe principale che coordina le funzionalità di Entity Framework per un determinato modello di dati. Deriva dalla classe Microsoft.EntityFrameworkCore.DbContext.
  • Razor pagine che gestiscono le operazioni Create, Read, Update e Delete (CRUD) per l'entità Student .
  • Creare una cartella Students nella cartella Pages.
  • In Esplora soluzioni fare clic con il pulsante destro del mouse sulla cartella Pages/Students e quindi scegliere Aggiungi>Nuovo elemento di scaffolding.
  • Nella finestra di dialogo Aggiungi scaffolding selezionareRazorPagine con Entity Framework (CRUD)>ADD.
  • Nella finestra di dialogo Aggiungi Razor pagine con Entity Framework (CRUD):
    • Nell'elenco a discesa Classe modello selezionare Student (ContosoUniversity.Models).
    • Nella riga Classe contesto di dati selezionare il segno più +.
    • Modificare il nome del contesto di dati da ContosoUniversity.Models.ContosoUniversityContext a ContosoUniversity.Data.SchoolContext.
    • Seleziona Aggiungi.

Vengono installati automaticamente i pacchetti seguenti:

  • Microsoft.VisualStudio.Web.CodeGeneration.Design
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.Extensions.Logging.Debug
  • Microsoft.EntityFrameworkCore.Tools

Se si riscontra un problema con il passaggio precedente, compilare il progetto e riprovare a eseguire il passaggio di scaffolding.

Il processo di scaffolding:

  • Crea Razor pagine nella cartella Pages/Students :
    • Create.cshtml e Create.cshtml.cs
    • Delete.cshtml e Delete.cshtml.cs
    • Details.cshtml e Details.cshtml.cs
    • Edit.cshtml e Edit.cshtml.cs
    • Index.cshtml e Index.cshtml.cs
  • Crea Data/SchoolContext.cs.
  • Aggiunge il contesto all'inserimento delle dipendenze in Startup.cs.
  • Aggiunge un stringa di connessione di database a appsettings.json.

Stringa di connessione al database

Il appsettings.json file specifica il stringa di connessione di SQL Server Local DB.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext6;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

Local DB è una versione leggera del motore di database di SQL Server Express appositamente pensato per lo sviluppo di app e non per la produzione. Per impostazione predefinita, Local DB crea file con estensione mdf nella directory C:/Users/<user>.

Aggiornare la classe del contesto di database

La classe principale che coordina la EF Core funzionalità per un determinato modello di dati è la classe di contesto del database. Il contesto è derivato da Microsoft.EntityFrameworkCore.DbContext. Il contesto specifica le entità incluse nel modello di dati. In questo progetto la classe è denominata SchoolContext.

Eseguire l'aggiornamento Data/SchoolContext.cs con il codice seguente:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

Il codice evidenziato crea una DbSet<TEntity> proprietà per ogni set di entità. Nella EF Core terminologia:

  • Un set di entità corrisponde in genere alla tabella di un database.
  • Un'entità corrisponde a una riga nella tabella.

Dato che un set di entità contiene più entità, le proprietà DBSet devono essere nomi plurali. Dato che lo strumento di scaffolding ha creato un DBSet Student, questo passaggio lo modifica nel plurale Students.

Per fare in modo che il Razor codice Pages corrisponda al nuovo nome DBSet, apportare una modifica globale nell'intero progetto di _context.Student in _context.Students. Sono presenti 8 occorrenze.

Compilare il progetto per verificare che non siano presenti errori di compilazione.

Startup.cs

ASP.NET Core viene compilato con l'inserimento di dipendenze. I servizi ,ad esempio il contesto del EF Core database, vengono registrati con inserimento delle dipendenze durante l'avvio dell'applicazione. I componenti che richiedono questi servizi,ad esempio Razor Pages, vengono forniti tramite parametri del costruttore. Più avanti nell'esercitazione viene illustrato il codice del costruttore che ottiene un'istanza del contesto di database.

Lo strumento di scaffolding ha registrato automaticamente la classe di contesto nel contenitore di inserimento delle dipendenze.

  • In ConfigureServices le righe evidenziate sono state aggiunte dallo scaffolder:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    
        services.AddDbContext<SchoolContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
    }
    

Il nome della stringa di connessione viene passato al contesto chiamando un metodo in un oggetto DbContextOptions. Per lo sviluppo locale, il sistema di configurazione ASP.NET Core legge il stringa di connessione dal appsettings.json file.

Creare il database

Aggiornare Program.cs per creare il database, se non esiste:

using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            CreateDbIfNotExists(host);

            host.Run();
        }

        private static void CreateDbIfNotExists(IHost host)
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    context.Database.EnsureCreated();
                    // DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

Il EnsureCreated metodo non esegue alcuna azione se esiste un database per il contesto. Se non esiste alcun database, vengono creati il database e lo schema. EnsureCreated abilita il flusso di lavoro seguente per la gestione delle modifiche al modello di dati:

  • Eliminare il database. Tutti i dati esistenti andranno perduti.
  • Modificare il modello di dati. Ad esempio, aggiungere un campo EmailAddress.
  • Eseguire l'app.
  • EnsureCreated crea un database con il nuovo schema.

Questo flusso di lavoro funziona correttamente nelle fasi iniziali dello sviluppo quando lo schema è in rapida evoluzione, purché non sia necessario conservare i dati. La situazione è diversa quando è necessario mantenere i dati immessi nel database. In tal caso, usare le migrazioni.

Più avanti nella serie di esercitazioni si vedrà come eliminare il database creato da EnsureCreated e usare invece le migrazioni. Non è possibile aggiornare un database creato da EnsureCreated usando le migrazioni.

Testare l'app

  • Eseguire l'app.
  • Selezionare il collegamento Students (Studenti) e quindi Crea nuovo.
  • Eseguire il test dei collegamenti Edit (Modifica), Details (Dettagli) e Delete (Elimina).

Specificare il valore di inizializzazione del database

Il metodo EnsureCreated crea un database vuoto. In questa sezione viene aggiunto il codice che popola il database con dati di test.

Creare Data/DbInitializer.cs con il codice seguente:

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            context.Database.EnsureCreated();

            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

Il codice controlla se esistono studenti nel database. Se non sono presenti studenti, aggiunge i dati di test al database. I dati di test vengono creati in matrici anziché in raccolte List<T> per ottimizzare le prestazioni.

  • In Program.cssostituire la EnsureCreated chiamata con una DbInitializer.Initialize chiamata:

    // context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
    

Arrestare l'app se è in esecuzione ed eseguire il comando seguente nella console di Gestione pacchetti:

Drop-Database
  • Riavviare l'app.

  • Selezionare la pagina Students per visualizzare i dati di seeding.

Visualizzare il database

  • Aprire Esplora oggetti di SQL Server dal menu Visualizza in Visual Studio.
  • In SSOX selezionare (localdb)\MSSQL Local DB > Databases > SchoolContext-{GUID}. Il nome del database viene generato dal nome del contesto specificato in precedenza con l'aggiunta di un trattino e un GUID.
  • Espandere il nodo Tabelle.
  • Fare clic con il pulsante destro del mouse sulla tabella Student (Studente) e fare clic su Visualizza dati per visualizzare le colonne create e le righe inserite nella tabella.
  • Fare clic con il pulsante destro del mouse sulla tabella Student e scegliere Visualizza codice per vedere il mapping tra il modello Student e lo schema della tabella Student.

Codice asincrono

La programmazione asincrona è la modalità predefinita per ASP.NET Core e EF Core.

Per un server Web è disponibile un numero limitato di thread e in situazioni di carico elevato tutti i thread disponibili potrebbero essere in uso. In queste circostanze il server non può elaborare nuove richieste finché i thread non saranno liberi. Con il codice sincrono, può succedere che molti thread siano vincolati nonostante in quel momento non stiano eseguendo alcuna operazione. Rimangono tuttavia in attesa che l'operazione I/O sia completata. Con il codice asincrono, se un processo è in attesa del completamento dell'operazione I/O, il thread viene liberato e il server lo può usare per l'elaborazione di altre richieste. Di conseguenza, il codice asincrono consente un uso più efficiente delle risorse del server e il server può gestire una maggiore quantità di traffico senza ritardi.

Il codice asincrono comporta un minimo sovraccarico in fase di esecuzione. In caso di traffico ridotto, il calo delle prestazioni è irrilevante, mentre nelle situazioni di traffico elevato, è essenziale ottimizzare le prestazioni potenziali.

Nel codice seguente la parola chiave async, il valore restituito Task<T>, la parola chiave await e il metodo ToListAsync consentono di eseguire il codice in modo asincrono.

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • La async parola chiave indica al compilatore di:
    • Generare callback per parti del corpo del metodo.
    • Creare l'oggetto Task restituito.
  • Il tipo restituito Task<T> rappresenta il lavoro in corso.
  • La parola chiave await indica al compilatore di suddividere il metodo in due parti. La prima parte termina con l'operazione avviata in modo asincrono. La seconda parte viene inserita in un metodo di callback che viene chiamato al termine dell'operazione.
  • ToListAsync è la versione asincrona del metodo di estensione ToList.

Alcuni aspetti da tenere presenti durante la scrittura di codice asincrono che usa EF Core:

  • Solo le istruzioni che generano query o comandi da inviare al database vengono eseguite in modo asincrono, Sono incluse ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync e SaveChangesAsync. Non sono incluse le istruzioni che modificano solo un oggetto IQueryable, ad esempio var students = context.Students.Where(s => s.LastName == "Davolio").
  • Un EF Core contesto non è thread-safe: non provare a eseguire più operazioni in parallelo.
  • Per sfruttare i vantaggi delle prestazioni del codice asincrono, verificare che i pacchetti di libreria (ad esempio per il paging) usino asincrona se chiamano EF Core metodi che inviano query al database.

Per altre informazioni sulla programmazione asincrona in .NET, vedere Panoramica della programmazione asincrona e Programmazione asincrona con async e await.

Passaggi successivi