¿Cómo borro las entidades rastreadas en el marco de la entidad?

81

Estoy ejecutando un código de corrección que se ejecuta sobre una gran pila de entidades, a medida que avanza, su velocidad disminuye, eso se debe a que la cantidad de entidades rastreadas en el contexto aumenta con cada iteración, puede llevar mucho tiempo, así que estoy guardando los cambios al final de cada iteración. Cada iteración es independiente y no cambia las entidades cargadas previamente.

Sé que puedo desactivar el seguimiento de cambios, pero no quiero, porque no es un código de inserción masiva, sino cargando las entidades y calculando algunas cosas y si los números no son correctos, configure los nuevos números y actualice / elimine / cree algunas entidades adicionales. Sé que puedo crear un nuevo DbContext para cada iteración y probablemente eso funcionaría más rápido que hacerlo todo en la misma instancia, pero estoy pensando que podría haber una mejor manera.

Entonces la pregunta es; ¿Hay alguna forma de borrar las entidades cargadas previamente en el contexto de la base de datos?

hazimdikenli
fuente
5
Simplemente puede llamar context.Entry(entity).State = EntityState.Detachedy dejará de rastrear esa entidad en particular.
Ben Robinson
2
¿Por qué no crea una instancia de un nuevo contexto? Realmente no hay una gran sobrecarga a menos que necesite un código muy optimizado.
Adrian Nasui
Entity Framework llega al servidor de base de datos solo para las entidades cambiadas, no tiene preocupaciones de rendimiento al respecto. pero puede crear un nuevo contexto que solo consista en las tablas con las que trabaja para hacerlo más rápido.
İsmet Alkan
1
@IsThatSo detectar los cambios lleva tiempo, no me preocupa DbPerformance.
hazimdikenli
¿Realmente ha depurado y rastreado el cuello de botella de rendimiento, o simplemente asumiendo esto?
İsmet Alkan

Respuestas:

117

Puede agregar un método a su DbContexto un método de extensión que use ChangeTracker para separar todas las entidades agregadas, modificadas y eliminadas:

public void DetachAllEntities()
{
    var changedEntriesCopy = this.ChangeTracker.Entries()
        .Where(e => e.State == EntityState.Added ||
                    e.State == EntityState.Modified ||
                    e.State == EntityState.Deleted)
        .ToList();

    foreach (var entry in changedEntriesCopy)
        entry.State = EntityState.Detached;
}
David Sherret
fuente
5
Asegúrese de llamar a 'ToList' después del 'Dónde'. De lo contrario, arroja una System.InvalidOperationException: 'La colección fue modificada; la operación de enumeración puede no ejecutarse. '
mabead
6
En mi prueba unitaria, el estado de las entradas es "Sin modificar", quizás porque utilizo una transacción que revierto al final del método de prueba. Esto significaba que tenía que establecer el estado de las entradas rastreadas en "Separado" sin verificar el estado actual, para que mis pruebas se ejecutaran todas correctamente a la vez. Llamo al código anterior justo después de revertir la transacción, pero lo tengo, revertir seguramente significa estado no modificado.
barbara.post
2
(y también var entitydebería ser realmente, var entryya que es la entrada, no la Entidad real)
oatsoda
2
@DavidSherret ¡Pensé que ese podría ser el caso! Cogí esto porque en una aplicación de prueba mía, recorrer 1000 elementos y marcar como Separado tomó aproximadamente 6000 ms con el código existente. Aproximadamente 15 ms con el nuevo :)
oatsoda
3
¿No debería usar también e.State == EntityState.Unchanged? Aunque la entidad no ha cambiado, todavía se realiza un seguimiento en el contexto y es parte del conjunto de entidades que se consideran durante DetectChanges. Por ejemplo, agrega una nueva entidad (está en estado Agregado), llama a SaveChanges y la entidad agregada ahora tiene el estado Sin cambios (va en contra del patrón UnitOfWork, pero op preguntó: Estoy guardando cambios al final de cada iteración )
jahav
25

1. Posibilidad: separar la entrada

dbContext.Entry(entity).State = EntityState.Detached;

Cuando separa la entrada, el rastreador de cambios dejará de rastrearla (y debería dar como resultado un mejor rendimiento)

Ver: http://msdn.microsoft.com/de-de/library/system.data.entitystate(v=vs.110).aspx

2. Posibilidad: trabajar con su propio Statuscampo + contextos desconectados

Tal vez desee controlar el estado de su entidad de forma independiente para poder usar gráficos desconectados. Agregue una propiedad para el estado de la entidad y transforme este estado en dbContext.Entry(entity).Statecuando realice operaciones (use un repositorio para hacer esto)

public class Foo
{
    public EntityStatus EntityStatus { get; set; }
}

public enum EntityStatus
{
    Unmodified,
    Modified,
    Added
}

Consulte el siguiente enlace para ver un ejemplo: https://www.safaribooksonline.com/library/view/programming-entity-framework/9781449331825/ch04s06.html

