Home » Approfondimenti » Computer Science » Entity Framework
  []

  1. ADO.NET Entity Framework
  2. Dynamic Proxy Class
  3. Entity Data Model
  4. DbContext
  5. Repository Pattern
  6. Come strutturare un’applicazione
  7. Database First vs Code First
  8. Meccanismi di esecuzione dele query
  9. Visual Studio Class Diagram (file .cd)
  10. Gestione dell’ereditarietà
  11. Gestione degli enum
  12. Relationships in EF 6 & CORE
  13. EF 6 vs EF Core
  14. Risorse

 

Entity Framework (EF)

  • Cosa é un ORM =>
    • E’ un OR/M (Object Relational Mapping) o “Peristence Framework” che permette di creare un livello di astrazione tra l’applicazione e la sorgente dati che l’applicazione utilizza per funzionare.
    • Perche?
      • Mappa i concetti di un OOP programma come c# nei concetti di un DB relazionale come SQL Server (colma la differenza concettuale e tecnica tra archiviazione e utilizzo)
      • Permette di semplificare/automatizzare una serie di attività che lo sviluppatore doveva ripetere molte volte durante lo sviluppo della sua applicazione
        • Model
        • DAL 
        • Mapping

 

      • Tale lavoro aumenta linearmente con l’aumentare delle tabelle/entità presenti nel DB o nel nostro Domain Model.
      • Tale lavoro aumenta in complessità, dovendo tradurre nel Domain Model un sacco di chiavi interne e esterne

  • Entity Framework =>
    • E’ l’implementazione di un ORM pensata da Microsoft 
    • Prima di EF si è usato =>
      • ADO.NET.
      • LINQ to SQL.
      • Entity Framework v1.
      • nHibernate.
      • Entity Framework v4.
      • Entity Framework v4.1 : DbContext
      • Entity Framework Core v7 (complete rewrite).
      • Persistent Framework alternativi => Dapper, ORMLite, PetaPocos
  • Componenti di Entity Framework (database first) =>
    1. EDMX
    2. EDMX Behind code
    3. Entity
    4. Context
  • Optimistic Locking
    • E’ il metodo di default per gestire la concorrenza in EF
  • Possibili approci a EF =>

  • Modello concettuale =>
    • Lo sviluppatore  utilizzerà il modello contettuale (in-memory) che rappresenta il DB e che interrogherà tramite LINQ, recuperando e manipolando dati fortemente tipati. 
    • Entità  =>
      • key property, scalar properties navigation properties (reference o collection)
    • Entità associate  (le vecchie join) =>
      • uno-a-uno, uno-a-molti, molti-a-molti
    • Entità derivate (le vecchie viste)

               

  • Getting started =>
    • Sql Server => Per poter utilizzare EF nel proprio computer avendo scelto Sql Server come DB provider esistono dverse opzioni (vedi foto)

 

 

Dynamic Proxy Class (POCO Proxy)

  • Classi POCO (Plain Old CLR Object) => Una class POCO è una classe che non dipende da uno specifico framework.E’ come ogni altra normale classe .NET CLR
  • Classi Dynamic Proxy => E’ un classe runtime che fa da wrapper ad una entità POCO.  Permette il lazy loading. Per diventare una POCO Proxy, una entità POCO deve rispettare le seguenti regole:
    • La classe POCO deve essere dichiarata public
    • La classe POCO non deve essere sealed (NotInheritable in Visual Basic)
    • La classe POCO non deve essere abstract (MustInherit in Visual Basic)
    • Ogni navigation property deve essere dichiarata public o virtual
    • Ogni collection property deve essere una ICollection
    • Nella classe context, context.Configuration.ProxyCreationEnabled = true (default)
      • //A runtime avrò 
        System.Data.Entity.DynamicProxies.Student
        var entityType = ObjectContext.GetObjectType(Student.GetType())
  • POCO e EF DatabaseFirst =>
    • E’ possibile decidere di bloccare la generazione automatica delle classi da parte di EF
    • Dopodiché è possibile generare le classi del MODEL manualmente, è sufficiente rispecchiare la denominazione del mapping presente nel file EDMX
    • EF continuerà a generare il CONTEXT ma non più il MODEL.
    • E’ utile =>
      • nel caso di migrazione da un progetto in cui il domain model esiste già
      • nel caso si voglia rendere più chiare e leggibili le classi create in automatico dal EF

 

