Le novità di Entity Framework Core 2.1

di Stefano Mostarda, in LINQ,

Alla Build della scorsa settimana è stata annunciata la Release Candidate 1 di .NET Core 2.1 e dei framework a esso collegati: ASP.NET Core ed Entity Framework Core. La RC ha anche la Go Live quindi possiamo usarla in scenari di produzione. Questo significa che le specifiche sono ormai stabili e non ci saranno più cambiamenti da qui alla RTM se non bug fix.

La nuova versione di Entity Framework Core (EF d'ora in poi) introduce una serie di novità molto importanti che vanno a colmare le principali lacune che non permettevano l'utilizzo di EF in diversi scenari. Infatti, oltre a un generale miglioramento delle performance, sono state introdotte funzionalità come il lazy loading, la traduzione del metodo LINQ GroupBy in SQL, la possibilità di lavorare con le view, l'utilizzo di value type e altro ancora. In questo articolo parleremo di queste novità mostrando come queste rendano più semplice lo sviluppo con EF rispetto alle versioni precedenti.

Lazy loading

Una delle funzionalità più richieste dagli sviluppatori è stato il lazy loading. Questa funzionalità è stata presente sin dalla prima versione di Entity Framework per .NET Framework, ma non era stata ancora portata in EF Core. Tuttavia, non è stato fatto un semplice porting della funzionalità da EF6 a EF Core, ma una vera e propria riscrittura che ha portato ad alcuni cambiamenti.

EF mette a disposizione due modi per abilitare il lazy loading: attraverso l'uso di proxy o attraverso l'uso di ILazyLoader. Entrambe le tecniche hanno i loro pro e i loro contro. Andiamo ad analizzarle partendo dalla tecnica che fa uso dei proxy.

Utilizzo dei proxy

La tecnica che fa uso dei proxy prevede che le nostre entity non siano sealed e che le navigation property siano marcate come virtual. In questo modo EF può generare un proxy che eredita dalla nostra entity e che esegue l'override delle navigation property aggiungendo il codice che esegue la query a run time se la proprietà non è stata ancora caricata.

public class Order
{
    public int Id { get; set; }
    public virtual ICollection<OrderDetail> Details { get; set; }
}

public class OrderDetail
{
    public int Id { get; set; }
    public virtual Order Order { get; set; }
} 

Oltre a questo, ci sono altri due compiti che dobbiamo svolgere. Il primo è referenziare l'assembly Microsoft.EntityFrameworkCore.Proxies che contiene il codice per generare il proxy a partire dalla nostra classe. Il secondo è abilitare l'utilizzo dei proxy all'interno delle opzioni di EF attraverso il metodo UseLazyLoadingProxies.

services.AddDbContext<AppContext>(
  b => b
    .UseSqlServer(connectionstring)
    .UseLazyLoadingProxies());

A questo punto la fase di configurazione è conclusa e possiamo cominciare a scrivere codice che sfrutta il lazy loading. Nel prossimo esempio recuperiamo l'ordine e successivamente ne recuperiamo i dettagli in lazy loading.

// Recupera l'ordine
var order = ctx.Orders.First(o => o.Id == 1);

// Recupera i dettagli in lazy loading
var order = ctx.Orders.First(o => o.Id == 1);

L'utilizzo dei proxy ha il vantaggio di richiedere poco codice sia nelle entity sia per la configurazione. Tuttavia, il fatto di avere un proxy significa che, se utilizziamo la reflection, potremmo avere risultati inaspettati in quanto il tipo dell'oggetto che EF ritorna non è il nostro tipo, bensì il tipo del proxy. Quindi, prima di selezionare questa tecnica di lazy loading è bene testarla per evitare di incorrere in problemi inaspettati.

Utilizzo di ILazyLoader

L'utilizzo dell'interfaccia ILazyLoader prevede che la nostra entity riceva questa interfaccia nel costruttore e la utilizzi nel getter e nel setter delle navigation property per recuperare i dati. In questo caso non dobbiamo né rendere la classe non sealed né dichiarare le navigation property come virtual in quanto non abbiamo un proxy, ma utilizziamo l'interfaccia come mostrato nel codice

public class Order
{
    private ICollection<OrderDetail> _details;

    public Order()
    {
    }

    private Order(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }
    private ILazyLoader LazyLoader { get; set; }

    public int Id { get; set; }

    public ICollection<OrderDetail> Details
    {
        get => LazyLoader?.Load(this, ref _details);
        set => _details = value;
    }
}

Quando recuperiamo la classe Order dal database, EF inietta automaticamente l'interfaccia. Se creiamo un'istanza della classe nel codice e la attacchiamo al contesto, EF sarà in grado di sfruttare il lazy loading dall'attach in poi (grazie alla proprietà privata LazyLoader). Questo ci garantisce una univocità di comportamento in tutti i casi. Se eseguiamo di nuovo il codice che recupera l'ordine e poi i suoi dettagli sfruttando per le classi il codice appena mostrato, vedremo che il risultato è il medesimo.

Questa tecnica di lazy loading ha l'ovvio vantaggio di non utilizzare i proxy, tuttavia comporta una notevole scrittura di codice in tutte le entity e soprattutto crea una dipendenza tra queste ed EF. Per eliminare il problema della dipendenza possiamo iniettare nella classe un delegato di tipo Action<object, string> al posto di ILazyLoader, ma questo comporta ulteriore scrittura di codice. Maggiori informazioni su questa tecnica possono essere reperite a questo indirizzo.

Supporto per System.Transactions

A partire dalla versione 2.1, ADO.NET su .NET Core supporta le classi del namespace System.Transactions. Fino alla versione 2.0, se avessimo provato a usare la classe TransactionScope avremmo ottenuto un'eccezione. Questo significa che possiamo eseguire comandi di aggiornamento dei dati sul database sia manualmente sia tramite EF o che possiamo eseguire più chiamate al metodo SaveChanges e far confluire tutti i comandi nella stessa transazione.

using (var scope = new TransactionScope())
{
  // Esegue comandi di aggiornamento tramite ADO.NET
  ...

  // Esegue comandi di aggiornamento tramite EF
  ctx.Orders.Add(new Order { ... });
  ctx.SaveChanges();

  ctx.Orders.Add(new Order { ... });
  ctx.SaveChanges();
  
  // Esegue il commit della transazione
  scope.Complete();
}

È importante sottolineare che EF basa il suo supporto alle classi di System.Transactions sul fatto che il provider del database le supporti. Il provider per SqlServer incluso in EF supporta System.Transactions, ma non è detto che tutti i provider supportino queste classi soprattutto nella fase in cui le API sono state appena rilasciate. Possiamo aspettarci che in un futuro abbastanza breve tutti i provider supportino queste API, ma è comunque bene eseguire dei test o leggere la documentazione del provider per essere sicuri che questo supporti le API richieste.

Un'ulteriore limitazione delle API di System.Transactions su .NET Core consiste nel fatto che non supportano le transazioni distribuite quindi non possono essere utilizzati più resource manager.

2 pagine in totale: 1 2
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

Top Ten Articoli

Articoli via e-mail

Iscriviti alla nostra newsletter nuoviarticoli per ricevere via e-mail le notifiche!

In primo piano

I più letti di oggi

In evidenza

Misc