Esercitazione: Introduzione a EF Core in un'app Web MVC ASP.NET

Di Tom Dykstra e Rick Anderson

Questa esercitazione illustra ASP.NET Core MVC ed Entity Framework Core con i controller e le viste. Razor Pages è un modello di programmazione alternativo. Per nuovi progetti di sviluppo, è consigliabile usare Razor Pages invece di MVC con controller e visualizzazioni. Vedere la versione per Razor Pages di questa esercitazione. Ogni esercitazione illustra alcuni materiali che l'altra versione non contiene:

Alcuni elementi presenti in questa esercitazione su MVC, ma che non sono contenuti nell'esercitazione su Razor Pages:

  • Implementare l'ereditarietà nel modello di dati
  • Eseguire query SQL non elaborate
  • Usare LINQ dinamico per semplificare il codice

Alcuni elementi presenti nell'esercitazione su Razor Pages, ma che non sono contenuti in questa esercitazione:

  • Usare il metodo Select per caricare i dati correlati
  • Procedure consigliate per EF.

L'app Web di esempio Contoso University illustra come creare un'app Web MVC di ASP.NET Core usando Entity Framework (EF) Core e Visual Studio.

L'app di esempio è un sito Web per una fittizia Contoso University. Include funzionalità, come ad esempio l'ammissione di studenti, la creazione di corsi e le assegnazioni di insegnati. Questa è la prima di una serie di esercitazioni che illustrano come compilare l'app di esempio Contoso University.

Prerequisiti

Questa esercitazione non è stata aggiornata per ASP.NET Core 6 o versione successiva. Le istruzioni dell'esercitazione non funzioneranno correttamente se si crea un progetto destinato ASP.NET Core 6 o versione successiva. Ad esempio, i modelli Web ASP.NET Core 6 e versioni successive usano il modello di hosting minimo, che unifica Startup.cs e Program.cs in un singolo Program.cs file.

Un'altra differenza introdotta in .NET 6 è la funzionalità NRT (tipi riferimento nullable). I modelli di progetto abilitano questa funzionalità per impostazione predefinita. I problemi possono verificarsi quando EF considera necessaria una proprietà in .NET 6, che è nullable in .NET 5. Ad esempio, la pagina Crea studente avrà esito negativo automaticamente, a meno che la Enrollments proprietà non sia resa nullable o che il asp-validation-summary tag helper venga modificato da ModelOnly a All.

Per questa esercitazione è consigliabile installare e usare .NET 5 SDK. Fino a quando questa esercitazione non viene aggiornata, vedere Razor Pagine con Entity Framework Core in ASP.NET Core - Esercitazione 1 di 8 su come usare Entity Framework con ASP.NET Core 6 o versione successiva.

Motori di database

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

Risolvere i problemi e risolvere i problemi

Se si verifica un problema che non si sa come risolvere, è generalmente possibile trovare la soluzione confrontando il codice con il progetto completato. Per un elenco degli errori comuni e delle relative soluzioni, vedere la sezione relativa alla risoluzione dei problemi nell'ultima esercitazione della serie. Se non si trovano le informazioni necessarie, è possibile pubblicare una domanda per StackOverflow.com per ASP.NET Core o EF Core.

Suggerimento

In questa serie di 10 esercitazioni ogni esercitazione si basa su quanto viene eseguito nelle esercitazioni precedenti. È consigliabile salvare una copia del progetto dopo aver completato ogni esercitazione. Se si verificano problemi, è possibile ricominciare dall'esercitazione precedente anziché tornare all'inizio della serie.

App Web di Contoso University

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. Ecco alcune schermate nell'app:

Students Index page

Students Edit page

Crea 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 Core Web App (Model-View-Controller).
    3. CreaNew ASP.NET Core Project dialog

Impostare lo stile del sito

Alcune modifiche di base configurano il menu, il layout e la home page del sito.

Aprire Views/Shared/_Layout.cshtml e apportare le modifiche seguenti:

  • Modificare ogni occorrenza di ContosoUniversity in Contoso University. Le occorrenze sono tre.
  • Aggiungere voci di menu per About, Students, Courses, Instructors e Departments ed eliminare la voce di Privacy menu.

Le modifiche precedenti sono evidenziate nel codice seguente:

<!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-controller="Home" asp-action="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 justify-content-between">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Students" asp-action="Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Courses" asp-action="Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Instructors" asp-action="Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Departments" asp-action="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; 2020 - Contoso University - <a asp-area="" asp-controller="Home" asp-action="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>

In Views/Home/Index.cshtmlsostituire il contenuto del file con il markup seguente:

@{
    ViewData["Title"] = "Home Page";
}

<div class="jumbotron">
    <h1>Contoso University</h1>
</div>
<div class="row">
    <div class="col-md-4">
        <h2>Welcome to Contoso University</h2>
        <p>
            Contoso University is a sample application that
            demonstrates how to use Entity Framework Core in an
            ASP.NET Core MVC web application.
        </p>
    </div>
    <div class="col-md-4">
        <h2>Build it from scratch</h2>
        <p>You can build the application by following the steps in a series of tutorials.</p>
        <p><a class="btn btn-default" href="https://docs.asp.net/en/latest/data/ef-mvc/intro.html">See the tutorial &raquo;</a></p>
    </div>
    <div class="col-md-4">
        <h2>Download it</h2>
        <p>You can download the completed project from GitHub.</p>
        <p><a class="btn btn-default" href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-mvc/intro/samples/5cu-final">See project source code &raquo;</a></p>
    </div>
</div>