Entity Data Model (file .EDMX

L’XML rende questi file ragionatamente leggibili e portabili. 

  • Modifiche alla sorgente dati si riflettono automaticamente al modello concettuale, così come viceversa le modifiche al modello contettuale come ad esempio un nuovo valore di una proprietà di una entità si riflettono sul database.
  • EF usa dei file template .tt (Text Template Transformation Toolkit) per generare (scaffold) le classi e il context.
  • Click destro sul grafico => ‘Mapping details’: è possibile gestire le proprietà del mapping e decidere magari di non mappare un campo dell’entità concettuale che non esiste nel DB.
  • Clicke destro sul grafico => ‘Model Browser’ : é possibile navigare graficamente il file xml

Il file XML è diviso in 2 nodi principali =>

  1. <Designer> (vengo memorizzare le informazioni per gestire puramente la visualizzazione grafica)
  2. <edmx:Runtime> (a sua volta divisa in 3 sotto nodi corrispondenti a 3 livelli logici indipendenti) =>
    1. <edmx:ConceptualModels> [Conceptual Schema Definition Language] =>
      • Descrive il modello concettuale (i dati dal punto di vista dell’applicativo) e ne permette la generazione automatica.
      • A runtime verrà creato il file => <my_name>Model.csdl
      • Lo sviluppatore percepisce la sorgente dati come un contenitore di entità (domain classes) chiamato context, delle loro relazioni, di convezioni e configurazioni.
      • E’ possibile ottenerlo =>
        1. Facendo reverse-engineer da un DB esistente.
        2. Utilizzando un desing tool.
        3. Creando le proprie classi manualmente (ed eventualmente generare in automatico il DB).
        <edmx:ConceptualModels>
         <Schema xmlns="http://schemas.microsoft.com/ado/2009/11/edm"...>
          <EntityContainer Name="Model1Container" annotation:LazyLoadingEnabled="true">
           <EntitySet Name="Customers" EntityType="Model1.Customer" />
          </EntityContainer>
          <EntityType Name="Customer">
           <Key>
            <PropertyRef Name="CustomerID" />
           </Key>
           <Property Type="Int32" Name="CustomerID" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
           <Property Type="String" Name="FirstName" Nullable="false" />
           <Property Type="String" Name="LastName" Nullable="false" />
           <Property Type="String" Name="AddressLine1" Nullable="false" />
           <Property Type="String" Name="AddressLine2" Nullable="false" />
           <Property Type="String" Name="City" Nullable="false" />
           <Property Type="String" Name="State_Province" Nullable="false" />
           <Property Type="String" Name="ZIP_Postal_Code" Nullable="false" />
           <Property Type="String" Name="Region_Country" Nullable="false" />
          </EntityType>
         </Schema>
        </edmx:ConceptualModels>
        
    2. <edmx:StorageModels> [Store Schema Definition Language] =>
      • Descrive il modello logico (i dati dal punto di vista del database) che permette all’ ADO.NET la gestione in autonomia delle connessioni e delle operazioni con il database
      • A runtime verrà creato il file => <my_name>Model.ssdl
         <edmx:StorageModels>
          <Schema Namespace="Model1.Store" Alias="Self"...>
           <EntityContainer Name="Model1StoreContainer">
            <EntitySet Name="Customers" EntityType="Model1.Store.Customers" store:Type="Tables" Schema="dbo" />
           </EntityContainer>
           <EntityType Name="Customers">
            <Key>
             <PropertyRef Name="CustomerID" />
            </Key>
            <Property Name="CustomerID" Type="int" StoreGeneratedPattern="Identity" Nullable="false" />
            <Property Name="FirstName" Type="nvarchar(max)" Nullable="false" />
            <Property Name="LastName" Type="nvarchar(max)" Nullable="false" />
            <Property Name="AddressLine1" Type="nvarchar(max)" Nullable="false" />
            <Property Name="AddressLine2" Type="nvarchar(max)" Nullable="false" />
            <Property Name="City" Type="nvarchar(max)" Nullable="false" />
            <Property Name="State_Province" Type="nvarchar(max)" Nullable="false" />
            <Property Name="ZIP_Postal_Code" Type="nvarchar(max)" Nullable="false" />
            <Property Name="Region_Country" Type="nvarchar(max)" Nullable="false" />
           </EntityType>
        </edmx:StorageModels>
    3. <edmx:Mappings> [Mapping Schema Language] =>
      • Descrive il mapping tra i due modelli. E’ possibile interagire con questo livello tramite il tool grafico o nel caso di approccio ‘code first’ tramite data annotations o fluent API
      • A runtime verrà creato il file => <my_name>Model.msl
      • Descrive il mapping tra i due modelli. E’ possibile interagire con questo livello tramite il tool grafico o nel caso di approccio ‘code first’ tramite data annotations o fluent API:
        <edmx:Mappings>
        <Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2009/11/mapping/cs">
          <EntityContainerMapping StorageEntityContainer="Model1StoreContainer" CdmEntityContainer="Model1Container">
           <EntitySetMapping Name="Customers">
            <EntityTypeMapping TypeName="IsTypeOf(Model1.Customer)">
             <MappingFragment StoreEntitySet="Customers">
              <ScalarProperty Name="CustomerID" ColumnName="CustomerID" />
              <ScalarProperty Name="FirstName" ColumnName="FirstName" />
              <ScalarProperty Name="LastName" ColumnName="LastName" />
              <ScalarProperty Name="AddressLine1" ColumnName="AddressLine1" />
              <ScalarProperty Name="AddressLine2" ColumnName="AddressLine2" />
              <ScalarProperty Name="City" ColumnName="City" />
              <ScalarProperty Name="State_Province" ColumnName="State_Province" />
              <ScalarProperty Name="ZIP_Postal_Code" ColumnName="ZIP_Postal_Code" />
              <ScalarProperty Name="Region_Country" ColumnName="Region_Country" />
             </MappingFragment>
            </EntityTypeMapping>
           </EntitySetMapping>
         </EntityContainerMapping>
         </Mapping>
        </edmx:Mappings>

 

DbContext

  • Prima c’era ObjectContext, di cui il DbContext (CodeFirst) è un wrapper
  • Espone le entità del modello concettuale definite.
  • Gestisce la connessione al DB.
  • Genera gli statement SQL (Entity Client Data Provider) 
    • Converte Linq-To-Entities a Statement SQL.
    • Riconverte i dati del DB alle entità del modello concettuale.
  • Gestisce il caching.
  • In-memory.
  • E’ un ponte tra il MODEL e la sorgente dati.
  • Recap comandi
    • context.ChangeTracker.Entities();
      context.Entry(<my-istance>); //dà accesso alle informazion del changetracker
      context.SaveChanges();
      context.my-entities.Attach(<my-istance>); //Attacca l’entità al DBcontext e marca le proprietà = Unchanged, quindi ogni cambiamento
      //precedentemente fatto sull'entità é ignorato da un eventuale SaveChanges()
      //es: context.Students.Attach(disconnectedStudent)
      context.my-entities.Update(<my-istance>) //Attacca l’entità al DBcontext e marca le proprietà = Modified
      context.my-entities.Local(<my-istance>); //Legge l'entità dalla cache (in-memory data) e non dal DB
      context.my-entities.FromSqlRaw("..")...
      context.Set<my-entity>()[.Find|.Add|.Remove] //Ritorna il DbSet dell'entità del tipo specificato
      //es: context.Set<Student>().Find()
      context.Database.BeginTransaction
      context.Database.ExecuteRawSql("..")
  • ChangeTracker =>
    • Viene tenuta traccia dello stato degli oggetti che il DbContext conosce (o perché recuperati dal DB o perché ‘attaccati’ dalla logica)
    • DbContext isolation =>
      • Quando viene lanciato ‘DbContext.SaveChanges’, EF sfrutta la proprietà  <entry.OriginalValues> per costruire i SQL Statement e riportare le modifiche che l’applicazione ha fatto sugli oggetti del domain nel DB
      • Le modifiche apportare ad un DbContext sono specifiche di quel DbContext =>
        • Il ChangeTracker non è ‘Singleton’ 
        • Le modifiche sono isolate e legate al DbContext che sitamo usando
        • Eventuali altri DbContext non conosco le modifiche finché non le si salva nel DB
        • Attenzione: Se si fa una modifica al DbContext senza lanciare un Savechanges, tale modifica non è vista dagli altri Dbcontext che leggeranno ancora il vecchio valore.
    • E’ possbile accedere al tracker per leggere l’attuale situazione nel DbContext =>
      • StringBuilder sb = new StringBuilder();

        var entries = context.ChangeTracker.Entries();
        foreach(var entry in entries)
        {
        //E' l'entità che si sta ispezionando tra quelle presenti nel ChangeTracker
        sb.AppendLine(entry.Entity.ToString());

        //E' lo stato dell'entità, EF lo utilizzerà per decidere che tipo di query
        //fare quando sarà eseguito SaveChanges()
        sb.AppendLine(entry.State.ToString());

        //Collezione del valore degli attributi dell'entità prima delle modifiche
        //effettuate dopo aver attaccato l'entità ad DbContext
        sb.AppendLine(entry.CurrentValues["Name"].ToString());

        //Collezione del valore attuali degli attributi dell'entità
        sb.AppendLine(entry.OriginalValues["Name"].ToString());

        //!!ATTENZIONE!!
        //E' sovrascrivere le modifiche fatte nel modello concettuale
        //e richiamare l'entità come attualmente memorizzata nel DB
        entry.Reload();

        sb.AppendLine("------------------------");
        }

        Debug.Write(sb.ToString());
    • Modificare State =>
      • E’ possibile modificare lo stato di un’entità nel changeTracker
        • //Forziamo l'EF a ignorare l'oggetto newDish (non sarà oggetto di modifiche nel DB)
          DbContext.Entry(newDish).State = EntityState.Detached;
    • Disable changeTracker (caso readonly) =>
      • AsNoTacking =>
        • Permette di dire a EF di non attivare il meccanismo del tracking.
        • Utile in situaizoni di readonly => se sappiamo che le entità recuperare dal DB non potranno essere modificare allora possiamo recuperarle senza attivare meccanismo di tracking
        • var dishes = await dbContext.Dishes.AsNoTracking().ToListAsync()
  • Stato degli oggetti nel DbContext
    • Added => è lo stato di un oggetto che è stato aggiunto al DbContext.
      1. var blog = new Blog { Mame = "blog 1" };
        context.Blogs.Add(blog);
        context.SaveChanges();
      2. var blog = new Blog { Mame = "blog 1" };
        context.Entry(blog).State = EntityState.Added;
        context.SaveChanges();
      3. //E' possibile aggenciare una nuova entità aggiungendola alle proprietò di navigazione di 
        //un'entità recuperata dal DbContext (e cioè già tracked)
        var blog = context.Blogs.Find(1);
        blog.user = new User { Name = "user 1"};
        blog.post.Add(new Post { Name = "post 1"});
        context.SaveChanges();
    • Modified => è lo stato di un oggetto che esiste nel DbContext ed è stato modificato.
    • Deleted => é lo stato di un oggetto che è stato aliminato dal DbContext. 
    • Detached => l’entità è stata creata (come oggetto in memory) ma non è stata ancora aggiunta al DbContext. Per questo EF non conosce l’entità in oggetto.

  • SaveChanges() =>
    • Quando viene eseguito questo metodo, EF guarda lo stato degli oggetti presenti nel DbContext e crea le corrispondenti query SQL.
    • Vengono cosi trasferiti al DB i cambiamenti fatti nel DbContext.
    • Unchanged =>
      • E’ lo stato degli oggetti una volta eseguito SaveChanges().
      • Equivale a fare l’attach di un’entità che si sa essere già presente nel DB
        • var existingBlog = new Blog { Name = "Existing blog 1");
          context.Blogs.Attach(existingBlog);
          oppure
          context.Entry(existingBlog).State = EntityState.Unchanged;
          context.SaveChanges();
  • Adding =>
    • //In questo caso EF aggiungerà una nuova linee alla tabella Course 
      //ma anche una nuova linea alla tabella Author, perché entrambe gli oggetti
      //sono marcati nuovi nel DbContext e il loro stato è marcato come Added
      var course = new Course
      {
         Name = "Nuovo corso",
         Descrizione = "Descrizione corso",
         Author = new Author {Id = 1, Name="Professore 1"}
      }
      context.Courses.Add(course); //course.State = Added
      context.SaveChanges();
      
      //Soluzione 1 - Adatta a WPF 
      //Uso un'oggetto già esistente nel DbContext (se c'è)
      var author = context.Authors.Single(a => a.Id==1);
      var course = new Course
      {
         Name = "Nuovo corso",
         Descrizione = "Descrizione corso",
         Author = author 
      }
      context.Courses.Add(course);
      context.SaveChanges();
      
      //Soluzuone 2 - Adatta ad una Web application
      //Non setto il valore della navigation property (Author) 
      //ma dalla chiave esterna (AuthorId)
      var course = new Course
      {
         Name = "Nuovo corso",
         Descrizione = "Descrizione corso",
         AuthorId = 1 
      }
      context.Courses.Add(course);
      context.SaveChanges();
  • Update =>
    • //Per modificare un oggetto è necessario prima caricarlo nel DbContext
      //altrimenti EF lo vede come un nuovo oggetto e lo aggiunge al DB invece che 
      //aggiornarlo
      var course = context.Courses.Find(4); //course.State = Unchanged
                                           //Single(c => c.Id==6);
      course.Name = "Nuovo nome"; //course.State == Modified
      course.AuthorId = 2;
      context.SaveChanges();
  • Delete =>
    • NB: è sempre meglio non eliminare fisicamente i record nel DB ma eliminarli logicamente (settare IsDelete = true). 
    • CascadeOnDelete é attivo =>
      • //Per eliminare un oggetto dal DB, bisogna prima caricarlo nel DbContext
        var course = context.Courses.Find(6); //course.State = unchanged
                                              //Single(c => c.Id==6);
        context.Courses.Remove(course); //course.state = Deleted
        context.SaveChanges();
    • CascadeOnDelete é disattivo =>
      • //Se l'eliminazione automatica é disattivata e se è nella logica
        //della nostra applicazione allora prima di eliminare un'entità padre dobbiamo
        //manualmente eliminare tutti i corrispondenti record nelle entità figlie
        //per farlo bisogna prima (explicit loading) caricarli nel context
        var author = context
        .Authors
        .Include(a => a.Courses)
        .Sigle(a => a.Id == 2);
        //eliminazione esplicita dei corsi legati all'autore che vogliamo eliminare
        context.Course.RemoveRange(author.Courses)
        //eliminazione dell'autore Id=2
        context.Authors.Remove(author); //throw SqlException
    • Configurare ‘delete action‘ =>
      • Cascade =>
        • se l’oggetto che si sta eliminando (es: Order) fa parte di una foreing key di un’altra entità (es: OrderDetail) allora verranno eliminate anche tutte le righe di dettaglio relative all’ordine che stiamo eliminando
      • SetNull =>
        • se l’oggetto che si sta eliminando (es: Order) fa parte di una foreing key di un’altra entità (es: OrderDetail) allora il campo foreign key di tutte le righe di dettaglio coinvolte viene settato a null
      • NoAction =>
        • EF non implementa nessuna azione aggiuntiva oltre all’implementazione dell’oggetto che si sta eliminado
        • Se il DB lancia un errore durante l’operazione di eliminazione, EF lo espone semplicemente all’applicazione senza mascherarlo
        • In tale caso, è necessario fare il delete dei dettagli dell’ordine e solo dopo delle loro tastate, per non avere nessun errore.
        • E’ l’opzione da usare nei casi il modello dati sia complesso
      • protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
        -- EF Core --
        modelBuilder.Entity<MyEntity>()
        .OnDelete(DeleteBehavior.Cascade | DeleteBehavior.SetNull | DeleteBehavior.NoAction);
        }
  • Context.Set =>
    • Un DbSet è una rappresentazione di una tabelle del DB che può essere interrogata tramite LINQ
    • Crea un DbSet che può essere usato per interrogare e salvare un’istanza di TEntity.
      • context.Set<User>().Find(id) => context.Users.Find(id) //Inizialmente controlla se l'entità richiesta è già stata caricata in-memory. 
        //Se lo è, il risultato viene restituito senza interrogare il database.
        //In caso contrario, viene eseguita la query sul database.
        //Attenzione: id deve essere la primary key context.Set<User>().Add(user) => context.Add(user) context.Set<User>().Remove(user) => context.Remove(user)
  • Context.Set.Update =>
    • Attacca l’oggeto al DbContext e marca le proprietà come ‘Modified’
    • Di conseguenza EF crearà un SQL Statement per ogni campo dell’oggetto 
    • dbContext.<MyEntities>.Update(<MyEntity>)
      //dbContext.Dishes.Update(myDish);
  • Context.Entry =>
    • Dà accesso alle informazioni del Change Tracking
    • Serve per attaccare l’entità al DBContext e indicare tutte le sue proprietà come ‘Modified’. EF genererà un’istruzione SQL che farà l’update di tutte le proprietà della tabella User.
      • context.Entry(user).State = EntityState.Modified;
        context.SaveChanges();
  • Context.<Entity>.Attach =>
    • Attacca l’entità al DBcontext ma marca le sue proprietà come ‘Unchanged’. Dando così la possibilità di modificare eventualmente le singole proprietà
    • In modo che EF genererà un’istruzione SQL con solo gli update necessari.
      • context.User.Attach(user); // State = Unchanged
        user.FirstName = "John"; 
        // State = Modified, and only the FirstName property is dirty.
        context.SaveChanges();
  • Context.<Entity>.Local =>
    • Permette di recuperare le entità materializzate nella cache (in-memory data) dal DbContext e non dal DB.
      • Item thisItem = context.Items.First();  
        thisItem.Name = "Update name";    
        Item thisUpdatedItem = context.Items.Local.Where(a=>a.Name == "Update name").First();
  • Eseguire raw Sql  (quando LINQ non è efficiente) =>
    • Spesso EF creare in modo automatico gli statement SQL => A volte però si vuole gestire manualmente lo statement SQL da eseguire (es: usare WITH statement)
    • FromSqlRaw:
      • var dishes = await dbContext.Dishes
        .FromSqlRaw("SELECT * FROM Dishes")
        .ToListAsync();
    • FromSqlInterpolated
      • E’ possibile usare il template string $” …{} ..” per scrivere lo statement SQL
      • Evita la Sql Injection
      • var filter = "%z";
        var dishes = await dbContext.Dishes
        .FromSqlInterpolated($"SELECT * FROM Dishes WHERE Notes LIKE {filter}")
        .ToListAsync();
    • ExecuteSqlRawAsync, ExecuteSqlIterolatedAsync
      • Per modificare il DB con un comando SQL scritto manualmente
      • await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM Dishes WHERE Id NOT IN (SELECT DishId FROM Igredients)");
  • Transaction =>
    • BeginTransactionAsync

  • Expression Tree
    • Permette di descrivere il codice c# (LINQ) come un albero di ‘expression’ che inseguito saranno esaminate da EF e eventualmente tradotte in SQL statemente da eseguire nel DB Server.
    • Il codice LINQ (se applicato ad un dbcontext connesso a un database) viene compilato normalmente ma a runtime non viene trascritto in codice macchina.
      • Non avrebbe senso perché non é la macchina che sta eseguendo il codice c# a dover eseguire i comandi SQL ma il piuttosto il nostro DB Server.
    • A runtime viene creato un object tree che è letto da EF e tradotto in SQL Statement per il nostro DB Server.
    • Se invece applico del codice LINQ ad una lista di oggetti in-memory (senza nessun legame con un DB Server), tale codice verrà compilato e eseguito a runtime normalmente senza passare per un expression object
    • In breve, le query LINQ eseguite in-process vengono compilate in delegati mentre le query eseguite out-process vengono compilate in expression tree
  • Settaggio minimo per poter utilizzare EF =>
    • Nuget Packages 
      • //Per poter usare EF in un'applicazione Wep API o MVC é necessario importare almeno questi 2 packages:

        1- Microsoft.EntityFrameworkCore.SqlServer //se uso il provider di dati é Sql Server

        [se si vuole usare la CLI (es: dotnet ef migrations add <nome>)]
        2A- Microsoft.EntityFrameworkCore.Design

        [oppure, se si vuole usare anche i relativi comandi Package Manager Console (PCM) (es: Add-Migration)]
        2B- Microsoft.EntityFrameworkCore.Tools
    • Creare la classe DbContext
      • public class MyDbContext : DbContext
        {
           public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
           {
        
           }
        
           public OnModelCreating(ModelBuilder modelBuilder)
           {
               modelBuilder.ApplyConfigurationsFromAssembly(typeof(VehicleConfiguration).Assembly);
           }
        
           DbSet<Make> Makes {get; set;}
        }

        public class VehicleConfiguration : IEntityTypeConfiguration
        {
        public void Configure(EntityTypeBuilder builder)
        {
        }
        }
    • (program.cs) Settare la Dependency Injection nel service container centralizzato. Scegliere quale client di DB utilizzare
      • using Microsoft.EntityFrameworkCore;
        
        //registrare nel container dei servizi il DbContext dopo aver recuperato la stringa di configurazione //la registrazione della DI è del tipo 'scoped'
        //di conseguenza anche la registrazione di ogni altra classe che usa il DbContext deve essere del tipo 'scoped'. builder.Services.AddDbContext(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")) });
    •   (appsettings,json) Aggiungere la connectionString
      • {
          "ConnectionStrings": {
              "DefaultConnection": "Server=.;Database=;Trusted_Connection=True;MultipleActiveResultSets=true"
          }  
        }

 

Repository Pattern (Clean architecture)

  • Cos’é =>
    • I repositori sono classi o componenti che incapsulano la logica richiesta per accedere alla sorgente dati
    • Fornisce un livello intermedio che disaccoppia il domain model layer dal data layer agendo come una collezione in-memory di oggetti.
    • Centralizza la logica di accesso ai dati.
    • Fornisce un punto di sostituzione per i test.
    • Fornisce un’architettura flessibile che si può adattare a future evoluzioni.

  • Implementa  i metodi per l’ iterazione con la collezione di oggetti => 
    • Add(obj). 
    • Remove(obj).
    • Get(id).
    • GetAll().
    • Find(predicate).
  • Non implementa metodi per la modifica dei dati =>
    • Update.
    • Save.
  • Errori comuni nell’implementare un repository pattern
    1. Implementare un solo repository per l’intera applicazione => la soluzione è creare un repository per ogni domain model
    2. Repository che ritornano ViewModel or DTO  => ritornare invece gli oggetti del dominio
    3. Implementare i metodi Update o Save =>  
      1. Update è inutile in quanto è sufficiente recuperare l’oggetto che esiste e modificarlo.
      2. Save non permetterebbe di implementare lo UnitOfWork pattern perché il salvataggio sarebbe gestito in ogni singolo repository creando delle ambiguità.
    4. Repository che ritornano IQueryable => devono ritornare IEnumerable
  • Unit of Work (!!non è parte di EF!!) =>
    • Permette di gestire una lista di oggetti soggetti ad una stessa transazione logica, ne coordina la scrittura nel DB e la risoluzione dei problemi di concorrenza.
    • Implementa cioé un livello superiore che ingloba tutti i repository permettendo così la gestione della transazione su più di un repository.
    • Ne sono esempi già implementati:
      • L’iterfaccia ITransaction in NHibernate.
      • La classe DataContext in LINQ to SQL.
      • La classe ObjectContext/DbContext the EF.
    • Le responsabilità di un UOW sono:
      • Gestire le transazioni.
      • Comandare gli Insert, Delete e Update nel DB
      • Prevenire la duplicazione degli statement all’interno di un singolo uno del UOW.
      • Tiene traccia e gestisce ogni cosa si faccia durante una transazione logica che interagisce con il DB.
      • Liberare il resto del codice da questi problemi e concentrarsi sulla scrittura delle Business Logic (ogni classe interessata dalla transazione modifica i dati come se fosse l’unita entità a farlo, senza interessarsi delle altre) evitando di usare direttamente DbSet e DbContext nell’applicazione
  • Come implementare Repository Pattern =>
    • IRepository => Esporre un ‘interfaccia IRepository e sviluppare le implementazioni concrete di tale interfaccia secondo i bisogni reali dell’applicazione:
      • //Interfaccia generica IRepository
        public interface IRepository<TEntity> where TEntity : class
        {
        //metodi per recuperare dati
        TEntity Get(int id);
        IEnumerable<TEntity> GetAll();
        IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate);

        //metodi per aggiungere dati
        void Add(TEntity entity);
        void AddRange(IEnumerable<TEntity> entities);

        //metodi per rimuovere dati
        void Remove(TEntity entity);
        void RemoveRange(IEnumerable<TEntity> entities);
        }
      • //Classe Repository generica che implementa l'interfaccia IRepository
        //e fornisce i metodi base tipici di tutti i repository presenti nell'applicazione
        public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
        {
        protected readonly DbContext _Context;

        public Repository(DbContext context)
        {
        _Context = context;
        }

        public TEntity Get(int id)
        {
        return _Context.Set<TEntity>().Find(id);
        }

        public IEnumerable<TEntity> GetAll()
        {
        return _Context.Set<TEntity>().ToList();
        }

        public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
        {
        return _Context.Set<TEntity>().Where(predicate);
        }

        public void Add(TEntity entity)
        //public void AddRange(IEnumerable<TEntity> entities)
        //puplic void Remove(TEntity entity)
        //public void RemoveRange(IEnumerable<TEntity> entities)
        {
        _Context.Set<TEntity>().Add(entity);
        //_Context.Set<TEntity>().AddRange(entities);
        //_Context.Set<TEntity>().Remove(entity);
        //_Context.Set<TEntity>().RemoveRange(entities);
        }

        }
      • //Interfaccia specifica ICourseRepository che implementa IRepository<Course>
        //qui si aggiungono metodi supplementari rispetto a quelli forniti dall'
        //interfaccia generica e tipici dell'implementazione particolare
        public interface ICourseRepository : IRepository<Course>
        {
        IEnumerable<Course> GetTopSellingCourses(int count);
        IEnumerable<Course> GetCoursesWithAuthors(int pageIndex, int pageSize);
        }
      • //Classe specifica CourseRepository che implementa l'interfaccia ICourseRepository
        //Fornisce i metodi specifici per la gestione dell' entità <Course>
        public class CourseRepository : Repository<Course>, ICourseRepository
        {
        public CourseRepository(<My_DB>Context context): base(context)
        {
        }

        public IEnumerable<Courses> GetTopSellingCourses(int count)
        {
        return <My_DB>ContextProp.Courses
        .OrderByDescending(c => c.Price).Take(count).ToList();
        }

        public IEnumerable<Courses> GetCoursesWithAuthors(int pageIndex, int pageSize)
        {
        return <My_DB>ContextProp.Courses
        .Include(c => c.Authors)
        .OrderBy(c => c.Name)
        .Skip((pageIndex-1)*pageSize)
        .Take(pageSize)
        .ToList();
        }
        //espongo la classe DbContext come property
        public <My_DB>Context <My_DB>ContextProp
        {
        get { return Context as <My_DB>Context; }
        }
        }

    • IUnitOfWork => Esporre un’interfaccia IUnitOfWork e sviluppare l’implementazione concreta specifica per l’applicazione. Esporrà tutti i repository necessari (le loro interfacce).
      • //interfaccia IUnitOfWork
        public interface IUnitOfWork : IDisposable
        {
        //espongo repository come property per poter facilmente creare
        //dei mock e agevolare la testibilità
        ICoursesRepository Courses { get; }
        IVideosRepository Videos { get; }
        int Complete(); [or Save()]
        }
      • //classe UnitOfWork che implemente l'interfaccia IUnitOfWork e espone tramite
        //proprietà le interfacce ai vari repository.
        public class UnitOfWork : IUnitOfWork
        {
        private readonly <My_Db>Context _context;

        public UnitOfWork(<My_Db>Context context)
        {
        _context = context;
        Courses = new CourseRepository(_context);
        Videos = new VideoRepository(_context);
        }

        public ICoursesRepository Course {get; private set;}
        public IVideosRepository Video {get; private set;}

        public int Complete()
        {
        return _context.SaveChanges();
        }

        public void Dispose()
        {
        _context.Dispose();
        }
        }
      • //come usare UnitOfWork in un'application console
        using(var uow = new UnitOfWork(new <My_Db>Context()))
        {
        //Recupero il corso con Id=1
        var course = uow.Courses.Get(1);
        //Recupero tutti i corsi e i loro autori
        var allCourses = uow.Corses.GetCoursesWithAuthors(1, 4);
        //Elimino un autore e tutti i suoi corsi
        //DeleteCascade tra Authors e Courses è off, quindi dobbiamo rimuove manulmente
        //i corsi legati all'autore prima di eliminarlo
        var author = uow.Authors.GetAuthorsWithCourses(1);
        uow.Courses.RemoveRange(author.Courses);
        uow.Authors.Remove(author);

        //salvare le modifiche nel DB
        uow.Complete();
        }

 

Come strutturare un’applicazione (Dependency inversion principle)

  • Livello presentazione
    • Form, ViewModel, Controller.
  • Livello business logic (core)
    • IUnitOfWork.
    • IRepository.
    • I<Entity1>Repository…I<EntityN>Repository.
  • Livello accesso ai dati (persistence) 
    • UnitOfWork.
    • Repository, <My_Db>Context.
    • <Entity1>Repository…<EntityN>Repository.
  • Esempio di file system dell’applicazione =>
    • Migrations (cartella tipica di EF code-first)
    • Core =>
      • Domain (cartella con le domain entities)
        • Author.cs
          ..
          Video.cs
      • Repositories (cartella con le interfacce)
        • IAuthorRepository.cs 
          ..
          IVideoRepository.cs
      • IUnitOfWork.cs
    • Persistence =>
      • EntityConfiguration (cartella con le configurazioni di EF code-first)
        • CourseConfiguration.cs
          ..
          VideoConfiguration.cs
      • Repositories (cartella con le implementazioni concrete delle interfacce dei repository)
        • AuthorRepository.cs
          ..
          VideoRepository.cs
      • <My_Db>Context.cs
      • UnitOfWork.cs

 

Database First vs Code First

  • Database First =>
    • Come funziona =>
      • Lo sviluppatore crea il DB => Successivamente EF genera il modello concettuale (domain classes) basandosi su tale db.
      • Le informazioni sulla struttura del DB (store schema), del nostro modello (conceptual model) e della loro mappatura sono presenti in un file XML .edmx.
    • Cons => Limitato accesso in EF Core.
    • Quando si importano delle Stored Procedure / Funzioni =>
      • EF crea un wrapper nella classe <my_name>DbContext per ognuna delle Stored Procedure importate.
      • EF crea (dove necessario) delle Complex Types per gestire il ritorno delle Stored/Funzioni.   
    • Complex Types =>
      • Servono per gestire i ritorni di Stored Procedure e Funzioni 
      • Non corrispondono forzatamente con le entities create da EF a partire dalle tabelle del DB, perché una stored potrebbe voler ritornare una quantità di campi inferiore o superiore.
      • E’ possibile crearne di nuovi semplicemente dall’interfaccia del modello concettuale (aprire la view ‘Model Browser’ => ‘Functions Import’) una volta selezionata la stored o la funzione desiderata.
        • Cliccare su ‘Get column information’
        • Cliccare su ‘Create new complex type’
    • Validazione =>
      • E’ possibile aggiungere della logica di validazione alle classi del MODEL grazie al fatto che queste ultime sono create partial
      • Una parte è creata in automatico da EF e viene sovrascritta dopo ogni UPDATE from DB
      • Perciò bisogna creare una classe partial dove sarà inserita la logica desiderata.

 

      • //countryvalidation.cs
        //Sfruttando il partial method OnCountryNameChanging è possibile inserire della logica di validazione
        //della proprietà Name della tabella "Customer" senza il rischio che tale logica venga sovrascritta dall'EF
        public partial class Country
        {
        public partial OnCountryNameChanging(string value)
        {
        //inserisci qui la logica
        }
        }

    • Update =>
      • E’ necessario modificare prima il DB, poi fare il refresh del file .edmx.
      • Aprire file .edmx => click destro => ‘Update Model from DB’ => aprire il tab (Add, Refresh, Delete)  => selezionare l’ogetto da aggiornare.
    • Problemi con l’update =>
      1. Quando rinominiamo (o eliminiamo) una colonna nel DB oppure  
      2. Quando cambiamo un tipo ad una colonna nel DB oppure 
      3. Quando eliminiamo una tabella nel DB
        • In tutti questi casi EF non riporta le modifiche nel modello concettuale (per una questione di legacy)
        • Bisogna fare le modifiche nel modello concettuale manualmente 
        • Bisogna validarle facendo ‘click destro’ sul grafico .edmx => ‘Validate’.
    • Lavorare con gli enum => E’ possibile mappare una delle proprietà delle entities create da EF verso un enum. Ci sono 2 modi possibili:
      1. Aggiungere un enum type e assegnarlo alla proprietà =>
        • Nel modello concettuale aprire la view ‘Model Browser’ => ‘Enum Types’.
        • Inserire il nome del nuovo enum type.
        • Inserire i possibili valori.
        • Assegnare alla proprietà l’enum appena creato (il campo del DB deve essere un intero per poter essere mappato come enum nel modello concettuale).
      2. Referenziare un enum type gia esistente e assegnarlo alla proprietà
        1. Nel modello concettuale aprire la view ‘Model Browser’ => ‘Enum Types’.
        2. Inserire il nome del nuovo enum type.
        3. Ceccare ‘Reference External Type’ e inserire il nome (namespace completo) dell’enum da usare.
        4. Assegnare alla proprietà l’enum appena creato (il campo del DB deve essere un intero per poter essere mappato come enum nel modello concettuale).
    • Passi per creare/ inizializzare un progetto =>
        1. Creare la struttura desiderata nel DB di riferimento.
        2. Creare/Aprire un progetto e installare il package EntityFramework (se necessario)
          • install-package EntityFramework
        3. Aggiunge al progetto un ‘New Item’ del tipo ADO.NET Entity Data Model (es: <data_model_name>Model)
        4. Scegliere il contenuto del modello => ‘EF Designer from DB‘.
        5. Scegliere il server e il DB e indicare gli oggetti (tabelle, viste, stored) che si vogliono modellizzare.
        6. Nel form, cambiare il valore di default proposto per la connection string e mettere <data_model_name>DbContext (al posto di <data_model_name>Entities)
        7. Ceccare il checkbox ‘Pluralize or singularize generated object names‘.
    • EntityFramework crea automaticamente =>
      • Diagramma entità (.edmx) =>
        • <data_model_name>.edmx => diagramma del modello dati (con proprietà e le proprietà di navigazione)
        • E’ possibile modificare i nome delle tabelle (se non sono stati creati correttamente).
        • Ogni modifica al file .edmx comporta una modifica al modello dati che ci sta dietro.
        • Aprire il file .edmx e fare click destro:
          • Diagram‘ => ‘Export as image‘ (esporta un’immagine del diagramma)
          • Diagram‘ => ‘Layout diagram‘ (riordina il diagramma)
          • Diagram‘ => ‘Collapse/Expand
          • Scalar Propery Format‘ => ‘Display Name and Type

      • DbContext =>
        • <data_model_name>.Context.tt => file di tipo Text templating per generare il file <data_model_name>.Context.cs  contente la classe (<nomeprogetto>Entities) che eredita da DbContext.
      • File con le entità =>
        • <data_model_name>.tt => file di tipo Text templating per generare il file <data_model_name>.cs contenente le entità (domain classes) che mappano gli oggetti del DB.

  • Code First =>         
    • Come funziona => 
      • Lo sviluppatore crea il modello concettuale (domain classes).
        • Scrivere le proprie classi (Plain Old CRL Object) e proprietà che corrispondono secondo logiche desiderate alle tabelle e colonne del DB, il DbContext e il mapping.
        • Utilizzare le utility fornite dal Entity Framework Power Tool che farà scriverà tali classi per noi a partire da un DB esistente.
      • Successivamente EF crea le tabelle nel database.
        • EF non sfrutta il file di configurazione (.edmx file) per memorizzare lo schema del db ma verrà generato dinamicamente a runtime grazie a:
          • l’implementare la Fluent API tramite il ModelBuilder presente nel DbContext.
          • l’utilizzo delle “Data Annotations” direttamente nelle entità che formano il nostro “model”.
    • E’ un approccio per seguire i principi del Domain-Driven Design (DDD).
    • Fluent interface =>
      • //Esempio di classe fluent
        //L'ultimo metodo della catena fluent è StatAt, che infatti ritorna void
        //Gli altri metodi ritornano l'oggetto stesso
        public CustomerFluent
        {
        private _customer = new Customer();

        public CustomerFluent Name(string name)
        {
        _customer.Name = name;
        return this;
        }

        public CustomerFluent Born(datetime date)
        {
        _customer.Born = Convert.toDateTime(date);
        return this;
        }

        public void StayAt(string town)
        {
        _customer.Town = town;

        }
        }

        //come utilizzare una classe fluent
        CustomerFluent obj = new CustomerObject();
        obj.Name("Nicola")
        .Born("01/01/2010")
        .StayAt("Bergamo");
    • Mapping dei tipi

    • Conventions  (EF sfrutta le seguenti conventions per creare gli oggetti nel DB) => 
      • Nome Schema => dbo.
      • Nome Tabelle => Nome della relativa classe al plurale (es: l’entità Student diventerà la tabella Students).
      • Colonne =>
        • Nome => Identico al nome delle proprietà della classe che mappa la tabella.
        • Tipo string => nvarchar.
      • PrimaryKey =>
        • Nome => ID o <entity_name>ID (case insensitive).
        • Tipo => Integer (autogenerato).
      • ForeingKey =>  <nome_entità_esterna>_ID.
      • Index => EF crea un indice con il campo che è chiave esterna per la tabella in questione.
      • Colonne Null => EF crea delle colonne nulle per tutte le proprietà di tipo referenza e di tipo primitivo nullabile (es: string).
      • Colonne Not Null => EF crea colonne non nulle per le colonne PK e i tipi primitivi non nullabili (es int, float, decimal, datetime etc).
      • Lunghezza colonne => nvarchar(MAX)
      • Ordine delle colonne => EF crea le colonne nell’ordine in cui sono presenti nella classe, salvaguardando la PK come prima.
      • Delete a cascata => EF lo attiva di default per ogni tipo di relazione.
      • Considerare/Non considerare proprietà => EF mappa tutte le proprietà  tranne quelle marcate con l’attributo [NotMapped].
        • public class Student
          {
             public int StudentId { get; set; }
             public string StudentName { get; set; }
             [NotMapped]
             public int Age { get; set; }
          }

    • Overriding Conventions =>
      • Campo obbligatorio =>
        • Usando le ‘Data Annotations’: 
          • //nella classe che mappa la tabelle che si vuole modificare
            //aggiungere [Required] nella proprietà 
            //che si vuole rendere NOT NULL database
            using System.ComponentModel.DataAnnotations; 
            public class Course 
            { 
               public int Id { get; set; } 
               
               [Required] 
               public string Title { get; set; } 
            }
        • Usando la sintassi ‘Fluent API’:
          • //Nella classe Context del nostro progetto
            //aggiungere o modificare il metodo OnModelCreation come mostrato sotto
            public class <my_name>DbContext : DbContext 
            { 
                public DbSet<Course> Courses { get; set; } 
                public DbSet<Author> Authors { get; set; }
            
                protected override void OnModelCreating(DbModeBuilder modelBuilder) 
                {      
                     modelBuilder.Configuration.Add(new CourseConfiguration());  
                     modelBuilder.Configuration.Add(new AuthorConfiguration()); 

            //dispaly generated SQL in Visual Studio output window
            Database.log = (query) => Debug.Write(query); } public class CourseConfiguration : EntityTypeConfiguration<Course> { public CourseConfiguration() { Property(c => c.Description) .IsRequired; } } }
      • Nome tabella (e schema) =>
        • Usando le ‘Data Annotations’: 
          • [Table("<nuovo_nome_della_tabella>", Schema = "<nuovo_schema>")]
            public class Course
        • Usando la sintassi ‘Fluent API’:
          • modelBuilder.Entity<Course>()
               .ToTable("<nuovo_nome_della_tabella>", "<new_schema>");
      • Nome/Tipo colonne =>
        • Usando le ‘Data Annotations’: 
          • [Column("<nuovo_nome_colonna>"), TypeName = "varchar"]
            public string Title { get; set; }
        • Usando la sintassi ‘Fluent API’:
          • modelBuilder.Entity<Course>()
               .Property(t => t.Title)
               .HasColumnName("<nuovo_nome_colonna>")
               .HasColumnType("varchar")
               .HasColumnOrder(2);
      • Primary Key =>
        • Usando le ‘Data Annotations’: 
          • [Key]
            //disabilito la generazione automatica della campo chiava
            [DatabaseGenerated(DatabaseGeneratedOption.None)]
            //The database generates a value when a row is inserted. [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
            //The database generates a value when a row is inserted or updated. [DatabaseGenerated(DatabaseGeneratedOption.Computed)] public string ISBN { get; set; }
        • Usando la sintassi ‘Fluent API’:
          • modelBuilder.Entity<Book>()
              .HasKey(t => t.ISBN)
              .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

            //DatabaseGeneratedOption.None
            modelBuilder.Entity<Course>() .Property(p => p.CourseId) .ValueGeneratedNever();
            //DatabaseGeneratedOption.Identity
            modelBuilder.Entity<Course>()
            .Property(p => p.RecordNum)
            .ValueGeneratedOnAdd();

            //DatabaseGeneratedOption.Compute
            modelBuilder.Entity<Student>() .Property(s => s.CreatedDate) .HasDefaultValueSql("GETDATE()");
      • Composite Primary Key =>
        • Usando le ‘Data Annotations’: 
          • [Key]
            [Column(Order = 1)]
            public int OrderId { get; set; }
            
            [Key]
            [Column(Order = 2)]
            public int OrderItemId { get; set; }
        • Usando la sintassi Fluent API:
          • modelBuilder.Entity<Book>()
              .HasKey(t => new {t.OrderId , t.OrderItemId });
      •  Lunghezza colonna =>
        • Usando le ‘Data Annotations’: 
          • [MaxLength(255)]
            public string Name { get; set; }
        • Usando la sintassi ‘Fluent API’:
          • modelBuilder.Entity<Book>()
              .Property(t => t.Name)
              .HasMaxLength(255);
      •  Index =>
        • [Index(IsUnique = true)]
          public string Name { get; set; }
      • Multiple Index =>
        • [Index("IX_AuthorStudentsCount", 1)]
          public int AuthorId { get; set; }
          
          [Index("IX_AuthorStudentsCount", 2)]
          public int StudentsCount { get; set; }
      • Foreign Keys =>
        • Usando le ‘Data Annotations’ :
          • //l'entità Course ha un collegamento esterno con l'entità Author
            //non vogliamo che EF crei la chiave esterna di default Author_ID
            //Allora aggiungo una nuova proprietà AuthorId che sarà la chiave esterna
            //nella tabella e aggiungo un'annotazione per specificare la proprietà
            //di navigazione associata alla nuova chiave esterna
            public class Course
            {
                [ForeignKey("Author")]
                public int AuthorId { get; set; }
                public Author Author { get; set; }
                 
                oppure
                
                 public int AuthorId { get; set; }
                 [ForeignKey("AuthorId")] 
                 public Author Author { get; set; }
            }
        • Usando la sintassi Fluent API:
          • Bisogna configurare la relazione tra 2 tabelle A e B nelle due direzioni (dal punto di vista dell’entità A e del punto di vista dell’entità B) 
          • Uno-a-molti (1….*) =>
            • //Un Author (principal entity) può avere molti Course
              //Un Course (dependent entity) appartiene a un solo Author
              //[Opzionalmente possiamo specificare il nome della chiave esterna]
              //[Opzionalmente possiamo disabilitare il delete in cascata, cioè
              //evitare che se elimino un Autore allora elimino tutti i suoi Corsi]
              modelBuilder.Entity<Author>()
                   .HasMany(a => a.Courses)
                   .WithRequired(c => c.Author)
                  [.HasForeignKey(c=> c.AuthorId)]
                  [.WithCascadeOnDelete(false)];
              
              oppure
              
              modelBuilder.Entity<Course>()
                   .HasRequired(a => a.Author)
                   .WithMany(c => c.Courses)
                   [.HasForeignKey(c=> c.AuthorId)]
                   [.WithCascadeOnDelete(false)];

              -----------------------------------------------
              EF Core
              WithRequired => WithOne
              HasRequired => HasOne

              modelBuilder.Entity<Course>()
              .HasRequired(a => a.Author)
              .WithMany(c => c.Courses)
              [.HasForeignKey(c=> c.AuthorId)]
              [.OnDelete(false)];
          • Molti-a-Molti (*….*) =>
            • //Un Course (principal entity) può avere molti Tag
              //Un Tag (dependent entity) può appartenenre a molti Course
              //Con il metodo Map è possibile configurare la tabella matrice creata
              //nel DB per gestirere la relazione molti a molti, chiamandola ad
              //esempio 'CourseTags' al posto del default 'TagCourses'
              modelBuilder.Entity<Course>()
                 .HasMany(c => c.Tags)
                 .WithMany(t => t.Courses)
                 .Map(m => 
                  {
                    m.ToTable("CourseTags");
                    m.MapLeftKey("CourseId");
                    m.MapRightKey("TagId");
                  });
              
            • Senza payload => EF non genera la tabella di link tra le 2 entità legate da una relazione molti-a-molti ma solo le 2 entità che saranno legate dalle navigation property rispettive.
            • Con payload  => EF se la tabella di link tra le 2 entità legate da una relazione molti-a-molti ha almeno un proprietà (un campo oltre alle 2 chiavi) allora genera anche l’entità link e relaziona le 2 entità tramite questa.
          • Uno-a-zero/uno (1….0.1) =>
            • //Un Course (principal entity) può avere opzionalmente una dicitura
              //Una dicitura (dependent entity) appartiene a un corso
              modelBuilder.Entity<Course>()
                 .HasOptional(c => c.Caption)
                 .WithRequired(ca => ca.Course);
              oppure
              modelBuilder.Entity<Caption>()
                 .HasRequired(ca => ca.Course)
                 .WithOptional(c => c.Caption);
          • Uno-a-uno (1…1)
            • //Un Course (principal entity) ha una Cover
              //Una Cover (dependent entity) appartiene a un Course
              modelBuilder.Entity<Course>()
                 .HasRequired(c => c.Cover)
                 .WithRequiredPrincipal(co => co.Course);
              oppure
              modelBuilder.Entity<Cover>()
                 .HasRequired(co => co.Course)
                 .WithRequiredDependent(c => c.Cover);
              
              
    • Passi per creare/inizializzare un progetto =>
      1. Installare EntityFramework (creare un progetto e installare il package EntityFramework) 
        • install-package EntityFramework [-Version:6.1.3]
      2. ConnectionsString =>
        • Specificare la connectionString nel file app.config. L’attributo name deve avere lo stesso valore del nome della classe DbContext appena creata.
        • <connectionStrings>
           <add name ="<my_name>DbContextConn" connectionString="Data Source=DELL-RICHLAB;Initial Catalog=DatabaseFirstDemo;
          Integrated Security=True" providerName="System.Data.SqlCLient" />
          </connectionStrings>
      3. Attivare migrazione =>
        • Usa sola volta, è necessario attivare la migrazione nel Package Manager Console.
        • Deve esistere già la classe che eredita da DbContext (vedi sotto)
        • //se necessario prima installare gli EntityFramework tools (dotnet-ef):
          dotnet tool install --global dotnet-ef

          //verrà aggiunta una cartella Migrations //con il file Configurations.cs Enable-Migrations
    • Operazioni sincronizzazione =>
      1. Creare/Modificare il modello concettuale =>
        • Classi/Enum 
        • Le classi posso avere relazione 1 a molti, 1 a 1,  molti a molti, in funziona a come vengono scritte. 
        • public class Course
          {
              public int Id { get; set; }
              public string Title { get; set; }
              public float FullPrice { get; set; }
              public Author Author { get; set; }
              public ICollection<Tag> Tags { get; set; }
            
              public Course()
              {
                 Tags = new HashSet<Tag>();
              }
          }
      2. Creare classe DbContext  (Creare/Modificare la classe <data_model_name>DbContext e esporre uno o più DbSet<> per mappare gli oggetti del DB) =>
        • public class <my_name>DbContext : DbContext
          {
              public DbSet<Course> Courses { get; set; }
              public DbSet<Author> Authors { get; set; }
              public DbSet<Tag> Tags { get; set; }
          
              public <my_name>DbContext(): base("name=<my_name>DbContextConn")
              {
              }
          
              protected override void OnModelCreating(DbModelBuilder modelBuilder) 
              {
                   modelBuilder.Entity<Course>() .Property(t => t.Title) .IsRequired(); 
              }
          }
          
      3. Creare file di migrazione (preferire tante piccole migrazione a una grossa) =>
        • //nella cartella Migrations aggiunge il file di migrazione con i comandi
          //che corrispondono alle modifiche fatte sul codice e da riportare nel DB.
          //Non sono scritti direttamente in SQL ma in un linguaggio compatibile


          [command in the 'Package Manager Console'] Add-Migration <nome_della_migrazione> [-Force]
          Add-Migration InitialModel
          Add-Migration InitialCreate

          oppure

          [command window in the directory that has the project.json file]
          dotnet ef migrations add InitialModel
        • Il file di migrazione deriva dalla classe DbMigration.
        • Il file di migrazione ha 2 metodi =>
          • Up() => script per portare le modifiche dal modello concettuale al db
          • Down() => script per riportare la situazione del db allo stato in cui era prima di questa migrazione
        • Come dare il nome alle migrazioni =>  
          • Model Centric (es: AddCategory)
          • Database Centric (es: AddCategoriesTable)
          • //esempi 
            AddCategoriesTable
            AddCategoryColumnToCoursesTable
            AddDatePublishedColumnToCoursesTable
            RenameTitleToNameInCoursesTable
            DeleteDatePublishedColumnFromCoursesTable
            DeleteCategoriesTable
      4. Seeding
        • Metodo 1 => creo una migrazione vuota e inserisco gli statement per riempire le tabelle che voglio nel metodo Up()
        • Metodo 2  => implemento il metodo Seed
          • Ogni linea di codice presente nel medoto Seed(context) presente nel file configuration.cs  (..\Migrations\Configurations.cs) verrà eseguito ad ogni esecuzione della migrazione
          • protected override void Seed(CodeFirstFromDBDemo.PlutoContext context)
            {
              // This method will be called after migrating to the latest version 
              // by executing Update-Database command
              context.Authors.AddOrUpdate(p => p.Name, 
                  new Author 
                  { 
                     Name = "Autore 1" ,
                     Courses = new Collection<Course>()
                     {
                       new Course { Name = "Corso 2", Description = "Descrizione 2"}
                     }
                 },
                 new Author 
                 { 
                     Name = "Autore 2" ,
                     Courses = new Collection<Course>()
                     {
                       new Course { Name = "Corso 3", Description = "Descrizione 3"}
                     }
                  });
            }
      5. Eseguire file di migrazione (eseguire il codice generato nei file di migrazione) =>
        • //questo comando lancia tutti i file di migrazione che non sono stati
          //lanciati dall'ultima volta per allineare il modello concettuale e il DB
          //Nel DB esiste una tabella __MigrationHystory per capire quali migrazioni
          //sono state già lanciate.
          [command in the 'Package Manager Console'] 
          Update-Database

          oppure

          [command window in the directory that has the project.json file]
          dotnet ef database update
        • //Esempio di migrazione
          public partial class AddCategoriesTable : DbMigration { public override void Up() { CreateTable( "dbo.Categories", c => new { Id = c.Int(nullable: false, identity: true), Name = c.String(), }) .PrimaryKey(t => t.Id); Sql("INSERT INTO Categories VALUES ('Web Development')"); Sql("INSERT INTO Categories VALUES ('Programming languages')"); } public override void Down() { DropTable("dbo.Categories"); } }
      6. Recovery da una migrazione precedente
        • //é possibile far tornare il DB allo stato in cui era in una precedente migrazione

          //in EF 6
          [command in the 'Package Manager Console']
          Update-Database -TargetMigration: <migration_name> 

          //in EF Core
          [command in the 'Package Manager Console']
          Update-Dababase <migration-name>

          oppure

          dotnet ef database update <nome-della-migrazione-da-recuperare>
      7. Eliminazione dell’ultima migrazione
        • In locale
          • //In caso una migrazione sia stata creata per errore, è possibile elimina solo se non ancora eseguita nel DB.

            [command in the 'Package Manager Console']
            Remove-Migration

            oppure

            [command window in the directory that has the project.json file]
            dotnet ef migrations remove
        • Nel DB:
          • ATTENZIONE: Nel caso invece la migrazione sia già stata lanciata nel DB, allora sarà necessario crearne una per ripristinare la corretta situazione. Senza eliminare alcun file.
          • Se si desidere comunque forzare l'eliminazione nel DB, seguire i seguenti passaggi:
            1- Aprire la tabella denominata "_EFMigrationsHistory"
            2- Eliminare la riga corrispondente alla migrazione che desideri annullare
            3- Eseguire il comando in per eliminare la migrazione in locale (vedi sopra)
      8. Generazione SQL Script dalle migrazioni:
        • //Genero gli script da fornire al Sql server DBA dalla prima migrazione all'ultima
          Update­‐Database -­‐Script -­‐SourceMigration:0
          
          //Genero gli script da lanciare sul DB per il range di migrazioni indicato
          [command in the 'Package Manager Console'] Update­‐Database -­‐Script -­‐SourceMigration:Migr1 -­‐TargetMigration:Migr2

          oppure

          [command window in the directory that has the project.json file]
          dotnet ef migrations script
    • Problemi nella creazione automatica del file di migrazione =>
      1. Rinominare una proprietà di un’entità del modello concettuale (es: Course.Title => Course.Name) 
        • Nel file di migrazione EF eliminerebbe il vecchio campo nel DB e creerebbe il campo con il nuovo nome.
        • 2 possibili soluzioni =>
          • Nel file di migrazione eliminiamo le righe create automaticamente e usiamo:
            • //eliminare queste 2 righe create automaticamente
              AddColumn("dbo.Courses", "Name", c => c.String(nullable:false));
              DropColumn("dbo.Courses", "Title");
              //aggiungere manualmente 
              RenameColumn("dbo.Courses", "Title", "Name");
          • Aggiungo il comando SQL per popolare il nuovo campo con i valori del vecchio prima di eliminare quest’ultimo
            • //Ricordarsi di modificare opportunamente anche il metodo Down()
              AddColumn("dbo.Courses", "Name", c => c.String(nullable:false));
              Sql("UPDATE Courses SET Name = Title");
              DropColumn("dbo.Courses", "Title");
              
    • Code First con un DB già esistente => E’ possibile usare l’approccio CodeFirst anche nel caso si abbia già un DB esistente
      • Dal DB esistente si crea il modello (reverse engineering)
        • Aggiungere un file ‘ADO.NET Entity Data Model’.
        • Scegliere l’opzione ‘Code First from DB’.
        • Specificare il DB e la connection string per connettersi al DB esistente.
        • Lanciare una prima migrazione per abilitare il tracker del modifiche (si creerà un primo file di migrazione vuoto, non avendo fatto alcuna modifica).
        • A ogni modifica successiva si lavora come un classico approccio CodeFirst (ripetere i passi presenti nel paragrafo precedente ‘Passi per sincronizzare il DB dopo aver modificato il modello concettuale’).
  • Database First vs Code First =>
    • Entrambi gli approccia hanno pieno controllo del DB
    • Code First =>
      • Si ottiene il versioning del DB implicitamente.
      • Aumenta la produttività.
  • Model First (non molto usato) =>
    • Il programmatore scrive (tramite un software visuale offerto da EF) il modello concettuale (UML diagram) => Dopodiché EF genera lo schema concettuale (domain classes) e il DB.
    • In questo approccio non c’è un DB esistente.
    • Cons =>
      • Limitato accesso in EF 6.
      • Non supportato in EF Core.

 

