Migliorare il codice e le prestazioni dell'accesso ai dati con Entity Framework

di Stefano Mostarda, in LINQ,

Entity Framework è sicuramente l'ORM in circolazione con la curva di apprendimento meno impegnativa. Tuttavia, sebbene approcciare ad Entity Framework sia molto semplice, soprattutto grazie al designer integrato in Visual Studio, quando ci si addentra nelle funzionalità di basso livello ci sono diverse tecniche e API che possiamo utilizzare per migliorare il codice delle nostre applicazioni.

In quest'articolo vedremo come sfruttare alcune di queste funzionalità per migliorare il codice di accesso ai dati; in particolare vedremo come utilizzare le stored procedure che tornano più resultset, come mappare una entity su più tabelle o una tabella su più entity per ottimizzare le performance e altro ancora. Cominciamo col vedere come manipolare lo stato delle singole proprietà di una entity.

Manipolare lo stato delle proprietà di una entity

Quando attacchiamo una entity al contesto e successivamente ne impostiamo lo stato a modificato, questa viene marcata come da aggiornare e quindi quando successivamente invochiamo il metodo SaveChanges, Entity Framework genera un'istruzione SQL di UPDATE per quella entity. Nell'istruzione SQL Entity Framework salva tutte le proprietà dell'entity sul database in quanto impostando l'entity come modificata anche le proprietà vengono marcate come modificate.

In alcuni casi abbiamo la necessità di persistere solo alcune proprietà dell'entity e quindi dobbiamo in qualche modo specificare a Entity Framework quali sono queste proprietà. Per fare questo possiamo recuperare le proprietà e poi impostarla come modificata. Tuttavia, quando ci sono molte proprietà da modificare, recuperarle una ad una e modificarne lo stato può essere un lavoro tedioso e ripetitivo che può provocare errori. In questi casi può tornare utile una nuova funzionalità di Entity Framework 5 ovvero la possibilità di marcare una proprietà come non modificata. Sfruttando questa funzionalità, possiamo approcciare al problema in maniera inversa impostando l'entity come modificata e marcando solo le proprietà da non persistere come non modificate. Il prossimo esempio mostra come ottenere questo risultato.

ctx.Contacts.Attach(contact);
//tutte le proprietà sono modificate
ctx.Entry(contact).State = EntityState.Modified;

//la proprietà City è non modificata
ctx.Entry(contact).Property(c => c.City).IsModified = false; 

ctx.SaveChanges();

Con le versioni precedenti di Entity Framework quest'approccio non era valido in quanto una volta impostata una proprietà come modificata, non potevamo reimpostarla come non modificata.

Override del metodo SaveChanges ed evento ObjectMaterialized

Tempo fa ho lavorato a un'applicazione che non prevedeva l'utilizzo dei decimali e quindi gli importi sul database erano espressi come numeri interi (su un campo comunque decimale) le cui ultime due cifre erano rappresentate dai decimali. Naturalmente, l'interfaccia lavorava con numeri decimali quindi c'era chiaramente una differenza di dati da gestire.

Il punto più giusto dove gestire questa differenza è il più possibile vicino ai dati. Entity Framework espone due punti di estensione che fanno proprio al caso nostro. Il primo punto è l'evento ObjectMaterialized che viene scatenato ogni volta che un'entity viene creata da una query e all'interno del quale possiamo convertire l'importo nel formato voluto (quindi dividendolo per 100). Il secondo punto è il metodo SaveChanges che possiamo sovrascrivere per convertire l'importo dal formato dell'interfaccia al formato del database (quindi moltiplicandolo per 100). Quello che dobbiamo fare è creare una classe di contesto che eredita da quella creata dal designer (nel caso usiamo l'approccio database-first) oppure modificare quella che creiamo quando usiamo code-first. Una volta fatto questo sfruttiamo i punti di estensione appena menzionati. Cominciamo ora col vedere come sfruttare l'evento ObjectMaterialized

Evento ObjectMaterialized

L'evento ObjectMaterialized non viene esposto dalla classe DbContext bensì dalla classe ObjectContext. Questo significa che dall'istanza della classe DbContext dobbiamo risalire all'ObjectContext sottostante e poi sottoscriverci all'evento. Una volta sottoscritti all'evento non dobbiamo fare altro che verificare che l'entity sia del tipo desiderato e poi modificarne la proprietà. Ovviamente, modificando la proprietà, l'entity andrà in stato di modificato; per evitare questo ci basta marcare la proprietà come non modificata sfruttando il codice visto nella precedente sezione.

public class MyContext : NorthwindEntities
{
  public MyContext()
    : base()
  {
    InitializeMaterializer();
  }

  private void InitializeMaterializer()
  {
    var ctx = ((IObjectContextAdapter)this).ObjectContext;
    ctx.ObjectMaterialized += 
      new ObjectMaterializedEventHandler(oc_ObjectMaterialized);
  }

  void oc_ObjectMaterialized(object sender, 
    System.Data.Objects.ObjectMaterializedEventArgs e)
  {
    var tracked = Entry(e.Entity).State != EntityState.Detached;

    if (e.Entity is EntityWithAmount)
    {
      var entity = (EntityWithAmount)e.Entity;
      entity.Amount = entity.Amount / 100;
      ctx.Entry(entity).Property(c => c.Amount).IsModified = false; 
    }
  }
}

A questo punto il nostro strato di business riceve l'entity con la proprietà Amount con i decimali corretti. Vediamo ora come fare l'inverso cioè come formattare correttamente gli importi prima della persistenza sul database.

Override SaveChanges

Eseguire l'override del metodo SaveChanges ci permette di eseguire operazioni prima e dopo la persistenza dei dati sul database. Nel nostro caso, quello che dobbiamo fare è recuperare le entity che devono essere persistite e per quelle che hanno un importo dobbiamo moltiplicarlo per 100. Il prossimo esempio mostra come raggiungere questo risultato.

public override int SaveChanges()
{
  var entriesToPersist = ChangeTracker.Entries().
    Where(c => c.State == EntityState.Added || c.State == EntityState.Modified);
  for (var i = 0; i < entriesToPersist.Count(); i++)
  {
    var entity = entriesToPersist.ElementAt(i).Entity;
    if (entity is EntityWithAmount)
    {
      var typedEntity = (EntityWithAmount)entity;
      typedEntity.Amount = typedEntity.Amount * 100;
    }
  }
}

La bellezza di questa tecnica sta nel fatto che gli strati superiori non sono a conoscenza del fatto che ci sia questa conversione degli importi e quindi abbiamo perfettamente isolato il formato dei dati dal codice di business.

In questo caso abbiamo fatto un esempio molto semplice relativo al formato dei decimali, ma ovviamente questa tecnica può essere sfruttata per effettuare conversioni di ogni tipo (si può utilizzare questa tecnica per convertire importi in valuta e molto altro ancora).

Ora cambiamo decisamente argomento e vediamo un trucco per ottimizzare le prestazioni.

3 pagine in totale: 1 2 3
Contenuti dell'articolo

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti