Rilevamento e mancata verifica delle query

Il rilevamento del comportamento controlla se Entity Framework Core mantiene informazioni su un'istanza di entità nel relativo rilevamento modifiche. Se viene rilevata un'entità, eventuali modifiche rilevate nell'entità vengono mantenute nel database durante SaveChanges. EF Core corregge anche le proprietà di navigazione tra le entità in un risultato della query di rilevamento e le entità presenti nello strumento di rilevamento delle modifiche.

Nota

I tipi di entità senza chiave non vengono mai rilevati. Ovunque in questo articolo siano menzionati i tipi di entità, si riferisce ai tipi di entità che hanno una chiave definita.

Suggerimento

È possibile visualizzare l'esempio di questo articolo in GitHub.

Query con rilevamento delle modifiche

Per impostazione predefinita, le query che restituiscono tipi di entità sono con rilevamento delle modifiche. Una query di rilevamento indica che tutte le modifiche apportate alle istanze di entità vengono mantenute da SaveChanges. Nell'esempio seguente la modifica alla classificazione dei blog viene rilevata e salvata in modo permanente nel database durante SaveChanges:

var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1);
blog.Rating = 5;
context.SaveChanges();

Quando i risultati vengono restituiti in una query di rilevamento, EF Core verifica se l'entità è già nel contesto. Se EF Core trova un'entità esistente, viene restituita la stessa istanza, che può potenzialmente usare meno memoria e essere più veloce di una query senza rilevamento. EF Core non sovrascrive i valori correnti e originali delle proprietà dell'entità nella voce con i valori del database. Se l'entità non viene trovata nel contesto, EF Core crea una nuova istanza di entità e la collega al contesto. I risultati della query non contengono alcuna entità aggiunta al contesto, ma non ancora salvata nel database.

Query senza registrazione

Le query senza rilevamento sono utili quando i risultati vengono usati in uno scenario di sola lettura. In genere sono più veloci da eseguire perché non è necessario configurare le informazioni sul rilevamento delle modifiche. Se non è necessario aggiornare le entità recuperate dal database, è necessario usare una query senza rilevamento. È possibile impostare una singola query senza rilevamento. Una query senza rilevamento offre anche risultati in base a ciò che si trova nel database ignorando eventuali modifiche locali o entità aggiunte.

var blogs = context.Blogs
    .AsNoTracking()
    .ToList();

Il comportamento di rilevamento predefinito può essere modificato a livello di istanza del contesto:

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

var blogs = context.Blogs.ToList();

La sezione successiva illustra quando una query senza rilevamento potrebbe essere meno efficiente di una query di rilevamento.

Risoluzione di identità

Poiché una query di rilevamento usa lo strumento di rilevamento delle modifiche, EF Core esegue la risoluzione delle identità in una query di rilevamento. Quando si materializza un'entità, EF Core restituisce la stessa istanza di entità dal rilevamento delle modifiche, se è già in corso il rilevamento. Se il risultato contiene più volte la stessa entità, viene restituita la stessa istanza per ogni occorrenza. Query senza rilevamento:

  • Non usare lo strumento di rilevamento delle modifiche e non eseguire la risoluzione delle identità.
  • Restituisce una nuova istanza dell'entità anche quando la stessa entità è contenuta nel risultato più volte.

Il rilevamento e la mancata registrazione possono essere combinati nella stessa query. Ovvero, è possibile avere una query senza rilevamento, che esegue la risoluzione delle identità nei risultati. Analogamente AsNoTracking all'operatore queryable, è stato aggiunto un altro operatore AsNoTrackingWithIdentityResolution<TEntity>(IQueryable<TEntity>). Nell'enumerazione è stata aggiunta anche la QueryTrackingBehavior voce associata. Quando la query per l'uso della risoluzione delle identità è configurata senza rilevamento, viene usato uno strumento di rilevamento delle modifiche autonomo in background durante la generazione dei risultati della query in modo che ogni istanza venga materializzata una sola volta. Poiché questo rilevamento modifiche è diverso da quello nel contesto, i risultati non vengono rilevati dal contesto. Dopo l'enumerazione completa della query, lo strumento di rilevamento modifiche esce dall'ambito e viene sottoposto a Garbage Collection in base alle esigenze.