Meccanismi di esecuzione dele query

  • Esecuzione differita delle query:
    • Le query non sono eseguite quando vengono create ma quando:
      • Si itera sulla variabile di tipo query. 
      • Si usano i metodi collezione => ToList(), ToArray(), ToDictionary().
      • Si usano i metodi singleton => First(), Last(), Single(), Count(), Max(), Min(), Average().
  • Caricamento entità correlate:
    • Lazy Loading (caricamento differito on demand, deve essere attivo) => 
      • Le entità correlate sono caricate (e le relative query eseguite) solo on demand (quando effettivamente interrogate) secondo le regole standard dell’esecuzione differita sul DB dei comandi LINQ to Entities.
      • Quando usare =>
        • Quando caricare un’oggetto è pesante (es: apertuta di un’applicazione). E’ preferibile caricare in prima battuta una versione ridotta dell’oggetto per poi caricare le varia navigation property solo quando necessario.
        • Desktop application.
      • Quando evitare =>
        • Web application. Quando creo la pagina devo conoscere già tutti i dati da visualizzare. Non è possibile farlo in 2 tempi.
        • Lazy loading costerebbe un’adanta e ritorno verso il DB con un abbassamento delle performance.
      • Come usare Lazy Loading
        • Per rendere una entità caricata in lazy loading è nesessario che siano virtual tutte le sue navigation properties:
        • //La navigation property Videos é marcata come virtual, quindi il meccaniscmo di lazy //loading è attivato. 
          //La query per caricare i Video legati al genere sarà eseguita
          //non al momento dell'eseguzione del metodo Single ma solo quando tale proprietà sarà
          //effettivamente interrogata var genere = context.Genres.Single(g => g.id ==1) public class Genre { public byte Id { get; set; } public virtual ICollection<Video> Videos { get; set; } public DateTime CreationDate { get; set; } public virtual ICollection<Video> Tags { get; set; } }
      • Attivare/Disattivare Lazy Loading in modo centralizzato =>
        • public class PlutoContext : DbContext
          {
             public PlutoContext : base("name=PlutoContext")
             {
                 this.Configuration.LazyLoadingEnable = true/false;
             }
          }
    • Eeager Loading (caricamente esplicito con un solo round-trip) =>
      • Se si vuole anticipare il caricamento delle entità correlate in un solo colpo (una sola query).
      • Come usare Eager Loading =>
        • //UNICA QUERY per recuperare: 
          // - sia l'entità corrente (Genre)
          // - sia i dati dell'entità correlata (Video) TRAMITE JOIN
          using System.Data.Entity; var genere = context.Genres .Include(g => g.Video) or .Include("Video") .Single(g => g.id ==1)
      • Caricare entità oltre il primo livello di correlazione =>
        • //Load single property (L'autore del video del genere id=1)
          var genere = context.Genres
                         .Include(g => g.Video.Author)
                         .Single(g => g.id ==1) 
          
          //Load multiple property (Il moderatore dei tags legati al genere id=1)
          var genTag = context.Genres
                          .Include(g => g.Tags.Select(t => t.Moderator))
          // oppure .Include(nameof(Genre.Tags) + "." + nameof(Moderator))
          // oppure .Include(b => b.Tags)
          .ThenInclude(t => t.Moderator)
          .Single(g => g.id ==1)
      • Pro => Una sola andata e ritorno verso il DB (dove si recuperano tutti i dati richiesti)
      • Cons => Se si caricano troppe entità correlate, la query può diventare complessa e lenta
    • Explicit Loading (caricamento esplicito con più round-trip) =>
      • Se si vuole anticipare il caricamento delle entità in modo più modulare (con 2 o più query).
      • Come usare Explicit Loading =>
        • //Invece di recuperare sia nell'entità corrente (Genre) sia i dati dell'entità correlata (Tags)
          // con una sola query che può diventare complessa, la spezzo in molte sotto query.
          //MOLTE SOTTO QUERY per recuperare
          // - l'entità corrente (Genre)
          // - e i dati dell'entità correlata (Video) SENZA JOIN var genTag = context.Genres.Single(g => g.id ==1); //Official way context.Entry(genTag) .Collection(g => g.Tags) or .Reference(g => g.Classification) .Load(); //Official way con filtro sui dati da caricare context.Entry(genere) .Collection(g => g.Tags) .Query() .Where(t => t.Moderator = "Billy") .Load();
      • Pro =>
        • La query è meno complessa che nel caso Eager Loading.
        • E’ possibile applicare filter (where) per restituire esattamente i dati desiderati senza doverli caricare tutti per poi filtrare successivamente.
      • Cons => Più andate e ritorno verso il DB per recuperare i dati dell’entità principale e quelli nell’entità correlate che si desidareno 
  • Persistenza =>
    • Scenario connesso => 
      • La stessa istanza della context class (derivata da DbContext) è usata per leggere e scrivere le entità.
      • Tiene traccia di tutte le entità durante il suo ciclo di vita.
      • E’ adatto a applicazioni windows con il DB in locale.
      • Pros => Veloce
      • Cons => Utilizza più risorse
    • Scenario disconnesso =>
      • Due differenti istanze della context class sono utilizzate per leggere e scrivere le entità.
      • Un’istanza è rilasciata (disposed) dopo avere letto i dati dal DB e una è creata per salvare le entità nel DB.
      • E’ più complesso perchè prima di comandare il context.SaveChanges() è necessario settare il giusto stato delle entità aggiunte al context.
      • Pros =>
        • Nessuna connessione aperta al DB.
        • Richiede meno risorse.
      • Cons =>
        • Meno rapido.
        • E’ necessario settare l’appopriato stato di ogni entità prima di salvare le modifiche verso il DB.

 

