Condividi tramite


Esercitazione: Scopri gli scenari avanzati - ASP.NET MVC con EF Core

Nell'esercitazione precedente, hai implementato l'ereditarietà di tabella per gerarchia. Questa esercitazione presenta diversi argomenti utili per essere a conoscenza di quando si vanno oltre le nozioni di base dello sviluppo di applicazioni Web ASP.NET Core che usano Entity Framework Core.

In questa esercitazione, tu:

  • Eseguire query SQL grezze
  • Eseguire una query per restituire entità
  • Chiamare una query per restituire altri tipi
  • Eseguire una query di aggiornamento
  • Esaminare le query SQL
  • Creare un livello di astrazione
  • Informazioni sul rilevamento automatico delle modifiche
  • Informazioni su EF Core codice sorgente e piani di sviluppo
  • Informazioni su come usare LINQ dinamico per semplificare il codice

Prerequisiti

Eseguire query SQL dirette

Uno dei vantaggi dell'uso di Entity Framework consiste nell'evitare di legare troppo il codice a un particolare metodo di archiviazione dei dati. Per questo, genera automaticamente query e comandi SQL per te, liberandoti anche dalla necessità di scriverli manualmente. Esistono tuttavia scenari eccezionali in cui è necessario eseguire query SQL specifiche create manualmente. Per questi scenari, l'API Code First di Entity Framework include metodi che consentono di passare i comandi SQL direttamente al database. In EF Core 1.0 sono disponibili le opzioni seguenti:

  • Usare il metodo DbSet.FromSql per le query che restituiscono tipi di entità. Gli oggetti restituiti devono essere del tipo previsto dall'oggetto DbSet e vengono rilevati automaticamente dal contesto del database, a meno che non si disattivare il rilevamento.

  • Usare il Database.ExecuteSqlCommand per i comandi non di query.

Se è necessario eseguire una query che restituisce tipi che non sono entità, è possibile usare ADO.NET con la connessione al database fornita da EF. I dati restituiti non vengono rilevati dal contesto del database, anche se si usa questo metodo per recuperare i tipi di entità.

Come sempre è vero quando si eseguono comandi SQL in un'applicazione Web, è necessario adottare precauzioni per proteggere il sito da attacchi SQL injection. Un modo per eseguire questa operazione consiste nell'usare query con parametri per assicurarsi che le stringhe inviate da una pagina Web non possano essere interpretate come comandi SQL. In questa esercitazione si useranno query parametrizzate integrando l'input dell'utente in una query.

Chiamare una query per restituire entità

La classe DbSet<TEntity> fornisce un metodo che è possibile usare per eseguire una query che restituisce un'entità di tipo TEntity. Per vedere come funziona, si modificherà il codice nel metodo Details del controller di reparto.

In DepartmentsController.cs, nel metodo Details sostituire il codice che recupera un reparto con una chiamata al metodo FromSql, come illustrato nel codice evidenziato seguente:

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

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

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

    return View(department);
}

Per verificare che il nuovo codice funzioni correttamente, selezionare la scheda Reparti e quindi Dettagli per uno dei reparti.

dettagli reparto

Eseguire una query per restituire altri tipi

In precedenza è stata creata una griglia delle statistiche degli studenti per la pagina Informazioni che mostrava il numero di studenti per ogni data di iscrizione. Hai ottenuto i dati dal set di entità Students (_context.Students) e hai usato LINQ per trasferire i risultati in un elenco di oggetti del modello di vista EnrollmentDateGroup. Si supponga di voler scrivere il codice SQL invece di usare LINQ. A tale scopo, è necessario eseguire una query SQL che restituisce un valore diverso da oggetti entità. In EF Core 1.0, un modo per eseguire questa operazione consiste nello scrivere codice ADO.NET e ottenere la connessione al database da EF.

In HomeController.cssostituire il metodo About con il codice seguente:

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

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

Aggiungere un'istruzione using:

using System.Data.Common;

Eseguire l'app e passare alla pagina Informazioni. Visualizza gli stessi dati che ha fatto in precedenza.

informazioni sulla pagina

Chiamare un'interrogazione di aggiornamento