var blogs = context.Blogs
    .AsNoTrackingWithIdentityResolution()
    .ToList();

Configurazione del comportamento di rilevamento predefinito

Se ci si trova a modificare il comportamento di rilevamento per molte query, è consigliabile modificare invece il valore predefinito:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying.Tracking;Trusted_Connection=True")
        .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}

In questo modo tutte le query non vengono monitorate per impostazione predefinita. È comunque possibile aggiungere AsTracking per eseguire il rilevamento di query specifiche.

Rilevamento e proiezioni personalizzate

Anche se il tipo di risultato della query non è un tipo di entità, EF Core continuerà a tenere traccia dei tipi di entità contenuti nel risultato per impostazione predefinita. Nella query seguente, che restituisce un tipo anonimo, le istanze di Blog nel set di risultati verranno incluse nel rilevamento delle modifiche.

var blog = context.Blogs
    .Select(
        b =>
            new { Blog = b, PostCount = b.Posts.Count() });

Se il set di risultati contiene tipi di entità provenienti dalla composizione LINQ, EF Core li tiene traccia.

var blog = context.Blogs
    .Select(
        b =>
            new { Blog = b, Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault() });

Se il set di risultati non contiene tipi di entità, non viene eseguito alcun rilevamento. Nella query seguente viene restituito un tipo anonimo con alcuni dei valori dell'entità ( ma nessuna istanza del tipo di entità effettivo). Non sono presenti entità rilevate che escono dalla query.

var blog = context.Blogs
    .Select(
        b =>
            new { Id = b.BlogId, b.Url });

EF Core supporta l'esecuzione della valutazione client nella proiezione di primo livello. Se EF Core materializza un'istanza di entità per la valutazione client, verrà rilevata. In questo caso, poiché si passano blog entità al metodo StandardizeURLclient , EF Core tiene traccia anche delle istanze del blog.

var blogs = context.Blogs
    .OrderByDescending(blog => blog.Rating)
    .Select(
        blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog) })
    .ToList();
public static string StandardizeUrl(Blog blog)
{
    var url = blog.Url.ToLower();

    if (!url.StartsWith("http://"))
    {
        url = string.Concat("http://", url);
    }

    return url;
}

EF Core non tiene traccia delle istanze di entità senza chiave contenute nel risultato. Ef Core tiene traccia di tutte le altre istanze dei tipi di entità con una chiave in base alle regole precedenti.

Versioni precedenti

Prima della versione 3.0, EF Core presentava alcune differenze nella modalità di esecuzione del rilevamento. Le differenze rilevanti sono le seguenti:

  • Come illustrato nella pagina Valutazione client e server, EF Core supporta la valutazione client in qualsiasi parte della query prima della versione 3.0. La valutazione client ha causato la materializzazione delle entità, che non facevano parte del risultato. Ef Core ha quindi analizzato il risultato per rilevare cosa tenere traccia. Questa progettazione presentava alcune differenze nel modo seguente:

    • Valutazione client nella proiezione, che ha causato la materializzazione ma non ha restituito l'istanza dell'entità materializzata non è stata rilevata. L'esempio seguente non tiene traccia blog delle entità.

      var blogs = context.Blogs
          .OrderByDescending(blog => blog.Rating)
          .Select(
              blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog) })
          .ToList();
      
    • EF Core non ha tracciato gli oggetti che escono dalla composizione LINQ in alcuni casi. Nell'esempio seguente non è stato possibile tenere traccia Postdi .

      var blog = context.Blogs
          .Select(
              b =>
                  new { Blog = b, Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault() });
      
  • Ogni volta che i risultati della query contengono tipi di entità senza chiave, l'intera query è stata eseguita senza rilevamento. Ciò significa che i tipi di entità con chiavi, che sono nel risultato non sono stati rilevati neanche.

  • EF Core usato per eseguire la risoluzione delle identità nelle query senza rilevamento. Usa riferimenti deboli per tenere traccia delle entità che erano già state restituite. Pertanto, se un set di risultati conteneva più volte la stessa entità, si otterrebbe la stessa istanza per ogni occorrenza. Anche se un risultato precedente con la stessa identità esce dall'ambito ed è stato sottoposto a Garbage Collection, EF Core ha restituito una nuova istanza.