road242
fuente
Creo que agregar un método de extensión y ejecutar todas las entidades en ChangeTracker y separarlas debería funcionar.
hazimdikenli
15

Estoy ejecutando un servicio de Windows que actualiza los valores cada minuto y he tenido el mismo problema. Intenté ejecutar la solución @DavidSherrets, pero después de unas horas esto también se volvió lento. Mi solución fue simplemente crear un nuevo contexto como este para cada nueva ejecución. Simple pero funciona.

_dbContext = new DbContext();

Ogglas
fuente
5
Esta no es una solución 'simple pero funciona' para su objetivo. Es el único correcto. El contexto debe vivir lo menos posible, 1 contexto por 1 transacción es la mejor opción.
Yegor Androsov
2
De acuerdo con @pwrigshihanomoronimo, el contexto sigue el patrón de diseño UnitOfWork. Según lo definido por Martin Fowler:> Mantiene una lista de objetos afectados por una transacción comercial y> coordina la escritura de cambios y la resolución de> problemas de concurrencia.
Michel
Esto pareció hacerme el truco. Estoy sincronizando datos y tengo aproximadamente la mitad de millón de transacciones (insertar y actualizar) en tablas con un par de millones de filas. Así que luché con OutOfMemoryException después de un tiempo (o varias operaciones). Esto se resolvió cuando creé un nuevo DbContext por X número de bucles que era un lugar natural para volver a instanciar el contexto. Eso probablemente desencadenó GC de una mejor manera, sin tener que pensar en la posible pérdida de memoria en EF y operaciones de larga ejecución. ¡Gracias!
Mats Magnem
4

Me encontré con este problema y, finalmente, encontré una mejor solución para aquellos que usan la típica inyección de dependencia de .NET Core. Puede usar un DbContext con ámbito para cada operación. Eso se restablecerá DbContext.ChangeTrackerpara que SaveChangesAsync()no se atasque al verificar las entidades de iteraciones pasadas. Aquí hay un ejemplo de método de controlador ASP.NET Core:

    /// <summary>
    /// An endpoint that processes a batch of records.
    /// </summary>
    /// <param name="provider">The service provider to create scoped DbContexts.
    /// This is injected by DI per the FromServices attribute.</param>
    /// <param name="records">The batch of records.</param>
    public async Task<IActionResult> PostRecords(
        [FromServices] IServiceProvider provider,
        Record[] records)
    {
        // The service scope factory is used to create a scope per iteration
        var serviceScopeFactory =
            provider.GetRequiredService<IServiceScopeFactory>();

        foreach (var record in records)
        {
            // At the end of the using block, scope.Dispose() will be called,
            // release the DbContext so it can be disposed/reset
            using (var scope = serviceScopeFactory.CreateScope())
            {
                var context = scope.ServiceProvider.GetService<MainDbContext>();

                // Query and modify database records as needed

                await context.SaveChangesAsync();
            }
        }

        return Ok();
    }

Dado que los proyectos ASP.NET Core generalmente usan DbContextPool, esto ni siquiera crea / destruye los objetos DbContext. (En caso de que estuviera interesado, DbContextPool realmente llama a DbContext.ResetState()y DbContext.Resurrect(), pero no recomendaría llamarlos directamente desde su código, ya que probablemente cambiarán en futuras versiones). Https://github.com/aspnet/EntityFrameworkCore/blob/v2 .2.1 / src / EFCore / Internal / DbContextPool.cs # L157

Mate
fuente
0

Desde EF Core 3.0 hay una API interna que puede restablecer el ChangeTracker. No use esto en el código de producción, lo menciono ya que puede ayudar a alguien en las pruebas según el escenario.

using Microsoft.EntityFrameworkCore.Internal;

_context.GetDependencies().StateManager.ResetState();

Como dice el comentario sobre el código;

Esta es una API interna que admite la infraestructura de Entity Framework Core y no está sujeta a los mismos estándares de compatibilidad que las API públicas. Puede cambiarse o eliminarse sin previo aviso en cualquier comunicado. Solo debe usarlo directamente en su código con extrema precaución y sabiendo que hacerlo puede provocar fallas en la aplicación al actualizar a una nueva versión de Entity Framework Core.

Stuart Hallows
fuente
-3

Bueno, mi opinión es que, en mi experiencia, EF, o siendo cualquier forma, no funciona bien bajo demasiada presión o modelo complejo.

Si no quiere rastrear, realmente diría ¿por qué incluso hacer orm?

Si la velocidad es la fuerza principal, nada supera a los procedimientos almacenados y la buena indexación.

Y más allá, si sus consultas son siempre por id, considere usar un nosql o quizás sql con solo key y json. Esto evitaría el problema de impedancia entre clases y tablas.

Para el escenario de su caso, cargar cosas en objetos de esa manera me parece muy lento. Realmente en su caso, los procedimientos almacenados son mejores porque evita el transporte de datos a través de la red, y sql es mucho más rápido y está optimizado para administrar la agregación y cosas así.

Kat Lim Ruiz
fuente