Optimización de Rendimiento en EF Core: Pasando de 3 a 0.3 Segundos

Identificando el Problema de Rendimiento

Recientemente, un usuario reportó que la carga de la lista de hogares tomaba entre 3 y 4 segundos. El sgiuiente fragmento SQL fue capturado con SQL Server Profiler: ``` SELECT [p].[Id], [p].[Nombre], [p].[PropietarioId], [p].[FechaCreacion], [o].[Id], [o].[Nombre], [o].[Nivel], [o].[NivelVIP] FROM [Hogares] AS [p] LEFT JOIN [Inmortales] AS [o] ON [p].[PropietarioId] = [o].[Id] WHERE [p].[Tipo] = 1 ORDER BY [p].[FechaCreacion] DESC


Herramientas de Diagnóstico
---------------------------

### 1. Registro de Logs en EF Core

Se puede configurar el registro de logs para obtener más detalles sobre las consultas generadas por EF Core. ```
builder.Services.AddDbContext<contextocultivo>(opciones =>
    opciones.UseSqlServer(cadenaConexion)
            .LogTo(Console.WriteLine, LogLevel.Information));</contextocultivo>

2. Análisis del Plan de Ejecución

Ejecutando SET STATISTICS IO ON antes de la consulta se observa que hay 1245 lecturas lógicas y se está realizando un escaneo de tabla en lugar de una búsqueda por índice. ### 3. Monitoreo con Application Insights

Se utiliza TelemetryClient para medir el tiempo de ejecución de la consulta. ``` var telemetry = new TelemetryClient(); var stopwatch = System.Diagnostics.Stopwatch.StartNew();

var hogares = await contexto.Hogares .Include(h => h.Propietario) .Where(h => h.Tipo == TipoHogar.Normal) .OrderByDescending(h => h.FechaCreacion) .ToListAsync();

stopwatch.Stop(); telemetry.TrackMetric("DuracionConsultaHogares", stopwatch.ElapsedMilliseconds);


Técnicas de Optimización
------------------------

### 1. Carga Selectiva

En lugar de cargar toda la información del propietario, solo se seleccionan los campos necesarios. ```
var hogares = await contexto.Hogares
    .Select(h => new HogarDTO
    {
        Id = h.Id,
        Nombre = h.Nombre,
        NombrePropietario = h.Propietario.Nombre,
        FechaCreacion = h.FechaCreacion
    })
    .ToListAsync();

2. Paginación

Se implementa paginación para cargar solo una cantidad limitada de registros a la vez. ``` var tamanoPagina = 20; var numeroPagina = 1;

var hogares = await contexto.Hogares .OrderBy(h => h.FechaCreacion) .Skip((numeroPagina - 1) * tamanoPagina) .Take(tamanoPagina) .ToListAsync();


### 3. Índices Optimizados

Se configuran índices compuestos para mejorar el rendimiento de las consultas. ```
constructorModelo.Entity<hogar>()
    .HasIndex(h => new { h.Tipo, h.FechaCreacion })
    .IncludeProperties(h => new { h.Nombre, h.PropietarioId });</hogar>

4. Consultas Sin Rastreo

Para consultas de solo lectura, se utiliza AsNoTracking para evitar el seguimiento de cambios. ``` var hogar = await contexto.Hogares .AsNoTracking() .FirstAsync(h => h.Id == id);


### 5. Operaciones en Lote

Se realiza una inserción en lote en lugar de insertar cada elemento individualmente. ```
contexto.AddRange(elementos);
await contexto.SaveChangesAsync();

6. Consultas Compiladas

Se utilizan consultas compiladas para evitar la recompilación constante de las mismas consultas. ``` private static readonly Func> ObtenerHogarPorId = EF.CompileAsyncQuery((ContextoCultivo db, int id) => db.Hogares.FirstOrDefault(h => h.Id == id));

var hogar = await ObtenerHogarPorId(contexto, id);


Nuevas Características en EF Core 8
-----------------------------------

### 1. Actualizaciones y Eliminaciones en Lote

Se pueden realizar actualizaciones y eliminaciones directamente en la base de datos sin cargar los objetos en memoria. ```
await contexto.Hogares
    .Where(h => h.Nivel < 5)
    .ExecuteUpdateAsync(establecer => establecer.SetProperty(h => h.Nivel, 5));

2. Consultas en Columnas JSON

Se pueden consultar atributos dentro de columnas JSON. ``` var hogares = await contexto.Hogares .Where(h => h.MetadataJson.Contains(""VIP": true")) .ToListAsync();


Comparación de Rendimiento
--------------------------

| Métrica | Antes de la Optimización | Después de la Optimización | Aumento del Rendimiento |
|---|---|---|---|
| Carga de Lista de Hogares | 3200 ms | 450 ms | 7 veces |
| Inserción de 1000 Registros | 30 s | 1.2 s | 25 veces |
| Ocupación Máxima de CPU | 85% | 25% | Disminución significativa |
| Consumo de Memoria | 420 MB | 180 MB | 57% menos |

Etiquetas: efcore OptimizacionDeRendimiento CSharp SQLServer

Publicado el 10-4 15:09