Entity Framework - Annotazioni di dati

DataAnnotations viene utilizzato per configurare le classi che evidenzieranno le configurazioni più comunemente necessarie. DataAnnotations sono comprese anche da numerose applicazioni .NET, come ASP.NET MVC che consente a queste applicazioni di sfruttare le stesse annotazioni per le convalide lato client. Gli attributi DataAnnotation sovrascrivono le convenzioni CodeFirst predefinite.

System.ComponentModel.DataAnnotations include i seguenti attributi che influiscono sul nullability o sulla dimensione della colonna.

  • Key
  • Timestamp
  • ConcurrencyCheck
  • Required
  • MinLength
  • MaxLength
  • StringLength

System.ComponentModel.DataAnnotations.Schema lo spazio dei nomi include i seguenti attributi che influiscono sullo schema del database.

  • Table
  • Column
  • Index
  • ForeignKey
  • NotMapped
  • InverseProperty

Chiave

Entity Framework si basa su ogni entità che ha un valore chiave che utilizza per tenere traccia delle entità. Una delle convenzioni da cui dipende Code First è il modo in cui implica quale proprietà è la chiave in ciascuna delle classi Code First.

  • La convenzione consiste nel cercare una proprietà denominata "Id" o una che combini il nome della classe e "Id", ad esempio "StudentId".

  • La proprietà verrà mappata a una colonna chiave primaria nel database.

  • Le classi Studente, Corso e Iscrizione seguono questa convenzione.

Supponiamo ora che la classe Student abbia utilizzato il nome StdntID invece di ID. Quando Code First non trova una proprietà che corrisponde a questa convenzione, verrà generata un'eccezione a causa del requisito di Entity Framework che richiede una proprietà chiave. È possibile utilizzare l'annotazione chiave per specificare quale proprietà deve essere utilizzata come EntityKey.

Diamo un'occhiata al seguente codice di una classe Student che contiene StdntID, ma non segue la convenzione Code First predefinita. Quindi, per gestire questo, viene aggiunto un attributo Key che lo renderà una chiave primaria.

public class Student {

   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Quando si esegue l'applicazione e si guarda nel database in SQL Server Explorer, si vedrà che la chiave primaria è ora StdntID nella tabella Studenti.

Entity Framework supporta anche chiavi composite. Composite keyssono anche chiavi primarie costituite da più di una proprietà. Ad esempio, hai una classe DrivingLicense la cui chiave primaria è una combinazione di LicenseNumber e IssuingCountry.

public class DrivingLicense {

   [Key, Column(Order = 1)]
   public int LicenseNumber { get; set; }
   [Key, Column(Order = 2)]
   public string IssuingCountry { get; set; }
   public DateTime Issued { get; set; }
   public DateTime Expires { get; set; }
}

Quando si dispone di chiavi composite, Entity Framework richiede di definire un ordine delle proprietà delle chiavi. Puoi farlo utilizzando l'annotazione Colonna per specificare un ordine.

Timestamp

Code First tratterà le proprietà Timestamp come le proprietà ConcurrencyCheck, ma garantirà anche che il campo del database che il codice genera per primo non sia annullabile.

  • È più comune utilizzare i campi rowversion o timestamp per il controllo della concorrenza.

  • Anziché utilizzare l'annotazione ConcurrencyCheck, è possibile utilizzare l'annotazione TimeStamp più specifica purché il tipo della proprietà sia un array di byte.