Premere CTRL+F5 per eseguire il progetto o scegliere Avvia debug > senza eseguire debug dal menu. La home page viene visualizzata con le schede per le pagine create in questa esercitazione.

Contoso University home page

EF Core Pacchetti NuGet

Questa esercitazione usa SQL Server e il pacchetto del provider è Microsoft.EntityFrameworkCore.SqlServer.

Il pacchetto EF SQL Server e le relative dipendenze Microsoft.EntityFrameworkCore e Microsoft.EntityFrameworkCore.Relational, forniscono il supporto di runtime per Entity Framework.

Aggiungere il pacchetto NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore . Nella console Gestione pacchetti immettere i comandi seguenti per aggiungere i pacchetti NuGet:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer

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

Per informazioni su altri provider di database disponibili per EF Core, vedere Provider di database.

Creare il modello di dati

Per questa app vengono create le classi di entità seguenti:

Course-Enrollment-Student data model diagram

Le entità precedenti hanno le relazioni seguenti:

  • Relazione uno-a-molti tra Student le entità e Enrollment . Uno studente può essere iscritto in un numero qualsiasi di corsi.
  • Relazione uno-a-molti tra Course le entità e Enrollment . Un corso può avere un numero qualsiasi di studenti iscritti.

Nelle sezioni seguenti viene creata una classe per ognuna di queste entità.

Entità Student (Studente)

Student entity diagram

Nella cartella Models creare la Student classe 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 ID proprietà è la colonna chiave primaria (PK) della tabella di database che corrisponde a questa classe. Per impostazione predefinita, Entity Framework interpreta una proprietà denominata ID o classnameID come chiave primaria. Ad esempio, l'infrastruttura a chiave pubblica può essere denominata StudentID anziché ID.

La proprietà Enrollments rappresenta una proprietà di navigazione. Le proprietà di navigazione contengono altre entità correlate a questa entità. Proprietà Enrollments di un'entità Student :

  • Contiene tutte le Enrollment entità correlate a tale Student entità.
  • Se una riga specifica Student nel database ha due righe correlate Enrollment :
    • La Student proprietà di navigazione dell'entità Enrollments contiene queste due Enrollment entità.

Enrollment le righe contengono il valore PK di uno studente nella StudentID colonna chiave esterna (FK).

Se una proprietà di navigazione può contenere più entità:

  • Il tipo deve essere un elenco, ad esempio ICollection<T>, List<T>o HashSet<T>.
  • È possibile aggiungere, eliminare e aggiornare le entità.

Le relazioni di navigazione molti-a-molti e uno-a-molti possono contenere più entità. Quando ICollection<T> viene usato, Entity Framework crea una HashSet<T> raccolta per impostazione predefinita.

Entità Enrollment (Iscrizione)

Enrollment entity diagram

Nella cartella Models creare la Enrollment classe 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 EnrollmentID proprietà è pk. Questa entità usa il classnameID modello anziché ID da solo. L'entità Student ha usato il ID modello. Alcuni sviluppatori preferiscono usare un modello in tutto il modello di dati. In questa esercitazione la variante illustra che è possibile usare entrambi i modelli. Un'esercitazione successiva illustra come usare ID senza classname semplifica l'implementazione dell'ereditarietà nel modello di dati.

La proprietà Grade è un oggettoenum. La ? proprietà dopo la Grade dichiarazione di tipo indica che la Grade proprietà è nullable. Un grado diverso null da zero. null significa che un voto non è noto o non è ancora stato assegnato.

La StudentID proprietà è una chiave esterna (FK) e la proprietà di navigazione corrispondente è Student. Un'entità Enrollment è associata a un'entità Student , pertanto la proprietà può contenere solo una singola Student entità. Ciò differisce dalla proprietà di Student.Enrollments navigazione, che può contenere più Enrollment entità.

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

Entity Framework interpreta una proprietà come proprietà FK se è denominata <nome della proprietà di navigazione nome><> proprietà chiave primaria. Ad esempio, per la Student proprietà di navigazione perché StudentID l'infrastruttura Student a chiave pubblica dell'entità è ID. Le proprietà FK possono anche essere denominate <nome> della proprietà della chiave primaria. Ad esempio, CourseID poiché l'infrastruttura Course a chiave pubblica dell'entità è CourseID.

Entità Course (Corso)

Course entity diagram

Nella cartella Models creare la Course classe 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 è illustrato in un'esercitazione successiva. Questo attributo consente di immettere l'infrastruttura a chiave pubblica per il corso invece di generare il database.

Creare il contesto di database

La classe principale che coordina la funzionalità di Entity Framework per un determinato modello di dati è la classe di contesto del DbContext database. Questa classe viene creata mediante derivazione dalla classe Microsoft.EntityFrameworkCore.DbContext. La DbContext classe derivata specifica le entità incluse nel modello di dati. È possibile personalizzare alcuni comportamenti di Entity Framework. In questo progetto la classe è denominata SchoolContext.

Nella cartella del progetto creare una cartella denominata Data.

Nella cartella Dati creare una SchoolContext classe con il codice seguente:

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

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

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

Il codice precedente crea una DbSet proprietà per ogni set di entità. Nella terminologia di Entity Framework:

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

Le DbSet<Enrollment> istruzioni e DbSet<Course> potrebbero essere omesse e funzionerebbero allo stesso modo. Ef le include in modo implicito perché:

  • L'entità Student fa riferimento all'entità Enrollment .
  • L'entità Enrollment fa riferimento all'entità Course .

Quando viene creato il database, Entity Framework crea tabelle i cui nomi sono gli stessi della proprietà DbSet. I nomi delle proprietà per le raccolte sono in genere plurali. Ad esempio, Students anziché Student. Gli sviluppatori non hanno un'opinione unanime sul fatto che i nomi di tabella debbano essere pluralizzati oppure no. Per queste esercitazioni, il comportamento predefinito viene sottoposto a override specificando nomi di tabella singolari in DbContext. A tale scopo, aggiungere il codice evidenziato seguente dopo l'ultima proprietà DbSet.

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

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

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

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

Registrare SchoolContext

ASP.NET Core include l'inserimento delle dipendenze. I servizi, ad esempio il contesto del database EF, vengono registrati con l'inserimento delle dipendenze durante l'avvio dell'app. I componenti che richiedono questi servizi, ad esempio i controller MVC, vengono forniti tramite parametri del costruttore. Il codice del costruttore del controller che ottiene un'istanza di contesto viene illustrato più avanti in questa esercitazione.

Per eseguire la registrazione SchoolContext come servizio, aprire Startup.cse aggiungere le righe evidenziate al ConfigureServices metodo .

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ContosoUniversity
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

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

            services.AddControllersWithViews();
        }

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

Aprire il appsettings.json file e aggiungere un stringa di connessione come illustrato nel markup seguente:

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

Aggiungere il filtro eccezioni del database

Aggiungere AddDatabaseDeveloperPageExceptionFilter a ConfigureServices come illustrato nel codice seguente:

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

    services.AddDatabaseDeveloperPageExceptionFilter();

    services.AddControllersWithViews();
}

fornisce AddDatabaseDeveloperPageExceptionFilter informazioni utili sull'errore nell'ambiente di sviluppo.

SQL Server Express LocalDB

La stringa di connessione specifica SQL Server Local DB. 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. Local DB viene avviato su richiesta ed eseguito in modalità utente; non richiede quindi una configurazione complessa. Per impostazione predefinita, Local DB crea file di database con estensione mdf nella directory C:/Users/<user>.

Inizializzare il database con dati di test

EF crea un database vuoto. In questa sezione viene aggiunto un metodo chiamato dopo la creazione del database per popolarlo con i dati di test.

Il EnsureCreated metodo viene utilizzato per creare automaticamente il database. In un'esercitazione successiva viene illustrato come gestire le modifiche del modello usando Migrazioni Code First per modificare lo schema del database anziché eliminare e creare nuovamente il database.

Nella cartella Dati creare una nuova classe denominata DbInitializer 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)
        {
            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("2005-09-01")},
            new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
            new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
            };
            foreach (Student s in students)
            {
                context.Students.Add(s);
            }
            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}
            };
            foreach (Course c in courses)
            {
                context.Courses.Add(c);
            }
            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},
            };
            foreach (Enrollment e in enrollments)
            {
                context.Enrollments.Add(e);
            }
            context.SaveChanges();
        }
    }
}

Il codice precedente verifica se il database esiste:

  • Se il database non viene trovato;
    • Viene creato e caricato con i dati di test. I dati di test vengono caricati in matrici anziché in raccolte List<T> per ottimizzare le prestazioni.
  • Se il database viene trovato, non viene eseguita alcuna azione.

Eseguire l'aggiornamento Program.cs con il codice seguente:

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

Program.cs esegue le operazioni seguenti all'avvio dell'app:

  • Ottenere un'istanza del contesto di database dal contenitore di inserimento delle dipendenze.
  • Chiamare il metodo DbInitializer.Initialize .
  • Eliminare il contesto al termine del Initialize metodo come illustrato nel codice seguente:
public static void Main(string[] args)
{
     var host = CreateWebHostBuilder(args).Build();

    using (var scope = host.Services.CreateScope())
    {
        var services = scope.ServiceProvider;
        try
        {
            var context = services.GetRequiredService<SchoolContext>();
            DbInitializer.Initialize(context);
        }
        catch (Exception ex)
        {
            var logger = services.GetRequiredService<ILogger<Program>>();
            logger.LogError(ex, "An error occurred while seeding the database.");
        }
    }

    host.Run();
}

La prima volta che viene eseguita l'app, il database viene creato e caricato con i dati di test. Ogni volta che il modello di dati cambia:

  • Eliminare il database.
  • Aggiornare il metodo di inizializzazione e avviare di nuovo con un nuovo database.

Nelle esercitazioni successive il database viene modificato quando il modello di dati cambia, senza eliminarlo e ricrearlo. Non vengono persi dati quando il modello di dati cambia.

Creare controller e visualizzazioni

Usare il motore di scaffolding in Visual Studio per aggiungere un controller MVC e viste che useranno Entity Framework per eseguire query e salvare i dati.

La creazione automatica di metodi di azione CRUD e visualizzazioni è nota come scaffolding.

  • In Esplora soluzioni fare clic con il pulsante destro del mouse sulla Controllers cartella e scegliere Aggiungi > nuovo elemento con scaffolding.
  • Nella finestra di dialogo Aggiungi scaffolding :
    • Selezionare Controller MVC con visualizzazioni, che usa Entity Framework.
    • Fare clic su Aggiungi. Viene visualizzata la finestra di dialogo Aggiungi controller MVC con visualizzazioni tramite Entity Framework : Scaffold Student
    • In Classe modello selezionare Student.
    • In Classe contesto dati selezionare SchoolContext.
    • Accettare il valore predefinito StudentsController come nome.
    • Fare clic su Aggiungi.

Il motore di scaffolding di Visual Studio crea un StudentsController.cs file e un set di visualizzazioni (*.cshtml file) che funzionano con il controller.

Si noti che il controller accetta come SchoolContext parametro del costruttore.

