Consultas de seguimiento frente a consultas de no seguimiento

El comportamiento de seguimiento controla si Entity Framework Core mantiene información sobre una instancia de entidad en su herramienta de seguimiento de cambios. Si se realiza el seguimiento de una entidad, cualquier cambio detectado en ella se conserva en la base de datos durante SaveChanges. EF Core también corrige las propiedades de navegación entre las entidades de un resultado de consulta de seguimiento y las entidades que se encuentran en la herramienta de seguimiento de cambios.

Nota:

No se realiza el seguimiento de los tipos de entidad sin clave. Siempre que en este artículo se mencionen los tipos de entidad, se refiere a aquellos con una clave definida.

Sugerencia

Puede ver un ejemplo de este artículo en GitHub.

Consultas de seguimiento

De manera predeterminada, las consultas que devuelven tipos de entidad son consultas de seguimiento. Una consulta de seguimiento significa que SaveChanges conserva los cambios en las instancias de entidad. En el ejemplo siguiente, se detecta el cambio en la clasificación de los blogs y se conserva en la base de datos durante SaveChanges:

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

Cuando los resultados se devuelven en una consulta de seguimiento, EF Core comprueba si la entidad ya está en el contexto. Si EF Core encuentra una entidad existente, se devuelve la misma instancia, que puede usar menos memoria y ser más rápida que una consulta sin seguimiento. EF Core no sobrescribe los valores actuales y originales de las propiedades de la entidad en la entrada con los valores de la base de datos. Si no se encuentra la entidad en el contexto, EF Core crea una instancia de la entidad y la asocia al contexto. Los resultados de la consulta no contienen ninguna entidad que se haya añadido al contexto pero que aún no se haya guardado en la base de datos.

Consultas de no seguimiento

Las consultas de no seguimiento son útiles cuando los resultados se usan en un escenario de solo lectura. Su ejecución suele ser más rápida porque no es necesario configurar la información de seguimiento de cambios. Si no necesita actualizar las entidades recuperadas de la base de datos, se debe usar una consulta de no seguimiento. Puede establecer una consulta individual para que sea de no seguimiento. Una consulta de no seguimiento también proporciona resultados en función de lo que haya en la base de datos, sin tener en cuenta los cambios locales ni las entidades agregadas.

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

También puede cambiar el comportamiento de seguimiento predeterminado en el nivel de instancia de contexto:

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

var blogs = context.Blogs.ToList();

En la sección siguiente se explica cuándo una consulta sin seguimiento podría ser menos eficaz que una consulta de seguimiento.

Resolución de identidad

Como una consulta de seguimiento usa la herramienta de seguimiento de cambios, EF Core realiza la resolución de identidades en una consulta de este tipo. Al materializar una entidad, EF Core devuelve la misma instancia de entidad de la herramienta de seguimiento de cambios si ya está en proceso de seguimiento. Si el resultado contiene la misma entidad varias veces, se devuelve la misma instancia con cada repetición. Consultas sin seguimiento:

  • Las consultas sin seguimiento no usan la herramienta de seguimiento de cambios y no realizan la resolución de identidades.
  • Devuelven una nueva instancia de la entidad incluso cuando la misma entidad está contenida varias veces en el resultado.

El seguimiento y el no seguimiento se pueden combinar en la misma consulta. Es decir, puede tener una consulta sin seguimiento, que lleva a cabo la resolución de identidades en los resultados. Al igual que el operador consultable AsNoTracking, hemos agregado otro operador AsNoTrackingWithIdentityResolution<TEntity>(IQueryable<TEntity>). También hay una entrada asociada agregada en la enumeración QueryTrackingBehavior. Cuando se configura la consulta para usar la resolución de identidades sin seguimiento, se usa un seguimiento de cambios independiente en segundo plano al generar los resultados de la consulta, por lo que cada instancia se materializa solo una vez. Dado que esta herramienta de seguimiento de cambios es diferente de la que se encuentra en el contexto, el contexto no realiza el seguimiento de los resultados. Una vez que se completa la enumeración de la consulta, la herramienta de seguimiento de cambios queda fuera de ámbito y se recopilan los elementos no utilizados según sea necesario.

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

