Condividi tramite


Esercitazione: Creare un modello di dati più complesso per un'app MVC ASP.NET

Nelle esercitazioni precedenti è stato usato un modello di dati semplice composto da tre entità. In questa esercitazione vengono aggiunte altre entità e relazioni e si personalizza il modello di dati specificando regole di formattazione, convalida e mapping del database. Questo articolo illustra due modi per personalizzare il modello di dati: aggiungendo attributi alle classi di entità e aggiungendo codice alla classe di contesto del database.

Al termine dell'operazione le classi di entità verranno incluse nel modello di dati completato, illustrato nella figura seguente:

School_class_diagram

In questa esercitazione:

  • Personalizzare il modello di dati
  • Aggiornare l'entità Student
  • Creare l'entità Instructor
  • Creare l'entità OfficeAssignment
  • Modificare l'entità Course
  • Creare l'entità Department
  • Modificare l'entità Enrollment
  • Aggiungere codice al contesto del database
  • Eseguire il seeding del database con dati di test
  • Aggiungere una migrazione
  • Aggiornare il database

Prerequisiti

Personalizzare il modello di dati

In questa sezione si apprenderà come personalizzare il modello di dati usando attributi che specificano regole di formattazione, convalida e mapping del database. In diverse sezioni seguenti si creerà quindi il modello di dati completo School aggiungendo attributi alle classi già create e creando nuove classi per i tipi di entità rimanenti nel modello.

Attributo DataType

Per le date di iscrizione degli studenti, tutte le pagine Web attualmente visualizzano l'ora oltre alla data, anche se l'unico elemento rilevante di questo campo è la data. Mediante gli attributi di annotazione dei dati è possibile modificare il codice per correggere il formato di visualizzazione in tutte le visualizzazioni che visualizzano i dati. Per un esempio di come eseguire questa operazione si aggiunge un attributo alla proprietà EnrollmentDate nella classe Student.

In Models\Student.cs aggiungere un'istruzione using per lo spazio dei System.ComponentModel.DataAnnotations nomi e aggiungere DataType attributi e DisplayFormat alla EnrollmentDate proprietà , come illustrato nell'esempio seguente:

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }
        
        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

L'attributo DataType viene utilizzato per specificare un tipo di dati più specifico del tipo intrinseco del database. In questo caso si vuole tenere traccia solo della data e non di data e ora. L'enumerazione DataType offre molti tipi di dati, ad esempio Data, Ora, PhoneNumber, Valuta, EmailAddress e altro ancora. L'attributo DataType può anche consentire all'applicazione di fornire automaticamente le funzionalità specifiche del tipo. Ad esempio, è possibile creare un mailto: collegamento per DataType.EmailAddress e un selettore data può essere fornito per DataType.Date nei browser che supportano HTML5. Gli attributi DataType generano attributi HTML 5 data- (pronunciato trattino dati) che i browser HTML 5 possono comprendere. Gli attributi DataType non forniscono alcuna convalida.

DataType.Date non specifica il formato della data visualizzata. Per impostazione predefinita, il campo dati viene visualizzato in base ai formati predefiniti basati su CultureInfo del server.

L'attributo DisplayFormat viene usato per specificare in modo esplicito il formato della data:

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

L'impostazione ApplyFormatInEditMode specifica che la formattazione specificata deve essere applicata anche quando il valore viene visualizzato in una casella di testo per la modifica. È possibile che non si voglia che per alcuni campi, ad esempio per i valori di valuta, non si voglia che il simbolo di valuta nella casella di testo venga modificato.

È possibile usare l'attributo DisplayFormat da solo, ma in genere è consigliabile usare anche l'attributo DataType . L'attributo DataType fornisce la semantica dei dati anziché come eseguirne il rendering su una schermata e offre i vantaggi seguenti che non si ottengono con DisplayFormat:

  • Il browser può abilitare le funzionalità HTML5, ad esempio per visualizzare un controllo di calendario, il simbolo della valuta appropriato per le impostazioni locali, i collegamenti alla posta elettronica, alcune istanze di convalida lato client e così via.
  • Per impostazione predefinita, il browser eseguirà il rendering dei dati usando il formato corretto in base alle impostazioni locali.
  • L'attributo DataType può consentire a MVC di scegliere il modello di campo appropriato per eseguire il rendering dei dati(DisplayFormat usa il modello stringa). Per altre informazioni, vedere Modelli ASP.NET MVC 2 di Brad Wilson. Anche se scritto per MVC 2, questo articolo si applica ancora alla versione corrente di ASP.NET MVC.

Se si usa l'attributo DataType con un campo data, è necessario specificare anche l'attributo DisplayFormat per assicurarsi che il rendering del campo venga eseguito correttamente nei browser Chrome. Per altre informazioni, vedere questo thread StackOverflow.

Per altre informazioni su come gestire altri formati di data in MVC, vedere Introduzione a MVC 5: Esame dei metodi di modifica e modifica visualizzazione e ricerca nella pagina per la "internazionalizzazione".

Eseguire di nuovo la pagina Student Index e notare che gli orari non vengono più visualizzati per le date di registrazione. Lo stesso vale per qualsiasi visualizzazione che usa il Student modello.

Students_index_page_with_formatted_date

The StringLengthAttribute