namespace ContosoUniversity.Controllers
{
    public class StudentsController : Controller
    {
        private readonly SchoolContext _context;

        public StudentsController(SchoolContext context)
        {
            _context = context;
        }

L'inserimento delle dipendenze di ASP.NET Core si occupa di passare un'istanza di SchoolContext al controller. È stato configurato nella Startup classe .

Il controller contiene un metodo di azione Index che consente di visualizzare tutti gli studenti nel database. Il metodo ottiene un elenco degli studenti dal set di entità Students (Studenti) leggendo la proprietà Students dell'istanza del contesto di database:

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}

Gli elementi di programmazione asincroni in questo codice vengono illustrati più avanti nell'esercitazione.

La Views/Students/Index.cshtml visualizzazione visualizza questo elenco in una tabella:

@model IEnumerable<ContosoUniversity.Models.Student>

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
                <th>
                    @Html.DisplayNameFor(model => model.LastName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstMidName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.EnrollmentDate)
                </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Premere CTRL+F5 per eseguire il progetto o scegliere Avvia debug > senza eseguire debug dal menu.

Fare clic sulla scheda Students (Studenti) per visualizzare i dati di test inseriti nel metodo DbInitializer.Initialize. A seconda delle dimensione della finestra del browser, il collegamento della scheda Students viene visualizzato in alto alla pagina oppure è necessario selezionare l'icona di spostamento in alto a destra per visualizzarlo.

Contoso University home page narrow

Students Index page

Visualizzare il database

All'avvio dell'app, il DbInitializer.Initialize metodo chiama EnsureCreated. Ef ha rilevato che non è presente alcun database:

  • Quindi ha creato un database.
  • Il codice del Initialize metodo ha popolato il database con i dati.

Usare SQL Server Esplora oggetti (SSOX) per visualizzare il database in Visual Studio:

  • Selezionare SQL Server Esplora oggetti dal menu Visualizza in Visual Studio.
  • In SSOX selezionare (localdb)\MSSQL Local DB > Database.
  • Selezionare ContosoUniversity1, la voce per il nome del database presente nel stringa di connessione nel appsettings.json file.
  • Espandere il nodo Tabelle per visualizzare le tabelle nel database.

Tables in SSOX

Fare clic con il pulsante destro del mouse sulla tabella Student e scegliere Visualizza dati per visualizzare i dati nella tabella.

Student table in SSOX

I file di *.mdf database e *.ldf si trovano nella cartella C:\Users\<username> .

Poiché EnsureCreated viene chiamato nel metodo di inizializzazione eseguito all'avvio dell'app, è possibile:

  • Apportare una modifica alla Student classe .
  • Eliminare il database.
  • Arrestare, quindi avviare l'app. Il database viene ricreato automaticamente in modo che corrisponda alla modifica.

Ad esempio, se una EmailAddress proprietà viene aggiunta alla Student classe , una nuova EmailAddress colonna nella tabella ricreata. La visualizzazione non visualizzerà la nuova EmailAddress proprietà.

Convenzioni

La quantità di codice scritta per consentire a Entity Framework di creare un database completo è minima a causa dell'uso delle convenzioni usate da Entity Framework:

  • I nomi delle proprietà DbSet vengono usate come nomi di tabella. Per le entità a cui non fa riferimento una proprietà DbSet, i nomi della classe di entità vengono usati come nome di tabella.
  • I nomi della proprietà di entità vengono usati come nomi di colonna.
  • Proprietà di entità denominate ID o classnameID riconosciute come proprietà PK.
  • Una proprietà viene interpretata come una proprietà FK se è denominata <nome proprietà pk nome><proprietà> di navigazione. Ad esempio, per la Student proprietà di navigazione perché StudentID l'infrastruttura Student a chiave pubblica dell'entità è ID. Le proprietà FK possono anche essere denominate <nome> della proprietà della chiave primaria. Ad esempio, EnrollmentID poiché l'infrastruttura Enrollment a chiave pubblica dell'entità è EnrollmentID.

È possibile eseguire l'override del comportamento convenzionale. Ad esempio, i nomi delle tabelle possono essere specificati in modo esplicito, come illustrato in precedenza in questa esercitazione. I nomi di colonna e qualsiasi proprietà possono essere impostati come PK o FK.

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 e il server è abilitato per 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, async, Task<T>await, e ToListAsync rendere il codice eseguito in modo asincrono.

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}
  • La parola chiaveasync indica al compilatore di generare callback per parti del corpo del metodo e di creare automaticamente l'oggetto Task<IActionResult> restituito.
  • Il tipo restituito Task<IActionResult> rappresenta il lavoro in corso con un risultato di tipo IActionResult.
  • 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 Entity Framework:

  • Solo le istruzioni che generano query o comandi da inviare al database vengono eseguite in modo asincrono, ad esempio ToListAsync, SingleOrDefaultAsync, 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 contesto di Entity Framework non è thread-safe. Non tentare di eseguire più operazioni parallelamente. Quando si chiama un metodo asincrono Entity Framework, usare sempre la parola chiave await.
  • Per sfruttare i vantaggi delle prestazioni del codice asincrono, assicurarsi che tutti i pacchetti di libreria usati usino anche async se chiamano metodi EF che causano l'invio di query al database.

Per altre informazioni sulla programmazione asincrona in .NET, vedere Panoramica della programmazione asincrona.

Limitare le entità recuperate

Per informazioni sulla limitazione del numero di entità restituite da una query, vedere Considerazioni sulle prestazioni .

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.

Passare all'esercitazione successiva per informazioni su come eseguire operazioni CRUD (creazione, lettura, aggiornamento ed eliminazione) di base.

