Condividi tramite


Creazione di un modello di dati più complesso per un'applicazione MVC ASP.NET (4 di 10)

di Tom Dykstra

L'applicazione Web di esempio Contoso University illustra come creare ASP.NET applicazioni MVC 4 usando Entity Framework 5 Code First e Visual Studio 2012. Per informazioni sulla serie di esercitazioni, vedere la prima esercitazione della serie.

Nota

Se si verifica un problema che non è possibile risolvere, scaricare il capitolo completato e provare a riprodurre il problema. In genere è possibile trovare la soluzione al problema confrontando il codice con il codice completato. Per alcuni errori comuni e come risolverli, vedere Errori e soluzioni alternative.

Nelle esercitazioni precedenti è stato usato un modello di dati semplice composto da tre entità. In questa esercitazione verranno aggiunte altre entità e relazioni e si personalizza il modello di dati specificando regole di formattazione, convalida e mapping del database. Verranno visualizzati 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

Personalizzare il modello di dati usando gli attributi

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 StudentID { 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)]
public DateTime EnrollmentDate { get; set; }

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 calendario, il simbolo di valuta appropriato per le impostazioni locali, i collegamenti di posta elettronica 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 corretto per eseguire il rendering dei dati (displayFormat se usato da solo 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.

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 e messaggi di convalida dei dati usando attributi. 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 StudentID { 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 MaxLengthOnNames comando crea un file denominato <timeStamp>_MaxLengthOnNames.cs. Questo file contiene codice che aggiornerà il database in modo che corrisponda al modello di dati corrente. Il timestamp anteporto al nome file delle migrazioni viene usato da Entity Framework per ordinare le migrazioni. Dopo aver creato più migrazioni, se si elimina il database o si distribuisce il progetto tramite Migrazioni, 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. Non appena si superano i 50 caratteri, la convalida lato client visualizza immediatamente un messaggio di errore.

errore val sul lato client

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à.

Aggiungere un'istruzione using per System.ComponentModel.DataAnnotations.Schema e l'attributo del nome della 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 StudentID { 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 (Esplora database se si usa Express for Web), fare doppio clic sulla tabella Student .

Screenshot che mostra la tabella Student in Esplora server.

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.

Screenshot che mostra la tabella Student in Esplora server. La riga First Name (Nome) nello screenshot precedente è stata modificata in modo da leggere come First Mid Name (Nome intermedio).

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

Nota

Se si tenta di compilare prima di completare la creazione di tutte queste classi di entità, è possibile che vengano visualizzati errori del compilatore.

Creare l'entità Instructor

Instructor_entity

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 InstructorID { 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; }

        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 Implementazione dell'ereditarietà più avanti in questa serie si eseguirà il refactoring usando l'ereditarietà per eliminare questa ridondanza.

Attributi obbligatori e visualizzati

Gli attributi della LastName proprietà specificano che si tratta di un campo obbligatorio, che la didascalia della casella di testo deve essere "Cognome" (anziché il nome della proprietà, che sarebbe "LastName" senza spazio) e che il valore non può superare i 50 caratteri.

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

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. L'attributo Required non è necessario per i tipi valore, ad esempio DateTime, int, double e float. I tipi valore non possono essere assegnati a un valore Null, pertanto sono intrinsecamente necessari. È possibile rimuovere l'attributo Required e sostituirlo con un parametro di lunghezza minima per l'attributo StringLength :

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

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

public class Instructor
{
   public int InstructorID { 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; }

   public string FullName
   {
      get { return LastName + ", " + FirstMidName; }
   }

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

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.

public string FullName
{
    get { return LastName + ", " + FirstMidName; }
}

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à. 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 ICollection<Course> Courses { get; set; }
public virtual OfficeAssignment OfficeAssignment { get; set; }

Creare l'entità OfficeAssignment

OfficeAssignment_entity

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 verrà illustrato 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

Course_entity

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

      [Display(Name = "Department")]
      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; }
    

Creazione dell'entità reparto

Department_entity

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)]
      public DateTime StartDate { get; set; }

      [Display(Name = "Administrator")]
      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.

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ò comportare regole di eliminazione circolare a catena, che causeranno un'eccezione quando viene eseguito il codice dell'inizializzatore. Ad esempio, se la Department.InstructorID proprietà non è stata definita come nullable, si otterrà il messaggio di eccezione seguente quando viene eseguito l'inizializzatore: "La relazione referenziale comporterà un riferimento ciclico non consentito". Se le regole business richiedono InstructorID proprietà non nullable, è necessario usare l'API Fluent seguente per disabilitare l'eliminazione a catena nella relazione:

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

Modifica dell'entità Student

Student_entity

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 StudentID { get; set; }

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

      [StringLength(50, MinimumLength = 1, 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)]
      [Display(Name = "Enrollment Date")]
      public DateTime EnrollmentDate { get; set; }

      public string FullName
      {
         get { return LastName + ", " + FirstMidName; }
      }

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

Entità di registrazione

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 dell'entità che visualizza le relazioni

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.

Personalizzare il modello di dati aggiungendo 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.

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.

Assegnare valori di inizializzazione al 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 System;
   using System.Collections.Generic;
   using System.Data.Entity;
   using System.Data.Entity.Migrations;
   using System.Linq;
   using ContosoUniversity.Models;
   using ContosoUniversity.DAL;

   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").InstructorID },
                new Department { Name = "Mathematics", Budget = 100000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Fakhouri").InstructorID },
                new Department { Name = "Engineering", Budget = 350000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Harui").InstructorID },
                new Department { Name = "Economics",   Budget = 100000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Kapoor").InstructorID }
            };
         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").InstructorID, 
                    Location = "Smith 17" },
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Harui").InstructorID, 
                    Location = "Gowan 27" },
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Kapoor").InstructorID, 
                    Location = "Thompson 304" },
            };
         officeAssignments.ForEach(s => context.OfficeAssignments.AddOrUpdate(p => p.Location, 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").StudentID, 
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID, 
                    Grade = Grade.A 
                },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").StudentID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID, 
                    Grade = Grade.C 
                 },                            
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").StudentID,
                    CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID, 
                    Grade = Grade.B
                 },
                 new Enrollment { 
                     StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
                    CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment { 
                     StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
                    CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
                    CourseID = courses.Single(c => c.Title == "Composition" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Anand").StudentID,
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Anand").StudentID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
                    Grade = Grade.B         
                 },
                new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Barzdukas").StudentID,
                    CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
                    Grade = Grade.B         
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Li").StudentID,
                    CourseID = courses.Single(c => c.Title == "Composition").CourseID,
                    Grade = Grade.B         
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Justice").StudentID,
                    CourseID = courses.Single(c => c.Title == "Literature").CourseID,
                    Grade = Grade.B         
                 }
            };

         foreach (Enrollment e in enrollments)
         {
            var enrollmentInDataBase = context.Enrollments.Where(
                s =>
                     s.Student.StudentID == 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,
       Department = departments.Single( s => s.Name == "Engineering"),
       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 e aggiornare il database

Dal PMC immettere il add-migration comando :

PM> add-Migration Chap4

Se si tenta di aggiornare il database a questo punto, verrà visualizzato l'errore seguente:

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").

Modificare il <timestamp>_Chap4.cs file e apportare le modifiche di codice seguenti (si aggiungerà un'istruzione SQL e si modificherà un'istruzione AddColumn ):

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));
    AddForeignKey("dbo.Course", "DepartmentID", "dbo.Department", "DepartmentID", cascadeDelete: true);
    CreateIndex("dbo.Course", "DepartmentID");
}

public override void Down()
{

Assicurarsi di impostare come commento o eliminare la riga esistente AddColumn quando si aggiunge la nuova riga oppure si riceverà un errore quando si immette il update-database comando.

In alcuni casi, quando si eseguono migrazioni con dati esistenti, è necessario inserire dati stub nel database per soddisfare i vincoli di chiave esterna e questo è ciò che si sta eseguendo. Il codice generato aggiunge una chiave esterna non nullable DepartmentID alla Course tabella. Se 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. Di conseguenza, il codice è stato modificato per assegnare alla nuova colonna un valore predefinito ed è stato creato un reparto stub denominato "Temp" per fungere da reparto predefinito. Di conseguenza, se sono presenti righe esistenti Course durante l'esecuzione di questo codice, verranno tutte correlate al reparto "Temp".

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".

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

Nota

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

<add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=CU_Test;
      Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\CU_Test.mdf" 
      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.

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 il database di Esplora server. Il nodo Tabelle viene espanso.

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

Riepilogo

Ora sono presenti un modello di dati più complesso e il database corrispondente. Nell'esercitazione seguente verranno fornite altre informazioni sui diversi modi per accedere ai dati correlati.

I collegamenti ad altre risorse di Entity Framework sono disponibili nella mappa del contenuto di accesso ai dati ASP.NET.