Si supponga che gli amministratori di Contoso University vogliano eseguire modifiche globali nel database, ad esempio la modifica del numero di crediti per ogni corso. Se l'università ha un numero elevato di corsi, sarebbe inefficiente recuperarli tutti come entità e modificarli singolarmente. In questa sezione verrà implementata una pagina Web che consente all'utente di specificare un fattore in base al quale modificare il numero di crediti per tutti i corsi e apportare la modifica eseguendo un'istruzione SQL UPDATE. La pagina Web sarà simile alla figura seguente:

Aggiorna pagina Crediti del Corso

In CoursesController.csaggiungere i metodi UpdateCourseCredits per HttpGet e HttpPost:

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

Quando il controller elabora una richiesta HttpGet, non viene restituito nulla in ViewData["RowsAffected"]e la visualizzazione visualizza una casella di testo vuota e un pulsante di invio, come illustrato nella figura precedente.

Quando si fa clic sul pulsante Aggiorna, viene chiamato il metodo HttpPost, e il moltiplicatore assume il valore inserito nella casella di testo. Il codice esegue quindi il codice SQL che aggiorna i corsi e restituisce il numero di righe interessate alla vista in ViewData. Quando la vista ottiene un valore RowsAffected, viene visualizzato il numero di righe aggiornate.

In Esplora soluzioni , fare clic con il pulsante destro del mouse sulla cartella Views/Courses e quindi scegliere Aggiungi > Nuovo elemento.

Nella finestra di dialogo Aggiungi nuovo elemento , fare clic su ASP.NET Core sotto Installato nel riquadro sinistro, fare clic su VisualizzazioneRazor, e assegnare alla nuova visualizzazione il nome UpdateCourseCredits.cshtml.

In Views/Courses/UpdateCourseCredits.cshtmlsostituire il codice del modello con il codice seguente:

@{
    ViewBag.Title = "UpdateCourseCredits";
}

<h2>Update Course Credits</h2>

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

Eseguire il metodo UpdateCourseCredits selezionando la scheda Courses e quindi aggiungendo "/UpdateCourseCredits" alla fine dell'URL nella barra degli indirizzi del browser , ad esempio http://localhost:5813/Courses/UpdateCourseCredits. Immettere un numero nella casella di testo:

Aggiorna la pagina dei crediti del corso

Fare clic su Aggiorna. Viene visualizzato il numero di righe interessate:

Aggiorna le righe interessate della pagina dei Crediti del corso

Fare clic su Torna all'elenco per visualizzare l'elenco dei corsi con il numero di crediti modificato.

Si noti che il codice di produzione garantisce che gli aggiornamenti comportino sempre dati validi. Il codice semplificato illustrato qui potrebbe moltiplicare il numero di crediti sufficienti per ottenere numeri maggiori di 5. La proprietà Credits ha un attributo [Range(0, 5)]. La query di aggiornamento funzionerebbe, ma i dati non validi potrebbero causare risultati imprevisti in altre parti del sistema che presuppongono che il numero di crediti sia 5 o inferiore.

Per altre informazioni sulle query SQL non elaborate, vedere query SQL non elaborate.

Esaminare le query SQL

In alcuni casi è utile essere in grado di visualizzare le query SQL effettive inviate al database. La funzionalità di registrazione predefinita per ASP.NET Core viene usata automaticamente da EF Core per scrivere log contenenti SQL per query e aggiornamenti. In questa sezione verranno visualizzati alcuni esempi di registrazione SQL.

Aprire StudentsController.cs e nel metodo Details impostare un punto di interruzione nell'istruzione if (student == null).

Eseguire l'app in modalità di debug e passare alla pagina Dettagli per uno studente.

Passare alla finestra Output che mostra l'output di debug, e vedrai la query:

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

Si noterà qualcosa che potrebbe sorprendere: SQL seleziona fino a 2 righe (TOP(2)) dalla tabella Person. Il metodo SingleOrDefaultAsync non viene risolto in 1 riga nel server. Ecco perché:

  • Se la query restituisce più righe, il metodo restituisce Null.
  • Per determinare se la query restituisce più righe, EF deve verificare se restituisce almeno 2.

Si noti che non è necessario usare la modalità debug e fermarsi a un punto di interruzione per ottenere l'output di registrazione nella finestra Output. È solo un modo pratico per arrestare la registrazione nel punto in cui si vuole esaminare l'output. In caso contrario, la registrazione continua ed è necessario scorrere indietro per trovare le parti a cui si è interessati.

Creare un livello di astrazione

Molti sviluppatori scrivono codice per implementare il repository e l'unità di modelli di lavoro come wrapper intorno al codice che funziona con Entity Framework. Questi modelli sono progettati per creare un livello di astrazione tra il livello di accesso ai dati e il livello della logica di business di un'applicazione. L'implementazione di questi modelli consente di isolare l'applicazione dalle modifiche nell'archivio dati e può facilitare unit test automatizzati o sviluppo basato su test (TDD). Tuttavia, la scrittura di codice aggiuntivo per implementare questi modelli non è sempre la scelta migliore per le applicazioni che usano Entity Framework, per diversi motivi:

  • La classe di contesto ef isola il codice dal codice specifico dell'archivio dati.

  • La classe di contesto di Entity Framework può fungere da classe unit-of-work per gli aggiornamenti del database eseguiti tramite Entity Framework.

  • EF include funzionalità per l'implementazione di TDD senza scrivere codice del repository.

Per informazioni su come implementare il repository e l'unità di modelli di lavoro, vedere la versione Entity Framework 5 di questa serie di esercitazioni.

Entity Framework Core implementa un provider di database in memoria che può essere usato per il test. Per altre informazioni, vedere Test con InMemory.

Rilevamento automatico delle modifiche

Entity Framework determina la modalità di modifica di un'entità (e quindi quali aggiornamenti devono essere inviati al database) confrontando i valori correnti di un'entità con i valori originali. I valori originali vengono archiviati quando l'entità viene interrogata o collegata. Alcuni dei metodi che causano il rilevamento automatico delle modifiche sono i seguenti:

  • DbContext.SaveChanges

  • DbContext.Entry

  • ChangeTracker.Entries

Se si rileva un numero elevato di entità e si chiama uno di questi metodi più volte in un ciclo, è possibile ottenere miglioramenti significativi delle prestazioni disattivando temporaneamente il rilevamento automatico delle modifiche usando la proprietà ChangeTracker.AutoDetectChangesEnabled. Per esempio:

_context.ChangeTracker.AutoDetectChangesEnabled = false;

EF Core codice sorgente e piani di sviluppo

La sorgente di Entity Framework Core è in https://github.com/dotnet/efcore. Il repository EF Core contiene compilazioni notturne, rilevamento dei problemi, specifiche di funzionalità, note sulla riunione di progettazione e la roadmap per lo sviluppo futuro. È possibile archiviare o trovare bug e contribuire.

Anche se il codice sorgente è aperto, Entity Framework Core è completamente supportato come prodotto Microsoft. Il team di Microsoft Entity Framework mantiene il controllo sui contributi accettati e testa tutte le modifiche al codice per garantire la qualità di ogni versione.

Ingegneria inversa dal database esistente

Per eseguire il reverse engineer di un modello di dati da un database esistente, includendo le classi di entità, utilizzare il comando scaffold-dbcontext . Consulta il tutorial introduttivo .

Usare LINQ dinamico per semplificare il codice

L' terza esercitazione di questa serie illustra come scrivere codice LINQ codificando manualmente i nomi di colonna in un'istruzione switch. Con due colonne tra cui scegliere, funziona bene, ma se sono presenti molte colonne, il codice potrebbe diventare prolisso. Per risolvere il problema, è possibile usare il metodo EF.Property per specificare il nome della proprietà come stringa. Per provare questo approccio, sostituire il metodo Index nel StudentsController con il codice seguente.

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

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

     ViewData["CurrentFilter"] = searchString;

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

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

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

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

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

Riconoscimenti

Tom Dykstra e Rick Anderson (twitter @RickAndMSFT)) hanno scritto questa esercitazione. Rowan Miller, Diego Vega e altri membri del team di Entity Framework hanno assistito con revisioni del codice e hanno contribuito a eseguire il debug dei problemi che si sono verificati durante la scrittura del codice per le esercitazioni. John Parente e Paul Goldman hanno lavorato all'aggiornamento dell'esercitazione per ASP.NET Core 2.2.