Questa esercitazione illustra ASP.NET Core MVC ed Entity Framework Core con i controller e le viste. Razor Pages è un modello di programmazione alternativo. Per nuovi progetti di sviluppo, è consigliabile usare Razor Pages invece di MVC con controller e visualizzazioni. Vedere la versione per Razor Pages di questa esercitazione. Ogni esercitazione illustra alcuni materiali che l'altra versione non contiene:

Alcuni elementi presenti in questa esercitazione su MVC, ma che non sono contenuti nell'esercitazione su Razor Pages:

  • Implementare l'ereditarietà nel modello di dati
  • Eseguire query SQL non elaborate
  • Usare LINQ dinamico per semplificare il codice

Alcuni elementi presenti nell'esercitazione su Razor Pages, ma che non sono contenuti in questa esercitazione:

  • Usare il metodo Select per caricare i dati correlati
  • Procedure consigliate per EF.

L'applicazione Web di esempio Contoso University illustra come creare ASP.NET applicazioni Web MVC Core 2.2 usando Entity Framework (EF) Core 2.2 e Visual Studio 2019.

Questa esercitazione non è stata aggiornata per ASP.NET Core 3.1. È stato aggiornato per ASP.NET Core 5.0.

L'applicazione di esempio è un sito Web per una fittizia Contoso University. Include funzionalità, come ad esempio l'ammissione di studenti, la creazione di corsi e le assegnazioni di insegnati. Questa è la prima di una serie di esercitazioni che illustrano come compilare da zero l'app di esempio di Contoso University.

Prerequisiti

Risoluzione dei problemi

Se si verifica un problema che non si sa come risolvere, è generalmente possibile trovare la soluzione confrontando il codice con il progetto completato. Per un elenco degli errori comuni e delle relative soluzioni, vedere la sezione relativa alla risoluzione dei problemi nell'ultima esercitazione della serie. Se non si trovano le informazioni necessarie, è possibile pubblicare una domanda per StackOverflow.com per ASP.NET Core o EF Core.

Suggerimento

In questa serie di 10 esercitazioni ogni esercitazione si basa su quanto viene eseguito nelle esercitazioni precedenti. È consigliabile salvare una copia del progetto dopo aver completato ogni esercitazione. Se si verificano problemi, è possibile ricominciare dall'esercitazione precedente anziché tornare all'inizio della serie.

App Web di Contoso University

L'applicazione che sarà compilata in queste esercitazioni è un semplice sito Web universitario.

Gli utenti possono visualizzare e aggiornare le informazioni che riguardano studenti, corsi e insegnanti. Di seguito sono disponibili alcune schermate che saranno create.

Students Index page

Students Edit page

Crea app Web

  • Aprire Visual Studio.

  • Dal menu File selezionare Nuovo > Progetto.

  • Nel riquadro sinistro selezionare Installato > Visual C# > Web.

  • Selezionare il modello di progetto Applicazione Web ASP.NET Core.

  • Immettere ContosoUniversity come nome e fare clic su OK.

    New Project dialog

  • Attendere che venga visualizzata la finestra di dialogo Nuova applicazione Web ASP.NET Core.

  • Selezionare .NET Core, ASP.NET Core 2.2 e il modello Applicazione Web (MVC).

  • Assicurarsi che per Autenticazione sia impostata l'opzione Nessuna autenticazione.

  • seleziona OK.

    New ASP.NET Core Project dialog

Impostare lo stile del sito

Con alcune modifiche è possibile impostare il menu del sito, il layout e la home page.

Aprire Views/Shared/_Layout.cshtml e apportare le modifiche seguenti:

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

  • Aggiungere voci di menu per About, Students, Courses, Instructors e Departments ed eliminare la voce di Privacy menu.

Le modifiche sono evidenziate.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"
              crossorigin="anonymous"
              integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="/>
    </environment>
    <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-controller="Home" asp-action="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-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Students" asp-action="Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Courses" asp-action="Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Instructors" asp-action="Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Departments" asp-action="Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <partial name="_CookieConsentPartial" />
        <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-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>

    <environment include="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    </environment>
    <environment exclude="Development">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"
                asp-fallback-src="~/lib/jquery/dist/jquery.js"
                asp-fallback-test="window.jQuery"
                crossorigin="anonymous"
                integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
        </script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/js/bootstrap.bundle.js"
                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"
                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
                crossorigin="anonymous"
                integrity="sha256-E/V4cWE4qvAeO5MOhjtGtqDzPndRO1LBk8lJ/PR7CA4=">
        </script>
    </environment>
    <script src="~/js/site.js" asp-append-version="true"></script>

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

In Views/Home/Index.cshtmlsostituire il contenuto del file con il codice seguente per sostituire il testo relativo a ASP.NET e MVC con testo sull'applicazione:

@{
    ViewData["Title"] = "Home Page";
}

<div class="jumbotron">
    <h1>Contoso University</h1>
</div>
<div class="row">
    <div class="col-md-4">
        <h2>Welcome to Contoso University</h2>
        <p>
            Contoso University is a sample application that
            demonstrates how to use Entity Framework Core in an
            ASP.NET Core MVC web application.
        </p>
    </div>
    <div class="col-md-4">
        <h2>Build it from scratch</h2>
        <p>You can build the application by following the steps in a series of tutorials.</p>
        <p><a class="btn btn-default" href="https://docs.asp.net/en/latest/data/ef-mvc/intro.html">See the tutorial &raquo;</a></p>
    </div>
    <div class="col-md-4">
        <h2>Download it</h2>
        <p>You can download the completed project from GitHub.</p>
        <p><a class="btn btn-default" href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-mvc/intro/samples/cu-final">See project source code &raquo;</a></p>
    </div>