È anche possibile specificare regole di convalida dei dati e messaggi di errore di convalida mediante gli attributi. L'attributo StringLength imposta la lunghezza massima nel database e fornisce la convalida lato client e lato server per ASP.NET MVC. È anche possibile specificare la lunghezza minima della stringa in questo attributo, ma il valore minimo non ha alcun effetto sullo schema del database.

Ad esempio si supponga di voler limitare a 50 il numero massimo di caratteri che gli utenti possono immettere per un nome. Per aggiungere questa limitazione, aggiungere gli attributi StringLength alle LastName proprietà e FirstMidName , come illustrato nell'esempio seguente:

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [StringLength(50)]
        public string LastName { get; set; }
        [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }
        
        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

L'attributo StringLength non impedisce a un utente di immettere spazi vuoti per un nome. È possibile usare l'attributo RegularExpression per applicare restrizioni all'input. Ad esempio, il codice seguente richiede che il primo carattere sia maiuscolo e i caratteri rimanenti siano alfabetici:

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]

L'attributo MaxLength fornisce funzionalità simili all'attributo StringLength , ma non fornisce la convalida lato client.

Eseguire l'applicazione e fare clic sulla scheda Students (Studenti ). Viene visualizzato l'errore seguente:

Il modello che esegue il backup del contesto "SchoolContext" è stato modificato dopo la creazione del database. È consigliabile usare Migrazioni Code First per aggiornare il database (https://go.microsoft.com/fwlink/?LinkId=238269).

Il modello di database è stato modificato in modo da richiedere una modifica dello schema del database e Entity Framework ha rilevato che. Si useranno le migrazioni per aggiornare lo schema senza perdere dati aggiunti al database usando l'interfaccia utente. Se sono stati modificati i dati creati dal Seed metodo , che verranno modificati nuovamente allo stato originale a causa del metodo AddOrUpdate usato nel Seed metodo . AddOrUpdate equivale a un'operazione "upsert" dalla terminologia del database.

Nella console di Gestione pacchetti immettere i comandi seguenti:

add-migration MaxLengthOnNames
update-database

Il add-migration comando crea un file denominato <timeStamp>_MaxLengthOnNames.cs. Il metodo Up di questo file contiene codice che aggiorna il database per adattarlo al modello di dati corrente. Il comando update-database ha eseguito tale codice.

Il timestamp anteporto al nome file delle migrazioni viene usato da Entity Framework per ordinare le migrazioni. È possibile creare più migrazioni prima di eseguire il update-database comando e quindi tutte le migrazioni vengono applicate nell'ordine in cui sono state create.

Eseguire la pagina Crea e immettere un nome di lunghezza superiore a 50 caratteri. Quando si fa clic su Crea, la convalida lato client visualizza un messaggio di errore: il campo LastName deve essere una stringa con una lunghezza massima di 50.

Attributo Column

È possibile usare gli attributi anche per controllare il mapping delle classi e delle proprietà nel database. Si supponga di aver usato il nome FirstMidName per il campo first-name (Nome) perché il campo potrebbe contenere anche un secondo nome. Tuttavia si vuole che la colonna di database sia denominata FirstName, perché gli utenti che scrivono query ad hoc per il database sono abituati a tale nome. Per eseguire questo mapping è possibile usare l'attributo Column.

L'attributo Column specifica che quando viene creato il database, la colonna della tabella Student mappata sulla proprietà FirstMidName verrà denominata FirstName. In altri termini, quando il codice fa riferimento a Student.FirstMidName i dati provengono dalla colonna FirstName della tabella Student o vengono aggiornati in tale colonna. Se non si specificano nomi di colonna, vengono assegnati lo stesso nome del nome della proprietà.

Nel file Student.cs aggiungere un'istruzione using per System.ComponentModel.DataAnnotations.Schema e aggiungere l'attributo del nome di colonna alla FirstMidName proprietà , come illustrato nel codice evidenziato seguente:

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [StringLength(50)]       
        public string LastName { get; set; }
        [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
        [Column("FirstName")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

        public DateTime EnrollmentDate { get; set; }
        
        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

L'aggiunta dell'attributo Column modifica il modello che esegue il backup di SchoolContext, in modo che non corrisponda al database. Immettere i comandi seguenti in PMC per creare un'altra migrazione:

add-migration ColumnFirstName
update-database

In Esplora server aprire la finestra di progettazione tabelle Student facendo doppio clic sulla tabella Student .

L'immagine seguente mostra il nome della colonna originale come prima dell'applicazione delle prime due migrazioni. Oltre al nome della colonna che passa da FirstMidName a FirstName, le due colonne dei nomi sono cambiate da MAX lunghezza a 50 caratteri.

Due screenshot che mostrano le differenze nelle tabelle Name e Data Type delle due tabelle Student.

È anche possibile apportare modifiche al mapping del database usando l'API Fluent, come illustrato più avanti in questa esercitazione.

Nota

Se si prova a compilare prima di aver creato tutte le classi di entità delle sezioni seguenti, possono verificarsi errori di compilazione.

Aggiornare l'entità Student

In Models\Student.cs sostituire il codice aggiunto in precedenza con il codice seguente. Le modifiche sono evidenziate.

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [Required]
        [StringLength(50)]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
        [Required]
        [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Enrollment Date")]
        public DateTime EnrollmentDate { get; set; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get
            {
                return LastName + ", " + FirstMidName;
            }
        }

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

Attributo obbligatorio

L'attributo Required rende obbligatori i campi delle proprietà del nome. Non Required attribute è necessario per i tipi valore, ad esempio DateTime, int, double e float. I tipi valore non possono essere assegnati a un valore Null, pertanto vengono considerati intrinsecamente come campi obbligatori.

L'attributo Required deve essere usato con MinimumLength per l'applicazione di MinimumLength.

[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }

MinimumLength e Required consentono spazi vuoti per soddisfare la convalida. Usare l'attributo RegularExpression per il controllo completo sulla stringa.

Attributo display

L'attributo Display specifica che la didascalia delle caselle di testo deve essere "First Name" (Nome), "Last Name" (Cognome), "Full Name" (Nome e cognome) ed "Enrollment Date" (Data di iscrizione) anziché il nome della proprietà (senza spazi tra le parole) in ogni istanza.

Proprietà calcolata FullName

FullName è una proprietà calcolata che restituisce un valore creato concatenando altre due proprietà. Pertanto, dispone solo di una get funzione di accesso e non verrà generata alcuna FullName colonna nel database.

Creare l'entità Instructor

Creare Models\Instructor.cs, sostituendo il codice del modello con il codice seguente:

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

namespace ContosoUniversity.Models
{
    public class Instructor
    {
        public int ID { get; set; }

        [Required]
        [Display(Name = "Last Name")]
        [StringLength(50)]
        public string LastName { get; set; }

        [Required]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        [StringLength(50)]
        public string FirstMidName { get; set; }

        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Hire Date")]
        public DateTime HireDate { get; set; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get { return LastName + ", " + FirstMidName; }
        }

        public virtual ICollection<Course> Courses { get; set; }
        public virtual OfficeAssignment OfficeAssignment { get; set; }
    }
}

Si noti che molte proprietà sono uguali nelle entità Student e Instructor. Nell'esercitazione Implementing Inheritance (Implementazione dell'ereditarietà) più avanti in questa serie si effettuerà il refactoring di questo codice per eliminare la ridondanza.

È possibile inserire più attributi su una riga, quindi è anche possibile scrivere la classe instructor come indicato di seguito:

public class Instructor
{
   public int ID { get; set; }

   [Display(Name = "Last Name"),StringLength(50, MinimumLength=1)]
   public string LastName { get; set; }

   [Column("FirstName"),Display(Name = "First Name"),StringLength(50, MinimumLength=1)]
   public string FirstMidName { get; set; }

   [DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
   public DateTime HireDate { get; set; }

   [Display(Name = "Full Name")]
   public string FullName
   {
      get { return LastName + ", " + FirstMidName; }
   }

   public virtual ICollection<Course> Courses { get; set; }
   public virtual OfficeAssignment OfficeAssignment { get; set; }
}

Proprietà di navigazione Courses e OfficeAssignment

Le proprietà Courses e OfficeAssignment sono proprietà di navigazione. Come spiegato in precedenza, vengono in genere definiti come virtuali in modo che possano sfruttare una funzionalità di Entity Framework denominata caricamento differita. Inoltre, se una proprietà di navigazione può contenere più entità, il relativo tipo deve implementare l'interfaccia T> di ICollection<. Ad esempio , IList<T> qualifica ma non IEnumerable<T> perché IEnumerable<T> non implementa Add.

Un insegnante può insegnare un numero qualsiasi di corsi, quindi Courses è definito come una raccolta di Course entità.

public virtual ICollection<Course> Courses { get; set; }

Le regole business dichiarano che un insegnante può avere al massimo un ufficio, quindi OfficeAssignment è definito come una singola OfficeAssignment entità (che può essere null se non viene assegnato alcun ufficio).

public virtual OfficeAssignment OfficeAssignment { get; set; }

Creare l'entità OfficeAssignment

Creare Modelli\OfficeAssignment.cs con il codice seguente:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class OfficeAssignment
    {
        [Key]
        [ForeignKey("Instructor")]
        public int InstructorID { get; set; }
        [StringLength(50)]
        [Display(Name = "Office Location")]
        public string Location { get; set; }

        public virtual Instructor Instructor { get; set; }
    }
}

Compilare il progetto, che salva le modifiche e verifica che non siano stati eseguiti errori di copia e incolla che il compilatore può intercettare.

Attributo chiave

Esiste una relazione uno-a-zero-o-uno tra le Instructor entità e OfficeAssignment . Un'assegnazione di ufficio esiste solo in relazione all'insegnante a cui è assegnata e pertanto la chiave primaria è anche la chiave esterna all'entità Instructor . Entity Framework non è tuttavia in grado di riconoscere InstructorID automaticamente come chiave primaria di questa entità perché il nome non segue la ID convenzione di denominazione o classnameID. Per identificare l'entità come chiave viene usato l'attributo Key:

[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }

È anche possibile usare l'attributo se l'entità Key dispone di una propria chiave primaria, ma si vuole assegnare alla proprietà un nome diverso da classnameID o ID. Per impostazione predefinita, Entity Framework considera la chiave come non generata dal database perché la colonna è per una relazione di identificazione.

Attributo ForeignKey

Quando è presente una relazione uno-a-zero-o-uno o una relazione uno-a-uno tra due entità (ad esempio tra OfficeAssignment e Instructor), EF non può determinare quale fine della relazione è l'entità e quale fine dipende. Le relazioni uno-a-uno hanno una proprietà di navigazione di riferimento in ogni classe all'altra classe. L'attributo ForeignKey può essere applicato alla classe dipendente per stabilire la relazione. Se si omette l'attributo ForeignKey, viene visualizzato l'errore seguente quando si tenta di creare la migrazione:

Impossibile determinare la fine principale di un'associazione tra i tipi "ContosoUniversity.Models.OfficeAssignment" e "ContosoUniversity.Models.Instructor". La fine principale di questa associazione deve essere configurata in modo esplicito usando l'API fluent della relazione o le annotazioni dei dati.

Più avanti nell'esercitazione si vedrà come configurare questa relazione con l'API Fluent.

Proprietà di navigazione Instructor

L'entità Instructor ha una proprietà di navigazione nullable OfficeAssignment (perché un insegnante potrebbe non avere un'assegnazione di ufficio) e l'entità OfficeAssignment ha una proprietà di navigazione non nullable Instructor (perché un'assegnazione di ufficio non può esistere senza un insegnante - InstructorID non è nullable). Quando un'entità Instructor ha un'entità correlata OfficeAssignment , ogni entità avrà un riferimento all'altro nella relativa proprietà di navigazione.

È possibile inserire un [Required] attributo nella proprietà di navigazione Instructor per specificare che deve essere presente un insegnante correlato, ma non è necessario farlo perché la chiave esterna InstructorID (che è anche la chiave di questa tabella) non è nullable.

Modificare l'entità Course

In Models\Course.cs sostituire il codice aggiunto in precedenza con il codice seguente:

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

namespace ContosoUniversity.Models
{
   public class Course
   {
      [DatabaseGenerated(DatabaseGeneratedOption.None)]
      [Display(Name = "Number")]
      public int CourseID { get; set; }

      [StringLength(50, MinimumLength = 3)]
      public string Title { get; set; }

      [Range(0, 5)]
      public int Credits { get; set; }

      public int DepartmentID { get; set; }

      public virtual Department Department { get; set; }
      public virtual ICollection<Enrollment> Enrollments { get; set; }
      public virtual ICollection<Instructor> Instructors { get; set; }
   }
}

L'entità course ha una proprietà DepartmentID di chiave esterna che punta all'entità correlata Department e ha una Department proprietà di navigazione. In Entity Framework non è necessario aggiungere una proprietà di chiave esterna al modello di dati se è disponibile una proprietà di navigazione per un'entità correlata. Ef crea automaticamente chiavi esterne nel database ovunque siano necessarie. Tuttavia il fatto di avere la chiave esterna nel modello di dati può rendere più semplici ed efficienti gli aggiornamenti. Ad esempio, quando si recupera un'entità corso da modificare, l'entità Department è Null se non viene caricata, quindi quando si aggiorna l'entità corso, è necessario recuperare prima l'entità Department . Quando la proprietà DepartmentID della chiave esterna è inclusa nel modello di dati, non è necessario recuperare l'entità prima dell'aggiornamento Department .

Attributo DatabaseGenerated

L'attributo DatabaseGenerated con il parametro None nella CourseID proprietà specifica che i valori di chiave primaria vengono forniti dall'utente anziché generati dal database.

[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

Per impostazione predefinita, Entity Framework presuppone che i valori di chiave primaria vengano generati dal database. Questa è la condizione ottimale nella maggior parte degli scenari. Per le entità, tuttavia Course , si userà un numero di corso specificato dall'utente, ad esempio una serie 1000 per un reparto, una serie 2000 per un altro reparto e così via.

Proprietà chiave esterna e navigazione

Le proprietà della chiave esterna e le proprietà di navigazione nell'entità Course riflettono le relazioni seguenti:

  • Un corso viene assegnato a un solo reparto, pertanto sono presenti una chiave esterna DepartmentID e una proprietà di navigazione Department per i motivi indicati in precedenza.

    public int DepartmentID { get; set; }
    public virtual Department Department { get; set; }
    
  • Un corso può avere un numero qualsiasi di studenti iscritti, pertanto la proprietà di navigazione Enrollments è una raccolta:

    public virtual ICollection<Enrollment> Enrollments { get; set; }
    
  • Un corso può essere impartito da più insegnanti, pertanto la proprietà di navigazione Instructors è una raccolta:

    public virtual ICollection<Instructor> Instructors { get; set; }
    

Creare l'entità Department

Creare Modelli\Department.cs con il codice seguente:

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

namespace ContosoUniversity.Models
{
   public class Department
   {
      public int DepartmentID { get; set; }

      [StringLength(50, MinimumLength=3)]
      public string Name { get; set; }

      [DataType(DataType.Currency)]
      [Column(TypeName = "money")]
      public decimal Budget { get; set; }

      [DataType(DataType.Date)]
      [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
      [Display(Name = "Start Date")]
      public DateTime StartDate { get; set; }

      public int? InstructorID { get; set; }

      public virtual Instructor Administrator { get; set; }
      public virtual ICollection<Course> Courses { get; set; }
   }
}

Attributo Column

In precedenza è stato usato l'attributo Column per modificare il mapping dei nomi di colonna. Nel codice per l'entità l'attributo Department viene usato per modificare il Column mapping dei tipi di dati SQL in modo che la colonna venga definita usando il tipo money di SQL Server nel database:

[Column(TypeName="money")]
public decimal Budget { get; set; }

Il mapping delle colonne in genere non è obbligatorio, perché Entity Framework sceglie in genere il tipo di dati DI SQL Server appropriato in base al tipo CLR definito per la proprietà. Il tipo CLR decimal esegue il mapping a un tipo SQL Server decimal. In questo caso, tuttavia, si sa che la colonna conterrà gli importi in valuta e il tipo di dati money è più appropriato. Per altre informazioni sui tipi di dati CLR e su come corrispondono ai tipi di dati di SQL Server, vedere SqlClient per Entity FrameworkTypes.

Proprietà chiave esterna e navigazione

Le proprietà di chiave esterna e le proprietà di navigazione riflettono le relazioni seguenti:

  • Un reparto può avere o meno un amministratore e un amministratore è sempre un insegnante. Di conseguenza, la InstructorID proprietà viene inclusa come chiave esterna per l'entità Instructor e un punto interrogativo viene aggiunto dopo la int designazione del tipo per contrassegnare la proprietà come nullable. La proprietà di navigazione è denominata Administrator ma contiene un'entità Instructor :

    public int? InstructorID { get; set; }
    public virtual Instructor Administrator { get; set; }
    
  • Un reparto può avere molti corsi, quindi c'è una Courses proprietà di navigazione:

    public virtual ICollection<Course> Courses { get; set; }
    

    Nota

    Per convenzione, Entity Framework consente l'eliminazione a catena per le chiavi esterne non nullable e per le relazioni molti-a-molti. Ciò può determinare regole di eliminazione a catena circolari, che generano un'eccezione quando si prova ad aggiungere una migrazione. Ad esempio, se la Department.InstructorID proprietà non è stata definita come nullable, si otterrà il messaggio di eccezione seguente: "La relazione referenziale comporterà un riferimento ciclico non consentito". Se le regole business richiedono InstructorID la proprietà non nullable, è necessario usare l'istruzione API Fluent seguente per disabilitare l'eliminazione a catena nella relazione:

modelBuilder.Entity().HasRequired(d => d.Administrator).WithMany().WillCascadeOnDelete(false);

Modificare l'entità Enrollment

In Models\Enrollment.cs sostituire il codice aggiunto in precedenza con il codice seguente

using System.ComponentModel.DataAnnotations;

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

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

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

Proprietà chiave esterna e navigazione

Le proprietà di chiave esterna e le proprietà di navigazione riflettono le relazioni seguenti:

  • Un record di iscrizione è relativo a un singolo corso, pertanto sono presenti una proprietà di chiave esterna CourseID e una proprietà di navigazione Course:

    public int CourseID { get; set; }
    public virtual Course Course { get; set; }
    
  • Un record di iscrizione è relativo a un singolo studente, pertanto sono presenti una proprietà di chiave esterna StudentID e una proprietà di navigazione Student:

    public int StudentID { get; set; }
    public virtual Student Student { get; set; }
    

Relazioni molti-a-molti

Esiste una relazione molti-a-molti tra le Student entità e Course e l'entità Enrollment funziona come tabella di join molti-a-molti con payload nel database. Ciò significa che la Enrollment tabella contiene dati aggiuntivi oltre alle chiavi esterne per le tabelle unite (in questo caso, una chiave primaria e una Grade proprietà).

La figura seguente illustra l'aspetto di queste relazioni in un diagramma di entità. Questo diagramma è stato generato usando Entity Framework Power Tools. La creazione del diagramma non fa parte dell'esercitazione, ma viene usata qui come illustrazione.

Da studente Course_many a many_relationship

Ogni riga della relazione inizia con un 1 e termina con un asterisco (*), per indicare una relazione uno-a-molti.

Se la Enrollment tabella non include informazioni di grado, sarebbe necessario contenere solo le due chiavi CourseID esterne e StudentID. In tal caso, corrisponderebbe a una tabella join molti-a-molti senza payload (o una tabella di join pura) nel database e non sarebbe necessario crearne affatto una classe modello. Le Instructor entità e Course hanno quel tipo di relazione molti-a-molti e, come si può notare, non esiste una classe di entità tra di esse:

Da istruttore Course_many a many_relationship

Nel database è tuttavia necessaria una tabella join, come illustrato nel diagramma di database seguente:

Da insegnante Course_many a many_relationship_tables

Entity Framework crea automaticamente la CourseInstructor tabella e la si legge e la si aggiorna indirettamente leggendo e aggiornando le Instructor.Courses proprietà di navigazione e Course.Instructors .

Diagramma della relazione di entità

La figura seguente visualizza il diagramma creato da Entity Framework Power Tools per il modello School completato.

School_data_model_diagram

Oltre alle linee di relazione molti-a-molti (* a *) e alle linee di relazione uno-a-molti (da 1 a *), è possibile vedere qui la linea di relazione uno-a-zero-o-uno (da 1 a 0,.1) tra le Instructor entità e OfficeAssignment e la riga di relazione zero-o-uno-a-molti (da 0..1 a *) tra le entità Instructor e Department.

Aggiungere codice al contesto del database

Successivamente si aggiungeranno le nuove entità alla SchoolContext classe e si personalizzano alcuni dei mapping usando chiamate API Fluent. L'API è "fluent" perché viene spesso usata tramite la stringa di una serie di chiamate di metodo in un'unica istruzione, come nell'esempio seguente:

modelBuilder.Entity<Course>()
     .HasMany(c => c.Instructors).WithMany(i => i.Courses)
     .Map(t => t.MapLeftKey("CourseID")
         .MapRightKey("InstructorID")
         .ToTable("CourseInstructor"));

In questa esercitazione si userà l'API Fluent solo per il mapping di database che non è possibile eseguire con gli attributi. È tuttavia possibile usare l'API Fluent anche per specificare la maggior parte delle regole di formattazione, convalida e mapping specificabili tramite gli attributi. Alcuni attributi quali MinimumLength non possono essere applicati con l'API Fluent. Come accennato in precedenza, MinimumLength non modifica lo schema, ma applica solo una regola di convalida lato client e server

Alcuni sviluppatori preferiscono usare esclusivamente l'API Fluent in modo che possano mantenere "pulite" le classi di entità. È possibile combinare attributi e API Fluent se si vuole e sono disponibili alcune personalizzazioni che possono essere eseguite solo usando l'API Fluent, ma in generale la procedura consigliata consiste nel scegliere uno di questi due approcci e usarli in modo coerente il più possibile.

Per aggiungere le nuove entità al modello di dati ed eseguire il mapping del database che non è stato eseguito usando gli attributi, sostituire il codice in DAL\SchoolContext.cs con il codice seguente:

using ContosoUniversity.Models;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace ContosoUniversity.DAL
{
   public class SchoolContext : DbContext
   {
      public DbSet<Course> Courses { get; set; }
      public DbSet<Department> Departments { get; set; }
      public DbSet<Enrollment> Enrollments { get; set; }
      public DbSet<Instructor> Instructors { get; set; }
      public DbSet<Student> Students { get; set; }
      public DbSet<OfficeAssignment> OfficeAssignments { get; set; }

      protected override void OnModelCreating(DbModelBuilder modelBuilder)
      {
         modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

         modelBuilder.Entity<Course>()
             .HasMany(c => c.Instructors).WithMany(i => i.Courses)
             .Map(t => t.MapLeftKey("CourseID")
                 .MapRightKey("InstructorID")
                 .ToTable("CourseInstructor"));
      }
   }
}

La nuova istruzione nel metodo OnModelCreating configura la tabella join molti-a-molti:

  • Per la relazione molti-a-molti tra le Instructor entità e Course , il codice specifica i nomi di tabella e colonna per la tabella di join. Code First può configurare la relazione molti-a-molti senza questo codice, ma se non viene chiamata, si otterranno nomi predefiniti, ad InstructorInstructorID esempio per la InstructorID colonna.

    modelBuilder.Entity<Course>()
        .HasMany(c => c.Instructors).WithMany(i => i.Courses)
        .Map(t => t.MapLeftKey("CourseID")
            .MapRightKey("InstructorID")
            .ToTable("CourseInstructor"));
    

Il codice seguente fornisce un esempio di come è possibile usare l'API Fluent anziché gli attributi per specificare la relazione tra le Instructor entità e OfficeAssignment :

modelBuilder.Entity<Instructor>()
    .HasOptional(p => p.OfficeAssignment).WithRequired(p => p.Instructor);

Per informazioni sulle istruzioni "Fluent API" in background, vedere il post di blog sull'API Fluent.

Eseguire il seeding del database con dati di test

Sostituire il codice nel file Migrations\Configuration.cs con il codice seguente per fornire i dati di inizializzazione per le nuove entità create.

namespace ContosoUniversity.Migrations
{
    using ContosoUniversity.Models;
    using ContosoUniversity.DAL;
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;
    
    internal sealed class Configuration : DbMigrationsConfiguration<SchoolContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(SchoolContext context)
        {
            var students = new List<Student>
            {
                new Student { FirstMidName = "Carson",   LastName = "Alexander", 
                    EnrollmentDate = DateTime.Parse("2010-09-01") },
                new Student { FirstMidName = "Meredith", LastName = "Alonso",    
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Arturo",   LastName = "Anand",     
                    EnrollmentDate = DateTime.Parse("2013-09-01") },
                new Student { FirstMidName = "Gytis",    LastName = "Barzdukas", 
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Yan",      LastName = "Li",        
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Peggy",    LastName = "Justice",   
                    EnrollmentDate = DateTime.Parse("2011-09-01") },
                new Student { FirstMidName = "Laura",    LastName = "Norman",    
                    EnrollmentDate = DateTime.Parse("2013-09-01") },
                new Student { FirstMidName = "Nino",     LastName = "Olivetto",  
                    EnrollmentDate = DateTime.Parse("2005-09-01") }
            };

            students.ForEach(s => context.Students.AddOrUpdate(p => p.LastName, s));
            context.SaveChanges();

            var instructors = new List<Instructor>
            {
                new Instructor { FirstMidName = "Kim",     LastName = "Abercrombie", 
                    HireDate = DateTime.Parse("1995-03-11") },
                new Instructor { FirstMidName = "Fadi",    LastName = "Fakhouri",    
                    HireDate = DateTime.Parse("2002-07-06") },
                new Instructor { FirstMidName = "Roger",   LastName = "Harui",       
                    HireDate = DateTime.Parse("1998-07-01") },
                new Instructor { FirstMidName = "Candace", LastName = "Kapoor",      
                    HireDate = DateTime.Parse("2001-01-15") },
                new Instructor { FirstMidName = "Roger",   LastName = "Zheng",      
                    HireDate = DateTime.Parse("2004-02-12") }
            };
            instructors.ForEach(s => context.Instructors.AddOrUpdate(p => p.LastName, s));
            context.SaveChanges();

            var departments = new List<Department>
            {
                new Department { Name = "English",     Budget = 350000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Abercrombie").ID },
                new Department { Name = "Mathematics", Budget = 100000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Fakhouri").ID },
                new Department { Name = "Engineering", Budget = 350000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Harui").ID },
                new Department { Name = "Economics",   Budget = 100000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Kapoor").ID }
            };
            departments.ForEach(s => context.Departments.AddOrUpdate(p => p.Name, s));
            context.SaveChanges();

            var courses = new List<Course>
            {
                new Course {CourseID = 1050, Title = "Chemistry",      Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 1045, Title = "Calculus",       Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 3141, Title = "Trigonometry",   Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 2021, Title = "Composition",    Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 2042, Title = "Literature",     Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
            };
            courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
            context.SaveChanges();

            var officeAssignments = new List<OfficeAssignment>
            {
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID, 
                    Location = "Smith 17" },
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Harui").ID, 
                    Location = "Gowan 27" },
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID, 
                    Location = "Thompson 304" },
            };
            officeAssignments.ForEach(s => context.OfficeAssignments.AddOrUpdate(p => p.InstructorID, s));
            context.SaveChanges();

            AddOrUpdateInstructor(context, "Chemistry", "Kapoor");
            AddOrUpdateInstructor(context, "Chemistry", "Harui");
            AddOrUpdateInstructor(context, "Microeconomics", "Zheng");
            AddOrUpdateInstructor(context, "Macroeconomics", "Zheng");

            AddOrUpdateInstructor(context, "Calculus", "Fakhouri");
            AddOrUpdateInstructor(context, "Trigonometry", "Harui");
            AddOrUpdateInstructor(context, "Composition", "Abercrombie");
            AddOrUpdateInstructor(context, "Literature", "Abercrombie");

            context.SaveChanges();

            var enrollments = new List<Enrollment>
            {
                new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").ID, 
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID, 
                    Grade = Grade.A 
                },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID, 
                    Grade = Grade.C 
                 },                            
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID, 
                    Grade = Grade.B
                 },
                 new Enrollment { 
                     StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment { 
                     StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Composition" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Anand").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Anand").ID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
                    Grade = Grade.B         
                 },
                new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
                    Grade = Grade.B         
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Li").ID,
                    CourseID = courses.Single(c => c.Title == "Composition").CourseID,
                    Grade = Grade.B         
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Justice").ID,
                    CourseID = courses.Single(c => c.Title == "Literature").CourseID,
                    Grade = Grade.B         
                 }
            };

            foreach (Enrollment e in enrollments)
            {
                var enrollmentInDataBase = context.Enrollments.Where(
                    s =>
                         s.Student.ID == e.StudentID &&
                         s.Course.CourseID == e.CourseID).SingleOrDefault();
                if (enrollmentInDataBase == null)
                {
                    context.Enrollments.Add(e);
                }
            }
            context.SaveChanges();
        }

        void AddOrUpdateInstructor(SchoolContext context, string courseTitle, string instructorName)
        {
            var crs = context.Courses.SingleOrDefault(c => c.Title == courseTitle);
            var inst = crs.Instructors.SingleOrDefault(i => i.LastName == instructorName);
            if (inst == null)
                crs.Instructors.Add(context.Instructors.Single(i => i.LastName == instructorName));
        }
    }
}