Risolvere gli errori comuni

ContosoUniversity.dll usato da un altro processo

Messaggio di errore:

Impossibile aprire '... bin\Debug\netcoreapp1.0\ContosoUniversity.dll' for writing -- 'The process cannot access the file '...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll' perché viene usato da un altro processo.

Soluzione:

Fermare il sito in IIS Express. Passare all'area di notifica di Windows, trovare IIS Express e fare clic con il pulsante destro del mouse sull'icona, selezionare il sito Contoso University e quindi fare clic su Arresta sito.

Migrazione strutturata senza codice nei metodi Up e Down

Possibile causa:

I comandi della CLI di Entity Framework non chiudono e salvano automaticamente i file di codice. Se sono state apportate modifiche non salvate quando si esegue il comando migrations add, EF non troverà le modifiche.

Soluzione:

Eseguire il comando migrations remove, salvare le modifiche al codice ed eseguire di nuovo il comando migrations add.

Errori durante l'esecuzione dell'aggiornamento del database

È possibile ottenere altri errori quando si apportano modifiche allo schema in un database con dati esistenti. Se si verificano errori di migrazione che non è possibile risolvere, è possibile modificare il nome del database nella stringa di connessione o eliminare il database. Con un nuovo database, non sono presenti dati di cui eseguire la migrazione e il comando update-database è molto più probabile che venga completato senza errori.

L'approccio più semplice consiste nel rinominare il database in appsettings.json. Alla successiva esecuzione database updateverrà creato un nuovo database.

Per eliminare un database in SSOX, fare clic con il pulsante destro del mouse sul database, scegliere Eliminae quindi nella finestra di dialogo Elimina database selezionare Chiudere le connessioni esistenti e fare clic su OK.

Per eliminare un database usando il CLI, eseguire il comando database drop del CLI:

dotnet ef database drop

Errore durante l'individuazione dell'istanza di SQL Server

Messaggio di errore:

Si è verificato un errore specifico della rete o dell'istanza durante la creazione di una connessione a SQL Server. Il server non è stato trovato o non è stato accessibile. Verificare che il nome dell'istanza sia corretto e che SQL Server sia configurato per consentire le connessioni remote. (provider: Interfacce di rete SQL, errore: 26 - Errore durante l'individuazione del server o dell'istanza specificata)

Soluzione:

Controllare la stringa di connessione. Se il file di database è stato eliminato manualmente, modificare il nome del database nella stringa di costruzione per ricominciare con un nuovo database.

Ottenere il codice

Scaricare o visualizzare l'applicazione completata.

Risorse aggiuntive

Per altre informazioni su EF Core, vedere la documentazione Entity Framework Core. È disponibile anche un libro: Entity Framework Core in Action.

Per informazioni su come distribuire un'app Web, vedere Host e distribuire ASP.NET Core.

Per informazioni su altri argomenti correlati a ASP.NET Core MVC, ad esempio l'autenticazione e l'autorizzazione, vedere Panoramica di ASP.NET Core.

Per indicazioni sulla creazione di un'app Web affidabile, sicura, efficiente, testabile e scalabile ASP.NET Core, vedere modelli di app Web Enterprise. È disponibile un'app Web di esempio completa di qualità di produzione che implementa i modelli.

Passaggi successivi

In questa esercitazione, voi:

  • Esecuzione di query SQL non elaborate
  • Chiamare una query per restituire le entità
  • Esecuzione di una query per restituire altri tipi
  • Denominata query di aggiornamento
  • Query SQL esaminate
  • Creazione di un livello di astrazione
  • Informazioni sul rilevamento automatico delle modifiche
  • Informazioni su EF Core codice sorgente e piani di sviluppo
  • Si è appreso come usare LINQ dinamico per semplificare il codice

Questa serie di esercitazioni completa l'uso di Entity Framework Core in un'applicazione MVC di ASP.NET Core. Questa serie ha lavorato con un nuovo database; un'alternativa consiste nel il reverse engineer di un modello da un database esistente.