</div>

Premere CTRL+F5 per eseguire il progetto o scegliere Avvia debug > senza eseguire debug dal menu. La home page sarà visualizzata con le schede create in queste esercitazioni.

Contoso University home page

Informazioni sui EF Core pacchetti NuGet

Per aggiungere EF Core il supporto a un progetto, installare il provider di database di destinazione. Questa esercitazione usa SQL Server e il pacchetto del provider è Microsoft.EntityFrameworkCore.SqlServer. Poiché il pacchetto è incluso nel metapacchetto Microsoft.AspNetCore.App, non è necessario inserire alcun riferimento al pacchetto.

Il pacchetto EF SQL Server e le relative dipendenze (Microsoft.EntityFrameworkCore e Microsoft.EntityFrameworkCore.Relational) offrono supporto di runtime per Entity Framework. Nell'esercitazione Migrazioni viene aggiunto un pacchetto di strumenti.

Per informazioni su altri provider di database disponibili per Entity Framework Core, vedere Provider di database.

Creare il modello di dati

A questo punto è possibile creare le classi delle entità per l'applicazione di Contoso University. Si inizia con le tre entità seguenti.

Course-Enrollment-Student data model diagram

Esiste una relazione uno-a-molti tra le entità Student e Enrollment ed esiste una relazione uno-a-molti tra le entità Course e Enrollment. In altre parole, uno studente può iscriversi a un numero qualsiasi di corsi e un corso può avere un numero qualsiasi di studenti iscritti.

Nelle sezioni seguenti viene creata una classe per ognuna di queste entità.

Entità Student (Studente)

Student entity diagram

Nella cartella Models creare un file di classe denominato Student.cs e sostituire il codice del modello 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 di chiave primaria della tabella di database che corrisponde a questa classe. Per impostazione predefinita, Entity Framework interpreta una proprietà denominata ID o classnameID come chiave primaria.

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 Student entity contiene tutte le entità Enrollment correlate all'entità Student. In altre parole, se una Student riga nel database ha due righe correlate Enrollment (righe che contengono il valore della chiave primaria dello studente nella colonna chiave esterna StudentID), la Student proprietà di navigazione dell'entità Enrollments conterrà queste due Enrollment entità.

Se una proprietà di navigazione può contenere più entità (come nel caso di relazioni molti-a-molti e uno-a-molti), il tipo della proprietà deve essere un elenco in cui le voci possono essere aggiunte, eliminate e aggiornate, come ad esempio ICollection<T>. È possibile specificare ICollection<T> o un tipo come List<T> o HashSet<T>. Se si specifica ICollection<T>, per impostazione predefinita EF crea una raccolta HashSet<T>.

Entità Enrollment (Iscrizione)

Enrollment entity diagram

Nella cartella Models creare Enrollment.cs e sostituire il codice esistente 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 sarà la chiave primaria. Questa entità usa il criterio classnameID anziché ID come descritto per l'entità Student. In genere si sceglie un criterio e lo si usa poi in tutto il modello di dati. In questo caso la variazione illustra che è possibile sia uno sia l'altro criterio. In un'esercitazione successiva viene illustrato l'uso di ID senza classname. In questo modo si semplifica l'implementazione dell'ereditarietà nel modello di dati.

La proprietà Grade è un oggettoenum. Il punto interrogativo dopo la dichiarazione del tipo Grade indica che la proprietà Grade ammette i valori Null. Un voto il cui valore è Null è diverso da un voto con valore zero. Null significa che un voto è sconosciuto o non è stato ancora 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à può contenere un'unica entità Student, a differenza della proprietà di navigazione Student.Enrollments vista in precedenza, che può contenere più entità Enrollment.

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

Entity Framework interpretata una proprietà come proprietà di chiave esterna se è denominata <navigation property name><primary key property name>, ad esempio StudentID per la proprietà di navigazione Student poiché la chiave primaria dell'entità Student è ID. Le proprietà di una chiave esterna possono anche essere denominate semplicemente <primary key property name>, ad esempio CourseID poiché la chiave primaria dell'entità Course è CourseID.

Entità Course (Corso)

Course entity diagram

Nella cartella Models creare Course.cs e sostituire il codice esistente 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.

Altre informazioni sull'attributo DatabaseGenerated sono disponibili nell'esercitazione successiva di questa serie. In pratica, questo attributo consente di immettere la chiave primaria per il corso invece di essere generata dal database.

Creare il contesto di database

La classe del contesto di database è la classe principale che coordina le funzionalità di Entity Framework per un determinato modello di dati. Questa classe viene creata mediante derivazione dalla classe Microsoft.EntityFrameworkCore.DbContext. Nel codice vengono specificate le entità incluse nel modello di dati. È anche possibile personalizzare un determinato comportamento di Entity Framework. In questo progetto la classe è denominata SchoolContext.

Creare una cartella denominata Data (Dati) nella cartella del progetto.

Nella cartella Dati creare un nuovo file di classe denominato SchoolContext.cse sostituire il codice del modello con il codice seguente:

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

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

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

Questo codice crea una proprietà DbSet per ogni set di entità. Nella terminologia di Entity Framework, un set di entità corrisponde in genere alla tabella di un database e un'entità corrisponde a una riga della tabella.

Se sono state omesse le istruzioni DbSet<Enrollment> e DbSet<Course>, potrebbe comunque funzionare. Entity Framework le include in modo implicito perché l'entità Student fa riferimento all'entità Enrollment e l'entità Enrollment fa riferimento all'entità Course.