Come si è visto nella prima esercitazione, la maggior parte di questo codice semplicemente aggiorna o crea nuovi oggetti entità e carica i dati di esempio in proprietà come richiesto per il test. Si noti tuttavia che l'entità Course , che ha una relazione molti-a-molti con l'entità Instructor , viene gestita:

var courses = new List<Course>
{
    new Course {CourseID = 1050, Title = "Chemistry",      Credits = 3,
      DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
      Instructors = new List<Instructor>() 
    },
    ...
};
courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
context.SaveChanges();

Quando si crea un Course oggetto , si inizializza la Instructors proprietà di navigazione come raccolta vuota usando il codice Instructors = new List<Instructor>(). In questo modo è possibile aggiungere Instructor entità correlate a questa Course operazione usando il Instructors.Add metodo . Se non è stato creato un elenco vuoto, non sarà possibile aggiungere queste relazioni, perché la Instructors proprietà sarebbe null e non avrebbe un Add metodo. È anche possibile aggiungere l'inizializzazione dell'elenco al costruttore.

Aggiungere una migrazione

Dal PMC immettere il add-migration comando (non eseguire ancora il update-database comando):

add-Migration ComplexDataModel

Se si prova a eseguire il comando update-database in questa fase (evitare di farlo), si ottiene il seguente errore:

The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK_dbo.Course_dbo.Department_DepartmentID". The conflict occurred in database "ContosoUniversity", table "dbo.Department", column 'DepartmentID' (L'istruzione ALTER TABLE è in conflitto con il vincolo FOREIGN KEY "FK_dbo.Course_dbo.Department_DepartmentID". Il conflitto si è verificato nella colonna 'DepartmentID' della tabella "dbo.Department"del database "ContosoUniversity").

In alcuni casi, quando si eseguono migrazioni con dati esistenti, è necessario inserire dati stub nel database per soddisfare i vincoli di chiave esterna ed è ciò che è necessario fare ora. Il codice generato nel metodo ComplexDataModel Up aggiunge una chiave esterna non nullable DepartmentID alla Course tabella. Poiché sono già presenti righe nella Course tabella durante l'esecuzione del codice, l'operazione AddColumn avrà esito negativo perché SQL Server non conosce il valore da inserire nella colonna che non può essere Null. È quindi necessario modificare il codice per assegnare alla nuova colonna un valore predefinito e creare un reparto stub denominato "Temp" per fungere da reparto predefinito. Di conseguenza, tutte le righe esistenti Course saranno correlate al reparto "Temp" dopo l'esecuzione del Up metodo. È possibile correlarli ai reparti corretti nel Seed metodo .

