Code First Conventions e DataAnnotations

Nella prima parte del tutorial abbiamo visto come interagire con Entity Framework scrivendo semplicemente le classi e invocando Code First mediante i comandi:

Enable-Migrations
Add-Migration
Update-Database

in questa seconda parte invece vedremo nel dettaglio come funziona Code First, cercando di capire come scrivere le classi per permettere a Code First di creare correttamente le tabelle.

Cosa sono le Convenzioni (Code First Conventions)?

Le convenzioni sono un set di regole di default, che servono a Code First per configurare automaticamente le tabelle del database, partendo dal dominio delle classi.

Queste convenzioni sono definite nel namespace: “System.Data.Entity.ModelConfiguration.Conventions”, che perciò dovrà sempre essere referenziato nelle nostre classi.

Vediamo ora nei dettagli le varie convezioni esistenti.

Type Discovery

Cominciamo dal Type Discovery, ossia dal processo d’identificazione dei tipi che dovrebbero far parte del modello di Entity Framework, e la loro trasformazione in tabelle anche se non espressamente richiesto mediante il metodo DbSet del Context.

Per capire meglio questo concetto, riprendiamo l’esempio della Scuola, in cui avevamo 2 classi “Studente” e “Classe” e modifichiamolo in questo modo:

  • aggiungiamo la classe Professore
  • modifichiamo la classe Studente per specificare che ad ogni studente è associato un professore
  • e lasciamo invariata la casse ScuolaContext

    public class Professore
    {
        public Professore()
        {

        }
        public int ProfessoreId { get; set; }
        public string NomeProfessore { get; set; }
    }    


    public class Studente
    {
        public Studente()
        {

        }
        public int StudenteId { get; set; }
        public string NomeStudente { get; set; }
        public DateTime DataDiNascita { get; set; }
        public byte[] Foto { get; set; }
        public decimal Altezza { get; set; }
        public float Peso { get; set; }

        public Classe Classe { get; set; }

        public Professore Professore { get; set; }
    }
    public class ScuolaContext : DbContext
    {
        public ScuolaContext() : base("name=ScuolaContext")
        {
        }

        public System.Data.Entity.DbSet<MyWebApi.Models.Classe> Classi { get; set; }

        public System.Data.Entity.DbSet<MyWebApi.Models.Studente> Studenti { get; set; }
    }

A questo punto aggiorniamo il database utilizzando nuovamente in sequenza i comandi precedenti:

  • Add-Migration ScuolaDB
  • Update-Database

Code First, grazie al Type Discovery, capisce che deve creare anche la tabella Professori e che la deve collegare alla tabella Studenti mediante “Foreign Key” (nella figura sottostante corrisponde alla colonna “Professore_ProfessoreID” della tabella “Studenti“), anche se non è stata inserita nella classe ScuolaContext e restituisce il seguente database:

image

 

Primary Key Convention

Nella prima parte del tutorial abbiamo visto che Code First, automaticamente, crea le tabelle con le proprie Primary Key, ma in realtà ciò avviene, grazie alle seguenti convenzioni:

  • la Primary Key deve essere una proprietà della classe, il cui nome è “Id” o “nome della classe” + “Id” (non è case sensitive),
  • il tipo di dato può essere qualsiasi ma se è numerico o GUID, la chiave sarà configurata come identity

Ricapitolando, se per esempio la nostra classe “Professore” l’avessimo scritta in questo modo:

    public class Professore
    {
        public Professore()
        {

        }
        public int PrfId { get; set; }
        public string NomeProfessore { get; set; }
    }

Entity Framework ci avrebbe restituito la seguente eccezione:

'System.Data.Entity.ModelConfiguration.ModelValidationException' occurred in EntityFramework.dll
EntityType 'Professore' has no key defined. Define the key for this EntityType.

perché il nome della classe è “Professore” e non “Prf” e quindi Entity Framework non riesce ad individuare la chiave.

Se abbiamo necessità di utilizzare un nome diverso dalla convezione, dobbiamo utilizzare le “DataAnnotation”, che vedremo dopo.

Relationship Convention

Code First per interpretare le relazioni utilizza la “navigation property”, che può essere semplicemente la definizione di un tipo o di una collezione.

Nel nostro esempio, nella classe Studente, abbiamo definito la seguente navigation property riferita alla classe Classe:

public Classe Classe { get; set; }

mentre nella classe Classe abbiamo definito la seguente navigation property riferita alla classe Studente:

public ICollection<Studente> Studenti { get; set; }

Entity Framework interpreta queste navigation property come una relazione uno a molti tra Classe e Studenti e crea la foreign key nella tabella Studenti secondo la seguente regola: “nome della navigation property” + “_” + “nome della proprietà indicante la primary key della navigation property”: Classe_ClasseId

   public class Studente
    {
        public Studente()
        {

        }
        public int StudenteID { get; set; }
        public string NomeStudente { get; set; }
        public DateTime DataDiNascita { get; set; }
        public byte[] Foto { get; set; }
        public decimal Altezza { get; set; }
        public float Peso { get; set; }
     
        //navigation property
        public Classe Classe { get; set; }
    }


    public class Classe
    {
        public Classe()
        {

        }
        public int ClasseID { get; set; }
        public string NomeClasse { get; set; }

        //navigation property
        public ICollection<Studente> Studenti { get; set; }
    }

image

 

Foreign Key Convention

Abbiamo appena visto che grazie alla navigation property, Code First aggiunge anche la Foreign Key però è consigliabile definirla esplicitamente, inserendola nella classe come ulteriore proprietà:

    public class Studente
    {
        public Studente()
        {

        }
        public int StudenteId { get; set; }
        public string NomeStudente { get; set; }
        public DateTime DataDiNascita { get; set; }
        public byte[] Foto { get; set; }
        public decimal Altezza { get; set; }
        public float Peso { get; set; }

        //foreign key
        public int ClasseId { get; set; }
        //navigation property
        public Classe Classe { get; set; }

    }

   public class Classe
    {
        public Classe()
        {

        }
        public int ClasseId { get; set; }
        public string NomeClasse { get; set; }

        public ICollection<Studente> Studenti { get; set; }
    }

Ottenendo in tabella il seguente risultato: il nome della colonna è ClasseId e non più Classe_ClasseId

image

 

Cosa solo le DataAnnotation (CodeFirst DataAnnotations)?

Code First costruisce il modello del database partendo dal dominio delle classi usando le convenzioni, però in alcuni casi si ha la necessità di aggiungere ulteriori dettagli alle convenzioni o. addirittura, si ha la necessità di modificarle, per far ciò si possono utilizzare due metodi: “DataAnnotation” e/o “FluentApi”.

Le DataAnnotation non sono altro che dei semplici attributi che si possono applicare al dominio delle classi e alle loro proprietà.

Questi attributi sono definiti nel namespace: “System.ComponentModel.DataAnnotations”, che perciò dovrà sempre essere referenziato nelle nostre classi.

Per esempio vediamo come potremmo cambiare la classe Studente utilizzando le DataAnnotation

   [Table("InfoStudente")]
    public class Studente
    {
        public Studente() { }
        
        [Key]
        public int SID { get; set; }

        [Column("Nome", TypeName="ntext")]
        [MaxLength(20)]
        public string NomeStudente { get; set; }

        [NotMapped]
        public int? Eta { get; set; }
        
        
        public int ClsId { get; set; }

        [ForeignKey("ClsId")]
        public virtual Classe Classe { get; set; }
    }

L’attributo Table ci permette di usare per la tabella un nome diverso da quello della classe, nell’esempio il nome della classe è “Studente” mentre il nome della tabella sarà “InfoStudente”.

L’attributo Key ci permette di usare un nome diverso dalla convenzione per la chiave primaria, nell’esempio la proprietà SID sarà la chiave primaria invece della standard “StudenteId”.

L’attributo Column ci permette di cambiare il nome e il tipo di dato di una colonna della tabella rispetto alla proprietà della classe, nell’esempio la proprietà “NomeStudente“ di tipo nvarchar(max) (tipo di dato sql associato al tipo c# string) diventerà in tabella “Nome” di tipo “ntext”.

L’attributo MaxLength ci permette di impostare la lunghezza massima della colonna del database, perciò nell’esempio il nome dello studente accettato sarà di massimo 20 caratteri.

L’attributo Not Mapped ci permette di ignorare una proprietà, ossia di non trasformarla in colonna all’interno della tabella, perciò nell’esempio la tabella InfoStudente non avrà la colonna “Eta”.

L’attributo ForeignKey ci permette di definire la foreign key di una classe con un nome diverso dal nome della primary key a cui fa riferimento. Nel nostro esempio la proprietà “ClsId” è definita foreign key della classe “Studente” e si riferisce alla classe “Classe”, però in quest’ultima il nome della primary key resta ClasseId.

Per l’elenco più dettagliato delle DataAnnotation vedere la tabella sottostante.

Attributo

Descrizione

Key Indica a EF che la proprietà sarà la chiave primaria della tabella (anche se non conforme alla Primary Key Convention)
TimeStamp Indica a EF che la proprietà sarà di tipo TimeStamp non nulla nella tabella
Required Indica a EF (e anche a MVC) che la proprietà è obbligatoria perciò non sono ammessi valori nulli
MinLength Indica a EF che la colonna deve avere una lunghezza minima obbligatoria, il cui valore sarà passato come parametro.
MaxLength Indica a EF che la colonna deve avere una lunghezza massima obbligatoria, il cui valore sarà passato come parametro.
StringLength Indica a EF che la colonna deve accettare valori che la cui lunghezza non deve superare un certo numero di caratteri, il cui valore sarà passato come parametro.
Table Indica a EF il nome della tabella, che sarà quindi diverso dal nome della classe
Column Indica a EF il nome della colonna e il suo tipo, che sarà quindi diverso da quello della proprietà della classe a cui si riferisce
Index Indica a EF che deve creare un indice per la colonna a cui si riferisce la proprietà
ForeignKey Indica a EF quale sarà la foreign key della navigation property a cui si riferisce
NotMapped Indica a EF che alla proprietà a cui si riferisce non deve corrispondere alcuna colonna nel database

Concludendo, in questa seconda parte del tutorial abbiamo visto come scrivere le classi per mapparle correttamente con il database e ora siamo in grado di scrivere le nostre prime applicazioni, nelle prossime sessioni vedremo ulteriori caratteristiche di Entity Framework Code First.

 

Autore:


blog comments powered by Disqus