Strutturare un'applicazione reale con Entity Framework

di Marco De Sanctis, in LINQ,

Utilizzare un O/RM come strato di accesso ai dati di un'applicazione non è un compito immediato, al di là di ciò che si potrebbe essere portati a pensare; in alcuni casi, infatti, è necessaria una progettazione architetturale estremameente specifica, basata insomma sulle peculiarità che il framework scelto offre e volta ad attutire l'impatto di eventuali punti deboli. Si tratta di considerazioni valide a 360 gradi, per le quali ADO.Net Entity Framework non rappresenta un'eccezione. Il primo passo per poter sfruttare al meglio il nuovo O/RM targato Microsoft, allora, è sicuramente quello di capire quali sono i meccanismi che si mettono in moto dietro le quinte e che culminano nella generazione delle query SQL opportune per gestire la persitenza di un domain model.

Ciclo di vita di una Entity

Quando, all'interno del codice, viene creata una nuova istanza di una entity di dominio, ad esempio un customer, essa è totalmente sconosciuta a Entity Framework, non appartenendo a nessuno degli EntitySet definiti dall'entity data model. Un oggetto in questo stato è definito come Detached e alla perdita della reference, verrà irrimediabilmente distrutto dal Garbage Collector. Per evitare ciò, è necessario renderlo Persistent, agganciandolo al rispettivo EntitySet con uno dei metodi AddTo... dell'ObjectContext generati dal designer di Visual Studio, notificando quindi il contesto della sua esistenza:

Customer myCustomer = new Customer();
context.AddToCustomer(myCustomer);

Nel caso invece in cui una entity venga recuperata da database, ad esempio tramite una query LINQ, per default la sua reference viene direttamente annessa all'ObjectContext e pertanto resa automaticamente persistente.

Una volta in tale stato, Entity Framework è in grado, tramite l'ObjectStateManager, di tracciarne eventuali modifiche, flaggandola come Inserted, Modified o Deleted e producendo le opportune query SQL quando si vorranno salvare i dati con il metodo

context.SaveChanges();

Quanto detto sembra essere fin troppo semplice e lineare, ma purtroppo nell'ambito delle web application si tratta di un esempio quasi mai reale. Immaginiamo di dover realizzare una pagina che consenta di recuperare un customer dato il suo Id, apportare alcune modifiche e salvarle su database; in questo scenario, una tipica transazione applicativa (fetch del customer, modifica e salvataggio) comprende concettualmente almeno due cicli di request/response:

  • il primo carica il customer e mostra i dati all'utente
  • il secondo recepisce le modifiche effettuate in form dall'utente ed effettua il salvataggio.

Scenario1

La conseguenza è che, almeno in prima approssimazione, entity e datacontext non possono restare in vita per tutta la sua durata, potendo esistere solo all'interno di una HttpRequest. Come gestire una situazione di questo genere? In fase di visualizzazione (ad esempio al Page Load) l'idea più semplice è sicuramente quella di istanziare entity e context per lo stretto tempo necessario a valorizzare gli elementi presenti in pagina:

protected void Page_Load(object sender, EventArgs e)
{
  if (!IsPostBack)
    loadCustomer(this.customerId);
}

private void loadCustomer(int customerId)
{
  using (ObjectContext context = new ObjectContext())
  {
    Customer customer =
      (from c in context.CustomerSet
       where c.Id == customerId
       select c).First();

    this.txtId.Text = customer.Id.ToString();
    this.txtName.Text = customer.Name;
    // e così via...
  }
}

Al click sul button salva, poi, tramite un nuovo context ci si preoccuperà di ricaricare il Customer da database, apportare e salvare le modifiche:

protected void btnSave_Click(object sender, EventArgs e)
{
  using (ObjectContext context = new ObjectContext())
  {
    Customer customer =
      (from c in context.CustomerSet
       where c.Id == customerId
       select c).First();

    customer.Name = txtName.Text;
    customer.Address = txtAddress.Text;
    // e così via...
    context.SaveChanges();
  }
}

Questo approccio presenta sostanzialmente un paio di grosse problematiche:

  • In uno scenario multiutente, in cui sia necessario gestire problematiche di concorrenza, si è costretti comunque a fare tutto a mano: è il programmatore che deve capire come comportarsi nel caso in cui la query eseguita al salvataggio non restituisca nessun customer (perchè ad esempio un altro utente l'ha nel frattempo eliminato) o a dover salvare e confrontare eventuali campi di versioning per evitare modifiche contemporanee da parte di più utenti allo stesso oggetto.
  • Il tutto è di fatto semplice da gestire fintanto che l'oggetto Customer non diviene troppo complesso; già in presenza di alcune lookup o addirittura di relazioni master-detail, le cose iniziano a complicarsi e non poco

In particolare, quando ci si imbatte in situazioni come quella descritta nel punto due, solitamente si preferisce mantenere il customer in sessione in modo da modificarlo nell'arco dei vari postback; uno scenario di questo tipo, però, è profondamente diverso da ciò che è stato descritto a inizio articolo, dato che ora si ha la necessità che la entity sopravviva al datacontext che l'ha recuperata o, in altri termini, che venga in qualche modo rimossa da quest'ultimo.

4 pagine in totale: 1 2 3 4

Attenzione: Questo articolo contiene un allegato.

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