Modificare il <timestamp>_ComplexDataModel.cs file, impostare come commento la riga di codice che aggiunge la colonna DepartmentID alla tabella Course e aggiungere il codice evidenziato seguente (anche la riga commentata è evidenziata):

CreateTable(
        "dbo.CourseInstructor",
        c => new
            {
                CourseID = c.Int(nullable: false),
                InstructorID = c.Int(nullable: false),
            })
        .PrimaryKey(t => new { t.CourseID, t.InstructorID })
        .ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: true)
        .ForeignKey("dbo.Instructor", t => t.InstructorID, cascadeDelete: true)
        .Index(t => t.CourseID)
        .Index(t => t.InstructorID);

    // Create  a department for course to point to.
    Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
    //  default value for FK points to department created above.
    AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false, defaultValue: 1)); 
    //AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false));

    AlterColumn("dbo.Course", "Title", c => c.String(maxLength: 50));

Quando viene eseguito, il Seed metodo inserisce righe nella Department tabella e correla le righe esistenti Course a quelle nuove Department righe. Se non sono stati aggiunti corsi nell'interfaccia utente, non è più necessario il reparto "Temp" o il valore predefinito nella Course.DepartmentID colonna. Per consentire la possibilità che un utente abbia aggiunto corsi usando l'applicazione, è anche necessario aggiornare il codice del Seed metodo per assicurarsi che tutte le Course righe (non solo quelle inserite dalle esecuzioni precedenti del Seed metodo) abbiano valori validi DepartmentID prima di rimuovere il valore predefinito dalla colonna ed eliminare il reparto "Temp".

Aggiornare il database

Dopo aver completato la modifica del <timestamp>_ComplexDataModel.cs file, immettere il update-database comando in PMC per eseguire la migrazione.

update-database

Nota

È possibile ottenere altri errori durante la migrazione dei dati e apportare modifiche allo schema. Se si verificano errori di migrazione che non si riesce a risolvere, è possibile modificare il nome del database nella stringa di connessione o eliminare il database. L'approccio più semplice consiste nel rinominare il database nel file Web.config . L'esempio seguente mostra il nome modificato in CU_Test:

<add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=CU_Test;Integrated Security=SSPI;" 
      providerName="System.Data.SqlClient" />

Con un nuovo database, non sono presenti dati di cui eseguire la migrazione e il update-database comando è molto più probabile che venga completato senza errori. Per istruzioni su come eliminare il database, vedere Come eliminare un database da Visual Studio 2012.

In caso di errore, è possibile provare a inizializzare nuovamente il database immettendo il comando seguente in PMC:

update-database -TargetMigration:0

Aprire il database in Esplora server come in precedenza ed espandere il nodo Tabelle per verificare che tutte le tabelle siano state create. (Se hai ancora Esplora server aperto dall'ora precedente, fare clic sul pulsante Aggiorna.

Screenshot che mostra la finestra Esplora server. La cartella Tabelle in Contesto dell'istituto di istruzione è aperta.

Non è stata creata una classe modello per la CourseInstructor tabella. Come spiegato in precedenza, si tratta di una tabella join per la relazione molti-a-molti tra le Instructor entità e Course .

Fare clic con il pulsante destro del mouse sulla CourseInstructor tabella e selezionare Mostra dati tabella per verificare che siano presenti dati in esso contenuti in seguito Instructor alle entità aggiunte alla Course.Instructors proprietà di navigazione.

Table_data_in_CourseInstructor_table

Ottenere il codice

Scaricare il progetto completato

Risorse aggiuntive

I collegamenti ad altre risorse di Entity Framework sono disponibili in ASP.NET Accesso ai dati - Risorse consigliate.

Passaggi successivi

In questa esercitazione:

  • Personalizzato il modello di dati
  • Entità Student aggiornata
  • Creazione dell'entità Instructor
  • Creazione dell'entità OfficeAssignment
  • Modifica dell'entità Course
  • Creazione dell'entità Department
  • Modifica dell'entità Enrollment
  • Aggiunta del codice al contesto del database
  • Seeding del database con dati di test
  • Aggiunta di una migrazione
  • Aggiornamento del database

Passare all'articolo successivo per informazioni su come leggere e visualizzare i dati correlati caricati da Entity Framework nelle proprietà di navigazione.