Quando viene creato il database, Entity Framework crea tabelle i cui nomi sono gli stessi della proprietà DbSet. I nomi di proprietà per le raccolte sono generalmente usati al plurale (Students anziché Student). Gli sviluppatori non hanno tuttavia un'opinione unanime sul fatto che i nomi di tabella debbano essere usati al plurale. Per queste esercitazioni, viene eseguito l'override del comportamento predefinito specificando nomi di tabella al singolare in DbContext. A tale scopo, aggiungere il codice evidenziato seguente dopo l'ultima proprietà DbSet.

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

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

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

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

Compilare il progetto per controllare se sono presenti errori del compilatore.

Registrare SchoolContext

ASP.NET Core implementa l'inserimento delle dipendenze per impostazione predefinita. I servizi, ad esempio il contesto di database di Entity Framework, vengono registrati con l'inserimento delle dipendenze durante l'avvio dell'applicazione. Questi servizi vengono quindi offerti ai componenti per cui sono necessari (ad esempio i controller MVC) tramite i parametri del costruttore. Il codice del costruttore del controller che ottiene un'istanza di contesto viene illustrato più avanti in questa esercitazione.

Per eseguire la registrazione SchoolContext come servizio, aprire Startup.cse aggiungere le righe evidenziate al ConfigureServices metodo .

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

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

    services.AddMvc();
}

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

Aggiungere le istruzioni using per gli spazi dei nomi ContosoUniversity.Data e Microsoft.EntityFrameworkCore, quindi compilare il progetto.

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Http;

Aprire il appsettings.json file e aggiungere un stringa di connessione come illustrato nell'esempio seguente.

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  }
}

SQL Server Express LocalDB

La stringa di connessione specifica un database Local DB di SQL Server. Local DB è una versione leggera del motore di database di SQL Server Express appositamente pensato per lo sviluppo di applicazioni e non per la produzione. Local DB viene avviato su richiesta ed eseguito in modalità utente; non richiede quindi una configurazione complessa. Per impostazione predefinita, Local DB crea file di database con estensione mdf nella directory C:/Users/<user>.

Inizializzare il database con dati di test

Entity Framework creerà un database vuoto. In questa sezione viene scritto un metodo che viene chiamato dopo aver creato il database al fine di popolare il database con i dati di test.

Per creare automaticamente il database, viene usato il metodo EnsureCreated. In un'esercitazione successiva viene spiegato come gestire le modifiche al modello usando Migrazioni Code First, che consente di modificare lo schema del database anziché dover eliminare e ricreare il database.

Nella cartella Dati creare un nuovo file di classe denominato DbInitializer.cs e sostituire il codice del modello con il codice seguente, che determina la creazione di un database quando necessario e carica i dati di test nel nuovo database.

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("2005-09-01")},
            new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
            new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
            };
            foreach (Student s in students)
            {
                context.Students.Add(s);
            }
            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}
            };
            foreach (Course c in courses)
            {
                context.Courses.Add(c);
            }
            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},
            };
            foreach (Enrollment e in enrollments)
            {
                context.Enrollments.Add(e);
            }
            context.SaveChanges();
        }
    }
}

Il codice verifica l'esistenza di studenti nel database. In caso negativo presuppone che il database sia nuovo e che sia necessario eseguire l'inizializzazione con i dati di test. I dati di test vengono caricati in matrici anziché in raccolte List<T> per ottimizzare le prestazioni.

In Program.csmodificare il Main metodo per eseguire le operazioni seguenti all'avvio dell'applicazione:

  • Ottenere un'istanza del contesto di database dal contenitore di inserimento delle dipendenze.
  • Chiamare il metodo di inizializzazione, passandolo al contesto.
  • Eliminare il contesto dopo che il metodo di inizializzazione è stato completato.
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>();
                    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>();
                });
    }
}

La prima volta che si esegue l'applicazione, il database verrà creato e sottoposto a seeding con i dati di test. Ogni volta che si modifica il modello di dati:

  • Eliminare il database.
  • Aggiornare il metodo di inizializzazione e avviare di nuovo con un nuovo database nello stesso modo.

Nelle esercitazioni successive viene illustrato come modificare il database alla modifica del modello di dati, senza eliminare e ricreare il database.

Creare controller e visualizzazioni

In questa sezione viene usato il motore di scaffolding in Visual Studio per aggiungere un controller MVC e viste che useranno Entity Framework per eseguire query e salvare i dati.

La creazione automatica di metodi di azione CRUD e di visualizzazioni è nota come scaffolding. Lo scaffolding differisce dalla generazione di codice in quanto il codice di scaffolding è un punto di partenza modificabile per essere adattato ai propri requisiti, mentre generalmente il codice generato non viene modificato. Quando è necessario personalizzare il codice generato, usare classi parziali oppure rigenerare il codice in caso di modifiche.

  • Fare clic con il pulsante destro del mouse sulla cartella Controllers in Esplora soluzioni e scegliere Aggiungi > nuovo elemento con scaffolding.
  • Nella finestra di dialogo Aggiungi scaffolding :
    • Selezionare Controller MVC con visualizzazioni, che usa Entity Framework.
    • Fare clic su Aggiungi. Viene visualizzata la finestra di dialogo Aggiungi controller MVC con visualizzazioni tramite Entity Framework : Scaffold Student
    • In Classe modello selezionare Student (Studente).
    • In Classe contesto di dati selezionare SchoolContext.
    • Accettare il valore predefinito StudentsController come nome.
    • Fare clic su Aggiungi.

Il motore di scaffolding di Visual Studio crea un StudentsController.cs file e un set di visualizzazioni (.cshtml file) che funzionano con il controller.