Visual Studio Class Diagram (file .cd)

  • In Visual Studio è possibile creare un diagramma del proprio modello dati a partire dalle classi che descrivono le entità del modello
  • Class diagram (.cd) =>
    • Aggiungere nuovo ‘class diagram’
      • Visual Studio => Seleziona progetto => Add new file => ‘Class Diagram’
      • Dalla finestra ‘Class View’ => Drag and Drop le classi che si vogliono aggiungere al diagramma
        • Fare l’unfold dei dettagli
        • Le eredità sono visualizzate
    • Visualizzare relazioni
      • Click Dx sul campo di cui si vuole visualizzare la relazione
        • selezionare ‘Show as Association’
        • oppure selezionare ‘Show as Collection Association’
    • Esportare file .jpg
      • Click Dx su una parte bianca del diagramma

 

Gestione dell’ereditarietà

  • EF può mappare una gerarchia di tipi .NET a un database.
  • Ciò consente di scrivere le  entità .NET nel codice come al solito, utilizzando tipi di base e derivati, e fare in modo che EF crei senza problemi lo schema del database appropriato
  • Ci sono 3 possibili approcci 
    • Table-per-hierarchy (THP)

    • Table-per-type (TPT)

    • Table-per-concrete-class (TPC)

 

  • Table-per-hierarchy Inheritance  (impostazione di default) =>
    • Come funziona:
      • Viene generata  un’unica tabella per archiviare i dati per tutti le classi nella gerarchia
      • Viene generata una colonna discriminatore viene utilizzata per identificare il tipo rappresentato da ciascuna riga.
    • E’ necessario inserire nel DbContext tutte le classi della gerarchia (classe base e classi derivate)
      • // L'esempio seguente espone un DbSet for Blog e la relativa sottoclasse RssBlog
        public abstract class Blog
        {
        public int BlogId { get; set; }
        public string Url { get; set; }
        }

        public class RssBlog : Blog
        {
        public string RssUrl { get; set; }
        }

        // Se Blog ha un'altra sottoclasse, non sarà inclusa nel modello.
        //CASO 1- E' possibile includere le classi desiderate esplicitamente nel DbContext
        internal class MyContext : DbContext
        {
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<RssBlog> RssBlogs { get; set; }
        }

        //CASO 2A- E' possibile includere la classi desiderate implicitamente nel OnModelCreating
        modelBuilder.Entity<Blog>();
        modelBuilder.Entity<RssBlog>();

        oppure

        //CASO 2B
        modelBuilder.Entity<RssBlog>().HasBaseType<Blog>();
    • Configurazioni possibili tramite Fluent API:
      • // È possibile configurare 
        // - il nome e il tipo della colonna del discriminatore
        // - i valori utilizzati per identificare ciascun tipo nella gerarchia

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
        modelBuilder.Entity<Blog>()
        .HasDiscriminator<string>("blog_type")
        .HasValue<Blog>("blog_base")
        .HasValue<RssBlog>("blog_rss");
        }

        // E' possibile mappare il 'discriminator' ad una proprietà presente nella classe .NET
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
        modelBuilder.Entity<Blog>()
        .HasDiscriminator(b=>b.BlogType);

        modelBuilder.Entity<Blog>()
        .Property(e => e.BlogType)
        .HasMaxLenght(200)
        .HasColumnName("blog_type);
        }
    • Mappatura del modello nel DB:

    • Tabelle derivate =>
      • Le colonne delle tabelle derivate sono automaticamente mappate nel DB come ‘nullable’
      • Shared columns:
        • Per impostazione predefinita, quando due tipi di entità di pari livello nella gerarchia hanno una proprietà con lo stesso nome, verranno mappati su due colonne separate.
        • Tuttavia, se il loro tipo è identico, possono essere mappati sulla stessa colonna del database
  • Table-per-type pattern  =>
    • Come funziona:
      • Tutte le classi della relazione di ereditartetà vengono mappate su singole tabelle con le loro rispettive proprietà (classi concrete + classi astratte).
      • Le tabelle che mappano i tipi memorizzano anche una chiave esterna che unisce la tabella derivata con la tabella di base.
    • Configurazioni possibili tramite Fluent API:
      • //Mappo implicitamente nel metodo OnModelCreating le entità che costituiscono la nostra gerarchia di tipi
        //affinché EF le prenda in considerazione nella creazione del DB
        modelBuilder.Entity<Blog>().ToTable("Blogs");
        modelBuilder.Entity<RssBlog>().ToTable("RssBlogs");
    • Mappatura del modello nel DB:
      • CREATE TABLE [Blogs] (
        [BlogId] int NOT NULL IDENTITY,
        [Url] nvarchar(max) NULL,
        CONSTRAINT [PK_Blogs] PRIMARY KEY ([BlogId])
        );

        CREATE TABLE [RssBlogs] (
        [BlogId] int NOT NULL,
        [RssUrl] nvarchar(max) NULL,
        CONSTRAINT [PK_RssBlogs] PRIMARY KEY ([BlogId]),
        CONSTRAINT [FK_RssBlogs_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([BlogId]) ON DELETE NO ACTION
        );
    • In molti casi risulta essere più lento dell’approccio THP
  • Table per Concrete Class pattern (EF 6 e > FE Core 7) =>
    • Viene creata una tabelle per ogni classe concreta. Le classi astratte non avranno rappresentazione nel DB.
    • Le proprietà delle classi astratte fanno allora parte di ognuna delle classi concrete che ereditano da esse.

 

Gestione degli enum

  • Se in una delle classi del nostro modello usiamo un campo di tipo enum, l’EF mapperà questo campo come int nel tabella che mappa l’entità.
  • Il seguente modello dati
    • enum Color
      {
      Black,
      White,
      Red
      }

      class Brick
      {
      public int Id { get; set; }
      public string Title { get; set; } = String.Empty;
      public Color? Color { get; set; }
      }
  • Produrrà il campo ‘Color’ di tipo ‘int’ nella tabella ‘Brick’

 

Relationships in EF 6 & CORE

  • Data model =>
    • Uno-a-Molti
      • Entità molti: campo primary key (PK) + navigation property verso l’altra entità.
      • Entità uno: campo primary key (PK) + navigation property verso l’altra entità + campo foreign key (FK) verso l’latra entità.

      • //esempio di inserimento delle 2 entità unite da una relazione 1 a molti
        [HttpPost]
        public async Task<ActionResult<List<Dish>>> Create([FromBody] IngretientDto ingretientDto)
        {
        if (ingretientDto == null)
        return BadRequest("Invalid input");

        var dish = await _dbContext.Dishes.FirstOrDefaultAsync(d => d.Id == ingretientDto.Dish.Id);
        if(dish == null){
        dish = _mapper.Map<Dish>(ingretientDto.Dish);
        _dbContext.Add(dish);
        }

        var ingretient = _mapper.Map<Ingretient>(ingretientDto );
        dish.Ingretients.Add(ingretient);
        await _dbContext.SaveChangesAsync();

        //201
        return await Get(ingretient.DishId);
        }
         
    • Molti-a-Molti (EF Core: non esiste una convenzione di default)
      • A differenza di quanto accede in EF 6, non c’é nessuna convenzione che crei automaticamente la tabella di raccordo tra le 2 entità legate da una relazione molti-a-molti é percio’ necessario definire una tabella di jointura che include le FK e le proprietà di navigazione verso le 2 entità

      • Definire una relazione one-to-many tra le 2 entità e la tabella di jointura appena creata

      • Nella tabella di jointura configurare entrambe le KF come chiave composita tramite Fluent API.

      • Eventualmente é possibile non seguire nel DB la nomenclatura usata nelle entità del nostro model:
        • modelBuilder.Entity<StudentCourse>().HasKey(sc => new { sc.SId, sc.CId });

          modelBuilder.Entity<StudentCourse>()
          .HasOne<Student>(sc => sc.Student)
          .WithMany(s => s.StudentCourses)
          .HasForeignKey(sc => sc.SId);


          modelBuilder.Entity<StudentCourse>()
          .HasOne<Course>(sc => sc.Course)
          .WithMany(s => s.StudentCourses)
          .HasForeignKey(sc => sc.CId);
    • Molti-a-Molti (EF 6: una una convenzione di default)
      • Tabella intermedia
        • Una relazione molti-a-molti verrà mappata creando una tabella intermedia che avrà una relazione uno-a-molti con le due entità che rappresentano la nostra relazione molti-a-molti
        • Tale tabella è creata automticamente nel DB senza che sia presente nel data model

      • Nel DB viene creata una tabelle ‘BrickTag’ che lega le 2 tabelle ‘Brick’ e ‘Tags’

    • Molti-a-Molti con proprietà addizionali (EF 6)
      • Tabella intermedia
        • Creo la tabella intermedia manualmente per poterci aggiungere le proprietà addizionali

      • Nel DB vengono create le 3 tabelle (vedi sotto)

    • Uno-a-uno (EF Core : usa una convenzione di default)
      • Entity Framework Core ha introdotto una convenzione di default che configura automaticamente una relazione One-to-One tra due entità.

      • EF Core crea une indice unique sulla colonna NotNull StudentId(FK ) nella tabellaStudentAddresses.Cio’ assicura che il valore della colonna chiave esterna StudentIddebba essere unico cosa che é necessaria per la relazione 1-a-1.
    • Uno-a-zero-uno (EF 6: non esiste una convenzione di default)
      •  Uno <Student> puo’ essere salvato con o senza un <StutendAddress>. Uno <StudentAddress> deve essere salvato con uno <Student> 
      • Il campo “StudentAddress.StudentAddressId” é sia PK della tabella “StudentAddress” 
      • Il campo “StudentAddress.StudentAddressId” é sia FK della tabella “Student” 
        • Infatti applicando [ForeignKey(“Student”)] sulla proprietà “StudentAddress.StudentAddressId” la si rende una chiave esterna per l’entità <Student>

  • Fluent API =>
    • Uno-a-zero-uno
      • protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
        // Configure Student & StudentAddress entity
        modelBuilder.Entity<Student>()
        .HasOptional(s => s.Address) // Mark Address property optional in Student entity
        .WithRequired(ad => ad.Student); // mark Student property as required in StudentAddress entity. Cannot save StudentAddress without Student
        }
    • Uno-a-uno
      • //EF Core
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
        modelBuilder.Entity<Student>()
        .HasOne<StudentAddress>(s => s.Address)
        .WithOne(ad => ad.Student)
        .HasForeignKey<StudentAddress>(ad => ad.AddressOfStudentId);
        oppure
        modelBuilder.Entity<StudentAddress>()
        .HasOne<Student>(ad => ad.Student)
        .WithOne(s => s.Address)
        .HasForeignKey<StudentAddress>(ad => ad.AddressOfStudentId);
        }

      • //EF 6
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
        // Configure StudentId as FK for StudentAddress
        modelBuilder.Entity<Student>()
        .HasRequired(s => s.Address)
        .WithRequiredPrincipal(ad => ad.Student);
        }
    • Uno-a-Molti
      • //EF Core
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
        modelBuilder.Entity<Student>() .HasOne<Grade>(s => s.Grade) .WithMany(g => g.Students) .HasForeignKey(s => s.CurrentGradeId);

        oppure

        modelBuilder.Entity<Grade>()
        .HasMany<Student>(g => g.Students)
        .WithOne(s => s.Grade)
        .HasForeignKey(s => s.CurrentGradeId);
        }


         
      • //EF 6
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
        modelBuilder.Entity<Student>()
        .HasRequired<Grade>(s => s.CurrentGrade) //L'entità <Student> ha la proprietà di navigazione "CurrentGrade" di tipo Grade
        .WithMany(g => g.Students) //L'entità <Grande> ha la proprietà di navigazione "Students" di tipo ICollection<Student>
        .HasForeignKey<int>(s => s.CurrentGradeId); //La tabella "Srudent" ha la FK CurrentGradeId che punta alla tabella "Grade"

        oppure

        modelBuilder.Entity<Grade>()
        .HasMany<Student>(g => g.Students)
        .WithRequired(s => s.CurrentGrade)
        .HasForeignKey<int>(s => s.CurrentGradeId); 
        }

    • Molti-a-Molti
      • //EF 6
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {

        modelBuilder.Entity<Student>()
        .HasMany<Course>(s => s.Courses)
        .WithMany(c => c.Students)
        .Map(cs =>
        {
        cs.MapLeftKey("StudentRefId");
        cs.MapRightKey("CourseRefId");
        cs.ToTable("StudentCourse");
        });

        }

 