  • Puoi avere solo una proprietà timestamp in una data classe.

Diamo un'occhiata a un semplice esempio aggiungendo la proprietà TimeStamp alla classe Course:

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   public int Credits { get; set; }
   [Timestamp]
   public byte[] TStamp { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Come puoi vedere nell'esempio precedente, l'attributo Timestamp viene applicato alla proprietà Byte [] della classe Course. Quindi, Code First creerà una colonna timestamp TStampnella tabella Courses.

ConcurrencyCheck

L'annotazione ConcurrencyCheck consente di contrassegnare una o più proprietà da utilizzare per il controllo della concorrenza nel database quando un utente modifica o elimina un'entità. Se hai lavorato con EF Designer, questo è in linea con l'impostazione di ConcurrencyMode di una proprietà su Fixed.

Diamo un'occhiata a un semplice esempio di come funziona ConcurrencyCheck aggiungendolo alla proprietà Title nella classe Course.

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   public string Title { get; set; }
   public int Credits { get; set; }
   [Timestamp, DataType("timestamp")]
   public byte[] TimeStamp { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Nella classe Course precedente, l'attributo ConcurrencyCheck viene applicato alla proprietà Title esistente. Ora, Code First includerà la colonna Title nel comando di aggiornamento per verificare la concorrenza ottimistica come mostrato nel codice seguente.

exec sp_executesql N'UPDATE [dbo].[Courses]
   SET [Title] = @0
   WHERE (([CourseID] = @1) AND ([Title] = @2))
   ',N'@0 nvarchar(max) ,@1 int,@2 nvarchar(max) ',@0=N'Maths',@1=1,@2=N'Calculus'
go

Annotazione richiesta

L'annotazione Required indica a EF che è richiesta una particolare proprietà. Diamo un'occhiata alla seguente classe Student in cui l'ID richiesto viene aggiunto alla proprietà FirstMidName. L'attributo obbligatorio costringerà EF a garantire che la proprietà contenga dati.

public class Student {

   [Key]
   public int StdntID { get; set; }

   [Required]
   public string LastName { get; set; }

   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Come si vede nell'esempio precedente, l'attributo Required viene applicato a FirstMidName e LastName. Quindi, Code First creerà le colonne FirstMidName e LastName NOT NULL nella tabella Students come mostrato nell'immagine seguente.

Lunghezza massima

L'attributo MaxLength consente di specificare ulteriori convalide di proprietà. Può essere applicato a una proprietà di tipo stringa o matrice di una classe di dominio. EF Code First imposterà la dimensione di una colonna come specificato nell'attributo MaxLength.

Diamo un'occhiata alla seguente classe Course in cui l'attributo MaxLength (24) è applicato alla proprietà Title.

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   [MaxLength(24)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Quando si esegue l'applicazione precedente, Code First creerà un titolo di colonna nvarchar (24) nella tabella CourseId come mostrato nell'immagine seguente.

Quando l'utente imposta il titolo che contiene più di 24 caratteri, EF lancerà EntityValidationError.

MinLength

L'attributo MinLength ti consente anche di specificare ulteriori convalide di proprietà, proprio come hai fatto con MaxLength. L'attributo MinLength può essere utilizzato anche con l'attributo MaxLength come mostrato nel codice seguente.

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   [MaxLength(24) , MinLength(5)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

EF genererà EntityValidationError, se imposti un valore della proprietà Title inferiore alla lunghezza specificata nell'attributo MinLength o maggiore della lunghezza specificata nell'attributo MaxLength.

StringLength

StringLength consente inoltre di specificare convalide di proprietà aggiuntive come MaxLength. L'unica differenza è che l'attributo StringLength può essere applicato solo a una proprietà di tipo stringa delle classi di dominio.

public class Course {

   public int CourseID { get; set; }
   [StringLength (24)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Entity Framework convalida anche il valore di una proprietà per l'attributo StringLength. Se l'utente imposta il titolo che contiene più di 24 caratteri, EF lancerà EntityValidationError.

tavolo

La convenzione Code First predefinita crea un nome di tabella simile al nome della classe. Se si lascia che Code First crei il database e si desidera anche modificare il nome delle tabelle che sta creando. Quindi -

  • È possibile utilizzare Code First con un database esistente. Ma non è sempre il caso che i nomi delle classi corrispondano ai nomi delle tabelle nel database.

  • L'attributo di tabella sovrascrive questa convenzione predefinita.

  • EF Code First creerà una tabella con un nome specificato nell'attributo Table per una determinata classe di dominio.

Diamo un'occhiata al seguente esempio in cui la classe è denominata Student e per convenzione, Code First presume che questo verrà mappato a una tabella denominata Students. In caso contrario, è possibile specificare il nome della tabella con l'attributo Table come mostrato nel codice seguente.

[Table("StudentsInfo")]
public class Student {

   [Key]
   public int StdntID { get; set; }
   [Required]
   public string LastName { get; set; }
   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Ora puoi vedere che l'attributo Table specifica la tabella come StudentsInfo. Quando la tabella viene generata, vedrai il nome della tabella StudentsInfo come mostrato nell'immagine seguente.

Non è possibile specificare solo il nome della tabella, ma è anche possibile specificare uno schema per la tabella utilizzando l'attributo Table, come mostrato nel codice seguente.

[Table("StudentsInfo", Schema = "Admin")] 
public class Student {

   [Key]
   public int StdntID { get; set; }
   [Required]
   public string LastName { get; set; }
   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Puoi vedere nell'esempio sopra, la tabella è specificata con lo schema admin. Ora Code First creerà la tabella StudentsInfo nello schema Admin come mostrato nell'immagine seguente.

Colonna

È anche lo stesso dell'attributo Table, ma l'attributo Table sostituisce il comportamento della tabella mentre l'attributo Column sostituisce il comportamento della colonna. La convenzione Code First predefinita crea un nome di colonna simile al nome della proprietà. Se si lascia che Code First crei il database e si desidera anche modificare il nome delle colonne nelle tabelle. Quindi -

  • L'attributo di colonna sovrascrive la convenzione predefinita.

  • EF Code First creerà una colonna con un nome specificato nell'attributo Column per una determinata proprietà.

Diamo un'occhiata al seguente esempio in cui la proprietà è denominata FirstMidName e per convenzione, Code First presume che questo verrà mappato a una colonna denominata FirstMidName.

In caso contrario, puoi specificare il nome della colonna con l'attributo Column come mostrato nel codice seguente.

public class Student {

   public int ID { get; set; }
   public string LastName { get; set; }
   [Column("FirstName")]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Puoi vedere che l'attributo Column specifica la colonna come FirstName. Quando viene generata la tabella, vedrai il nome della colonna FirstName come mostrato nell'immagine seguente.

Indice

L'attributo Index è stato introdotto in Entity Framework 6.1. Se stai utilizzando una versione precedente, le informazioni in questa sezione non si applicano.

  • È possibile creare un indice su una o più colonne utilizzando IndexAttribute.

  • L'aggiunta dell'attributo a una o più proprietà farà sì che EF crei l'indice corrispondente nel database quando crea il database.

  • Gli indici rendono il recupero dei dati più veloce ed efficiente, nella maggior parte dei casi. Tuttavia, il sovraccarico di una tabella o di una vista con indici potrebbe influire in modo spiacevole sulle prestazioni di altre operazioni come inserimenti o aggiornamenti.

  • L'indicizzazione è la nuova funzionalità di Entity Framework in cui è possibile migliorare le prestazioni dell'applicazione Code First riducendo il tempo necessario per eseguire query sui dati dal database.

  • Puoi aggiungere indici al tuo database utilizzando l'attributo Index e sovrascrivere le impostazioni Univoche e Cluster predefinite per ottenere l'indice più adatto al tuo scenario.

  • Per impostazione predefinita, l'indice verrà denominato IX_ <nome proprietà>

Diamo un'occhiata al codice seguente in cui viene aggiunto l'attributo Index nella classe del corso per i crediti.

public class Course {
   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Puoi vedere che l'attributo Index viene applicato alla proprietà Credits. Quando la tabella viene generata, vedrai IX_Credits negli indici.

Per impostazione predefinita, gli indici non sono univoci, ma puoi utilizzare l'estensione IsUniqueparametro denominato per specificare che un indice deve essere univoco. L'esempio seguente introduce un indice univoco come illustrato nel codice seguente.

public class Course {
   public int CourseID { get; set; }
   [Index(IsUnique = true)]
	
   public string Title { get; set; }
   [Index]
	
   public int Credits { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

ForeignKey

La convenzione Code First si prenderà cura delle relazioni più comuni nel tuo modello, ma in alcuni casi è necessaria assistenza. Ad esempio, la modifica del nome della proprietà della chiave nella classe Student ha creato un problema con la sua relazione con la classe Enrollment.

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

public class Student {
   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Durante la generazione del database, Code First vede la proprietà StudentID nella classe Enrollment e la riconosce, per convenzione che corrisponde a un nome di classe più "ID", come chiave esterna per la classe Student. Tuttavia, non è presente alcuna proprietà StudentID nella classe Student, ma la proprietà StdntID è la classe Student.

La soluzione è creare una proprietà di navigazione in Enrollment e usare ForeignKey DataAnnotation per aiutare Code First a capire come costruire la relazione tra le due classi come mostrato nel codice seguente.

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
	
   public Grade? Grade { get; set; }
   public virtual Course Course { get; set; }
   [ForeignKey("StudentID")]
	
   public virtual Student Student { get; set; }
}

Ora puoi vedere che l'attributo ForeignKey è applicato alla proprietà di navigazione.

NotMapped

Per le convenzioni predefinite di Code First, ogni proprietà che è di un tipo di dati supportato e che include getter e setter è rappresentata nel database. Ma questo non è sempre il caso delle tue applicazioni. L'attributo NotMapped sovrascrive questa convenzione predefinita. Ad esempio, potresti avere una proprietà nella classe Student come FatherName, ma non è necessario memorizzarla. È possibile applicare l'attributo NotMapped a una proprietà FatherName di cui non si desidera creare una colonna nel database, come mostrato nel codice seguente.

public class Student {
   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
	
   public DateTime EnrollmentDate { get; set; }
   [NotMapped]

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

Puoi vedere che l'attributo NotMapped è applicato alla proprietà FatherName. Quando la tabella viene generata vedrai che la colonna FatherName non verrà creata in un database, ma è presente nella classe Student.

Code First non creerà una colonna per una proprietà che non ha getter o setter come mostrato nell'esempio seguente di proprietà Address ed Age della classe Student.

InverseProperty

InverseProperty viene utilizzato quando si hanno più relazioni tra le classi. Nel corso di iscrizione, potresti voler tenere traccia di chi si è iscritto a un corso corrente e a un corso precedente. Aggiungiamo due proprietà di navigazione per la classe Enrollment.

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
   public Grade? Grade { get; set; }
	
   public virtual Course CurrCourse { get; set; }
   public virtual Course PrevCourse { get; set; }
   public virtual Student Student { get; set; }
}

Allo stesso modo, dovrai anche aggiungere nella classe Course a cui fanno riferimento queste proprietà. La classe Course ha proprietà di navigazione per tornare alla classe Enrollment, che contiene tutte le iscrizioni correnti e precedenti.

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]

   public int Credits { get; set; }
   public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
   public virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}

Code First crea la colonna della chiave esterna {Nome classe} _ {Chiave primaria}, se la proprietà della chiave esterna non è inclusa in una particolare classe come mostrato nelle classi precedenti. Quando il database viene generato, vedrai le seguenti chiavi esterne.

Come puoi vedere, Code first non è in grado di abbinare le proprietà nelle due classi da solo. La tabella del database per le iscrizioni dovrebbe avere una chiave esterna per CurrCourse e una per PrevCourse, ma Code First creerà quattro proprietà della chiave esterna, ad es.

  • CurrCourse _CourseID
  • PrevCourse _CourseID
  • Course_CourseID e
  • Course_CourseID1

Per risolvere questi problemi, è possibile utilizzare l'annotazione InverseProperty per specificare l'allineamento delle proprietà.

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]

   public int Credits { get; set; }
   [InverseProperty("CurrCourse")]

   public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
   [InverseProperty("PrevCourse")]

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

Come puoi vedere, l'attributo InverseProperty viene applicato nella classe Course precedente specificando a quale proprietà di riferimento della classe Enrollment appartiene. Ora, Code First genererà un database e creerà solo due colonne di chiavi esterne nella tabella Enrollments come mostrato nell'immagine seguente.

Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.