Si noti che il controller accetta come SchoolContext parametro del costruttore.

namespace ContosoUniversity.Controllers
{
    public class StudentsController : Controller
    {
        private readonly SchoolContext _context;

        public StudentsController(SchoolContext context)
        {
            _context = context;
        }

L'inserimento delle dipendenze di ASP.NET Core si occupa di passare un'istanza di SchoolContext al controller. Configurato nel Startup.cs file.

Il controller contiene un metodo di azione Index che consente di visualizzare tutti gli studenti nel database. Il metodo ottiene un elenco degli studenti dal set di entità Students (Studenti) leggendo la proprietà Students dell'istanza del contesto di database:

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}

Informazioni sugli elementi di programmazione asincroni in questo codice più avanti nell'esercitazione.

La Views/Students/Index.cshtml visualizzazione visualizza questo elenco in una tabella:

@model IEnumerable<ContosoUniversity.Models.Student>

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
                <th>
                    @Html.DisplayNameFor(model => model.LastName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstMidName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.EnrollmentDate)
                </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Premere CTRL+F5 per eseguire il progetto o scegliere Avvia debug > senza eseguire debug dal menu.

Fare clic sulla scheda Students (Studenti) per visualizzare i dati di test inseriti nel metodo DbInitializer.Initialize. A seconda delle dimensione della finestra del browser, il collegamento della scheda Students viene visualizzato in alto alla pagina oppure è necessario selezionare l'icona di spostamento in alto a destra per visualizzarlo.

Contoso University home page narrow

Students Index page

Visualizzare il database

Dopo aver avviato l'applicazione, il metodo DbInitializer.Initialize chiama EnsureCreated. Entity Framework ha verificato che non esiste un database e ne ha pertanto creato uno. La parte restante di codice del metodo Initialize ha popolato il database con i dati. È possibile usare Esplora oggetti di SQL Server per visualizzare il database in Visual Studio.

Chiudere il browser.

Se la finestra di Esplora oggetti di SQL Server non è già aperta, selezionarla dal menu Visualizza in Visual Studio.

In SSOX fare clic su (localdb)\MSSQL Local DB > Databases e quindi fare clic sulla voce relativa al nome del database nel stringa di connessione nel appsettings.json file.

Espandere il nodo Tabelle per visualizzare le tabelle nel database.

Tables in SSOX

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.

Student table in SSOX

I file di database .mdf e ldf si trovano nella cartella C:\Users\<username> .

Poiché si sta chiamando EnsureCreated nel metodo di inizializzatore che viene eseguito all'avvio dell'app, è ora possibile modificare la classe Student, eliminare il database ed eseguire nuovamente l'applicazione. Il database sarà automaticamente ricreato e rispecchierà la modifica. Ad esempio, se si aggiunge una proprietà EmailAddress alla classe Student, una nuova colonna EmailAddress sarà visualizzata nella tabella ricreata.

Convenzioni

Grazie all'uso di convenzioni o di ipotesi di Entity Framework, la quantità di codice scritto che è necessario a Entity Framework per creare un database completo è minino.

  • I nomi delle proprietà DbSet vengono usate come nomi di tabella. Per le entità a cui non fa riferimento una proprietà DbSet, i nomi della classe di entità vengono usati come nome di tabella.
  • I nomi della proprietà di entità vengono usati come nomi di colonna.
  • Le proprietà dell'entità vengono denominate ID o classnameID e vengono riconosciute come proprietà della chiave primaria.
  • Una proprietà viene interpretata come una proprietà di chiave esterna se è denominata nome della proprietà di navigazione nome><della proprietà> chiave primaria (ad esempio, StudentID per la Student proprietà di navigazione perché la Student chiave primaria dell'entità è ID).< Le proprietà della chiave esterna possono anche essere denominate semplicemente <nome> della proprietà della chiave primaria ( ad esempio, EnrollmentID poiché la Enrollment chiave primaria dell'entità è EnrollmentID).

È possibile eseguire l'override del comportamento convenzionale. Ad esempio, è possibile specificare in modo esplicito i nomi di tabella, come illustrato in precedenza in questa esercitazione. È anche possibile impostare i nomi delle colonne e impostare qualsiasi proprietà come chiave primaria o chiave esterna, come sarà spiegato in un'esercitazione successiva di questa serie.

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 e il server è abilitato per 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<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}
  • La parola chiaveasync indica al compilatore di generare callback per parti del corpo del metodo e di creare automaticamente l'oggetto Task<IActionResult> restituito.
  • Il tipo restituito Task<IActionResult> rappresenta il lavoro in corso con un risultato di tipo IActionResult.
  • 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 considerare quando si scrive codice asincrono usato da Entity Framework:

  • Solo le istruzioni che generano query o comandi da inviare al database vengono eseguite in modo asincrono, ad esempio ToListAsync, SingleOrDefaultAsync, 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 contesto di Entity Framework non è thread-safe. Non tentare di eseguire più operazioni parallelamente. Quando si chiama un metodo asincrono Entity Framework, usare sempre la parola chiave await.
  • Se si vogliono sfruttare i vantaggi del codice asincrono in termini di prestazioni, verificare che i pacchetti della libreria impiegati, ad esempio per il paging, usino la modalità asincrona per chiamare i metodi di Entity Framework che generano query da inviare al database.

Per altre informazioni sulla programmazione asincrona in .NET, vedere Panoramica della programmazione asincrona.

Passaggi successivi

Passare all'esercitazione successiva per informazioni su come eseguire operazioni CRUD (creazione, lettura, aggiornamento ed eliminazione) di base.