Configuración del comportamiento de seguimiento predeterminado

Si comprueba que cambia el comportamiento de seguimiento de muchas consultas, es posible que quiera cambiar el valor predeterminado en su lugar:

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

Esto hace que, de forma predeterminada, todas las consultas sean sin seguimiento. Todavía puede agregar AsTracking para realizar el seguimiento de consultas específico.

Seguimiento y proyecciones personalizadas

Incluso si el tipo de resultado de la consulta no es un tipo de entidad, EF Core seguirá realizando el seguimiento de los tipos de entidad contenidos en el resultado de forma predeterminada. En la consulta siguiente, que devuelve un tipo anónimo, se hará seguimiento de las instancias de Blog en el conjunto de resultados.

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

Si el conjunto de resultados contiene tipos de entidad que proceden de la composición LINQ, EF Core realizará un seguimiento de ellos.

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

Si el conjunto de resultados no contiene ningún tipo de entidad, no se realiza ningún seguimiento. En la consulta siguiente, se devuelve un tipo anónimo con algunos de los valores de la entidad (pero sin instancias del tipo de entidad real). No hay entidades con seguimiento que procedan de la consulta.

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

EF Core admite la evaluación del cliente en la proyección de nivel superior. Si EF Core materializa una instancia de entidad para la evaluación del cliente, se realizará un seguimiento de esta. Aquí, como se pasan entidades de blog al método cliente StandardizeURL, EF Core también realizará un seguimiento de las instancias 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 no realiza un seguimiento de las instancias de entidad sin clave contenidas en el resultado. Sin embargo, sí lo hace de todas las demás instancias de tipos de entidad con clave según las reglas anteriores.

Versiones anteriores

Antes de la versión 3.0, EF Core presentaba algunas diferencias en el modo en que se realizaba el seguimiento. Las diferencias destacables son las siguientes:

  • Como se explica en la página Evaluación de cliente frente a servidor, EF Core admitía la evaluación de clientes admitidos en cualquier parte de la consulta anterior antes de la versión 3.0. La evaluación de clientes provocaba la materialización de entidades, las cuales no formaban parte del resultado. Por lo tanto, EF Core analizaba el resultado para detectar de qué realizar el seguimiento. Este diseño tenía algunas diferencias, como se indica a continuación:

    • No se realizaba el seguimiento de la evaluación de clientes en la proyección, lo que provocaba la materialización pero no se devolvía la instancia de la entidad materializada. En el ejemplo siguiente no se realizaba un seguimiento de entidades blog.

      var blogs = context.Blogs
          .OrderByDescending(blog => blog.Rating)
          .Select(
              blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog) })
          .ToList();
      
    • En algunos casos, EF Core no realizaba un seguimiento de los objetos que procedían de la composición LINQ. En el ejemplo siguiente no se realizaba un seguimiento de Post.

      var blog = context.Blogs
          .Select(
              b =>
                  new { Blog = b, Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault() });
      
  • Siempre que los resultados de consulta contenían tipos de entidad sin clave, significaba que no se hacía un seguimiento de la consulta completa. Esto quiere decir que tampoco se realizaba un seguimiento de los tipos de entidad con claves que estaban en el resultado.

  • EF Core realizaba la resolución de identidades en consultas de no seguimiento. Se usaban referencias débiles para mantener el seguimiento de entidades que ya se habían devuelto. Por lo tanto, si un conjunto de resultados contenía la misma entidad varias veces, obtenía la misma instancia para cada caso. Sin embargo, si un resultado anterior con la misma identidad se salía del ámbito y generaba un elemento no utilizado, EF Core devolvía una nueva instancia.