EF 6 vs EF Core

  • EF Core =  EF 6.
    • DbContext & DbSet
    • Data Model
    • Querying using Linq-to-Entities
    • Change Tracking
    • SaveChanges
    • Migrations
  • EF 6 :
    • EDMX/ Graphical Visualization of Model
    • Entity Data Model Wizard (for DB-First approach)
    • ObjectContext API
    • Querying using Entity SQL.
    • Automated Migration
    • Inheritance: Table per type (TPT)
    • Inheritance: Table per concrete class (TPC)
    • Many-to-Many without join entity
    • Entity Splitting
    • Spatial Data
    • Lazy loading of related data
    • Stored procedure mapping with DbContext for CUD operation
    • Seed data
    • Automatic migration
  • EF Core:
    • Easy relationship configuration
    • Batch INSERT, UPDATE, and DELETE operations
    • In-memory provider for testing
    • Support for IoC (Inversion of Control)
    • Unique constraints
    • Shadow properties
    • Alternate keys
    • Global query filter
    • Field mapping
    • DbContext pooling
    • Better patterns for handling disconnected entity graphs

 

Risorse

  • LinqPad (linkpad.net)
    • Passi per connettersi ad un DbContext esistente =>
      1. Cliccare su ‘Add Connection’.
      2. Ceccare ‘Use a typed data context from your own assembly”.
      3. Scegliere ‘Entity Framework (DbContext)’.
      4. Indicare il path dell’assembly ‘Path to Custom Assembly’ => seleziona il DbContext
      5. Indicare il path del file di configurazione ‘Path to application config file’ dove si trova la connectionString (es: app.cofig).
  • Web clipboard
    • CL1P.NET è la clipboard di Internet.
    • Il modo più semplice per inviare dati tra dispositivi connessi a Internet.
    • Scegli qualsiasi URL che inizi con cl1p.net e inserisci i dati.
    • Quindi su qualsiasi altro dispositivo inserisci lo stesso URL.
  • Slide on line