← maurobernal.com.ar

Etiqueta: Vector Search

  • Novedades en .NET 11 Preview 2 (Marzo 2026): IA, Rendimiento y Blazor

    Microsoft lanzó en marzo de 2026 la segunda Preview de .NET 11, y la lista de novedades es suficientemente interesante como para que valga la pena revisarla ahora, sin esperar al release final de noviembre. Lo que se ve en esta preview marca la dirección del ecosistema: más IA integrada en el ORM, mejor observabilidad por defecto, y optimizaciones del runtime que van a impactar en aplicaciones de alta concurrencia. Acá te cuento lo más relevante.

    1. Entity Framework Core: IA y Funciones Avanzadas de SQL Server

    Índices Vectoriales DiskANN y VECTOR_SEARCH()

    Las versiones anteriores introdujeron soporte básico para embeddings vectoriales. .NET 11 da el siguiente paso: integración nativa con DiskANN, el algoritmo de búsqueda vectorial aproximada ultrarrápido de SQL Server y Azure SQL. Esto permite correr consultas de tipo RAG (Retrieval Augmented Generation) a escala masiva sin degradar el rendimiento transaccional de la base de datos. En proyectos donde combinamos búsqueda semántica con lógica de negocio tradicional, esto es un cambio significativo.

    // Configuración del índice vectorial DiskANN en el DbContext
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Articulo>()
            .HasIndex(a => a.Embedding)
            .HasMethod("diskann"); // Índice de alto rendimiento
    }
    
    // Consulta LINQ que usa la función nativa VECTOR_SEARCH
    ReadOnlyMemory<float> vectorUsuario = ObtenerVectorDelPrompt();
    
    var articulosSimilares = await context.Articulos
        .Where(a => EF.Functions.VectorSearch(a.Embedding, vectorUsuario))
        .Take(10)
        .ToListAsync();

    JSON_CONTAINS y Soporte Full-Text

    Trabajar con columnas JSON en bases de datos relacionales siempre fue un poco incómodo: o traías todo a memoria y deserializabas, o escribías SQL crudo. EF Core 11 expone EF.Functions.JsonContains() que se traduce directamente a la instrucción optimizada de SQL Server. Además, ya podés crear catálogos e índices Full-Text directamente desde las migraciones, sin tocar SQL a mano.

    // Busca dentro del documento JSON sin traerlo a memoria
    var usuariosConPreferencia = await context.Usuarios
        .Where(u => EF.Functions.JsonContains(u.Configuraciones, "{\"Tema\":\"Oscuro\"}"))
        .ToListAsync();

    Soporte nativo para MaxBy y MinBy

    Pequeño pero muy práctico. Históricamente, obtener la entidad completa con el valor máximo de una propiedad requería un .OrderByDescending().FirstOrDefault() que generaba una query más pesada. Ahora .MaxByAsync() y .MinByAsync() se traducen directamente a la consulta SQL más eficiente según el proveedor.

    // Obtiene la entidad completa del empleado con el salario más alto
    var empleadoTop = await context.Empleados
        .Where(e => e.DepartamentoId == 5)
        .MaxByAsync(e => e.Salario);

    2. ASP.NET Core y Blazor: Observabilidad y Manejo de Estado

    Trazabilidad Nativa de OpenTelemetry

    OpenTelemetry ya era compatible con versiones anteriores, pero la integración requería configuración manual. En .NET 11 Preview 2, el rastreo de métricas, logs y trazas distribuidas viene activado desde los templates base. Esto es especialmente valioso en arquitecturas orquestadas con .NET Aspire: el viaje completo de una petición entre microservicios queda trazado sin escribir una línea extra de configuración.

    TempData en Blazor

    En MVC clásico, TempData era la forma estándar de pasar mensajes efímeros («Guardado exitosamente») que sobrevivían a una redirección HTTP. Blazor no tenía un equivalente nativo sencillo, lo que obligaba a usar workarounds con servicios de estado o query params. .NET 11 lo resuelve de forma limpia.

    @page "/formulario"
    @inject NavigationManager Nav
    
    <button class="btn" @onclick="Guardar">Guardar Perfil</button>
    
    @code {
        [TempData]
        public string MensajeExito { get; set; }
    
        private void Guardar()
        {
            // Lógica de guardado...
            MensajeExito = "¡El perfil se ha actualizado correctamente!";
            Nav.NavigateTo("/perfil"); // El mensaje sobrevive a esta redirección
        }
    }

    Simple, declarativo, y consistente con lo que ya conocían los desarrolladores que venían de MVC.

    Soporte para OpenAPI 3.2.0

    Las APIs creadas con .NET 11 soportan de fábrica el estándar OpenAPI 3.2.0 mediante el paquete interno Microsoft.AspNetCore.OpenApi. Documentación automática de contratos REST alineada con las mejores prácticas modernas, sin dependencias externas.

    3. Mejoras en el Runtime y Bibliotecas Base

    System.Text.Json Genérico y Optimizaciones AOT

    El soporte de JSON para compilación nativa (Native AOT) sigue madurando. La novedad es un selector genérico para metadatos (GetTypeInfo<T>) que permite a los desarrolladores de librerías navegar árboles de serialización de forma ultrarrápida, sin las penalizaciones clásicas del boxing ni la reflexión lenta. Si publicás paquetes NuGet, esto te va a interesar.

    Optimizaciones del JIT y Runtime Async V2

    Esta es la mejora que más me entusiasma a nivel de plataforma. El equipo de .NET está evolucionando cómo se compila el modelo async/await bajo el capó (la state machine que genera el compilador). El nuevo modelo Async V2 reduce significativamente las asignaciones de memoria en operaciones asíncronas, lo que se traduce en menos presión sobre el Garbage Collector en aplicaciones web de alta concurrencia. Combinado con el nuevo Cached Interface Dispatch (que mejora la velocidad del código genérico), el runtime de .NET 11 promete ser notablemente más eficiente.

    En producción, en APIs que manejan miles de requests por segundo, estas optimizaciones no son menores.

    4. Contenedores, F#, MAUI y más

    • Imágenes Docker más livianas: Las imágenes SDK de .NET para Linux y macOS se redujeron un 17%. Menos tiempo de descarga en CI/CD, pipelines más rápidos.
    • F# modernizado: La directiva #elif para compilación condicional, caché de resolución de sobrecargas y simplificaciones en la herencia de interfaces con Default Interface Methods (DIM). La comunidad de F# lo venía pidiendo hace tiempo.
    • .NET MAUI: Optimizaciones en TypedBindings y marcadores de inmutabilidad para elementos como Font y Color. El efecto práctico es que los motores de interfaz evitan re-renderizar pantallas cuando los valores no cambiaron, mejorando la fluidez visual.

    Conclusión

    .NET 11 Preview 2 no es solo una preview de mantenimiento: marca claramente las apuestas de Microsoft para el ciclo siguiente. IA integrada a nivel de ORM, observabilidad como ciudadano de primera clase, y un runtime async más eficiente son señales de hacia dónde va el ecosistema.

    No recomendaría usar una preview en producción, pero sí vale la pena explorarla en proyectos de laboratorio ya. Varias de estas features van a cambiar hábitos que tenemos muy arraigados —especialmente MaxByAsync, JsonContains y el soporte nativo de OpenTelemetry.

    ¿Alguna de estas novedades te genera dudas o querés ver un caso de uso más concreto? Dejalo en los comentarios.

  • Novedades en Entity Framework Core: Todo lo que cambió de .NET 8 a .NET 10

    Si hay algo que me gusta de trabajar con .NET es que el ecosistema no se detiene. Hace unos años, cuando arranqué a usar Entity Framework, la discusión era «¿EF o Dapper?». Hoy esa discusión todavía existe, pero EF Core evolucionó tanto que la balanza se inclinó bastante. En este artículo repaso todas las novedades que llegaron desde .NET 8 hasta .NET 10, con ejemplos concretos y el contexto necesario para entender por qué cada feature importa.

    Novedades en EF Core 8 (.NET 8)

    La versión 8 fue la que me hizo replantear varios patrones que venía usando desde hace tiempo. Especialmente los Tipos Complejos, que resolvieron algo que siempre me molestó de las Owned Entities.

    1. Tipos Complejos (Complex Types)

    Cuando modelamos Value Objects en DDD, la lógica indica que un objeto como Dirección no tiene identidad propia: se define por sus atributos. Antes de EF8, si querías mapear esto usabas Owned Entities, que internamente requerían una clave primaria oculta y te generaban joins innecesarios. Con los Complex Types en EF Core 8, las propiedades del objeto se mapean directamente como columnas en la tabla del padre. Sin clave, sin tabla aparte, sin overhead.

    [ComplexType]
    public class Direccion
    {
        public string Calle { get; set; }
        public string Ciudad { get; set; }
    }
    
    public class Cliente
    {
        public int Id { get; set; }
        public string Nombre { get; set; }
        
        // Se mapeará como columnas Calle y Ciudad en la tabla Clientes
        public required Direccion DireccionResidencia { get; set; } 
    }

    En producción esto se traduce en columnas como DireccionResidencia_Calle y DireccionResidencia_Ciudad directamente en la tabla Clientes. Mucho más limpio y sin el overhead de las relaciones.

    2. Colecciones de Tipos Primitivos

    Otro punto de dolor histórico: guardar una lista de strings o enteros. La solución clásica era crear una tabla auxiliar o serializar a mano. EF Core 8 lo resuelve nativamente: en SQL Server serializa a JSON automáticamente, en PostgreSQL usa tipos de array nativos.

    public class Producto
    {
        public int Id { get; set; }
        public string Nombre { get; set; }
        public List<string> Etiquetas { get; set; } = new();
    }
    
    // EF Core traduce esto a funciones JSON en SQL Server
    var productos = context.Productos
        .Where(p => p.Etiquetas.Contains("Urgente"))
        .ToList();

    La primera vez que vi esto funcionar en un query real me sorprendió. EF genera el JSON_VALUE correspondiente en SQL Server y la consulta funciona sin traer entidades a memoria.

    3. Consultas SQL Nativas para Tipos No Mapeados (SqlQuery)

    Cuando necesitás ejecutar SQL crudo que retorna un escalar o un DTO que no está en tu DbContext, antes tenías que recurrir a ADO.NET o trucos con FromSqlRaw. Con SqlQuery<T> ya no.

    var salarios = await context.Database
        .SqlQuery<decimal>($"SELECT AVG(Salario) AS Value FROM Empleados")
        .ToListAsync();

    Lo que más me gusta es que podés componer LINQ encima de este resultado, combinando la potencia del SQL manual con las ventajas del query pipeline de EF.

    Novedades en EF Core 9 (.NET 9)

    Con .NET 9, el foco se desplazó hacia rendimiento extremo e inteligencia artificial. Dos temas que, honestamente, no esperaba ver tan rápido en el ORM.

    4. Búsqueda Vectorial (Vector Search)

    Si estás construyendo aplicaciones con IA generativa, tarde o temprano necesitás almacenar y consultar embeddings. EF Core 9 introduce soporte de primera clase para esto en SQL Server, PostgreSQL (con pgvector) y Cosmos DB. Esto es clave para patrones como RAG (Retrieval Augmented Generation).

    public class Documento
    {
        public int Id { get; set; }
        public string Contenido { get; set; }
        public ReadOnlyMemory<float> Embedding { get; set; }
    }
    
    ReadOnlyMemory<float> vectorBusqueda = ObtenerVector("¿cómo configuro autenticación?");
    
    var docs = await context.Documentos
        .OrderBy(d => EF.Functions.VectorDistance(d.Embedding, vectorBusqueda)) 
        .Take(5)
        .ToListAsync();

    En un proyecto donde implementé un chatbot sobre documentación interna, esto redujo el tiempo de integración a la mitad. Antes había que saltar a SDKs separados; ahora EF Core maneja el acceso al store vectorial junto con el resto de la lógica de datos.

    5. Optimizaciones de Rendimiento y Native AOT

    EF Core 9 habilita compatibilidad con compilación Native AOT, lo que significa arranques de aplicación drásticamente más rápidos y menor consumo de memoria. En microservicios y funciones serverless esto no es un detalle: es la diferencia entre ser viable o no. El modelo compilado pre-genera el mapeo en tiempo de build, eliminando la reflexión en runtime.

    6. Mejoras en Actualizaciones y Borrados Masivos

    ExecuteUpdate y ExecuteDelete se introdujeron en EF7, pero en EF9 se potenciaron para soportar actualizaciones de Complex Types y traducir expresiones LINQ más avanzadas. Lo importante: siguen sin traer entidades a memoria.

    await context.Productos
        .Where(p => p.Categoria == "Herramientas")
        .ExecuteUpdateAsync(s => s.SetProperty(p => p.Precio, p => p.Precio * 1.10m));

    Una sola query SQL de UPDATE. Sin SaveChanges, sin tracking, sin roundtrips innecesarios.

    Novedades en EF Core 10 (.NET 10)

    .NET 10 se lanzó en noviembre de 2025 y la versión de EF Core que lo acompaña terminó de pulir varias aristas que la comunidad venía pidiendo. Algunas son pequeñas pero hacen una diferencia real en el día a día.

    7. Operadores Explícitos LeftJoin y RightJoin

    ¿Cuántas veces escribiste un GroupJoin + SelectMany + DefaultIfEmpty solo para hacer un LEFT JOIN? Demasiadas. EF Core 10 lo resuelve con operadores directos.

    var clientesConYsinPedidos = await context.Clientes
        .LeftJoin(
            context.Pedidos,
            c => c.Id,
            p => p.ClienteId,
            (c, p) => new { c.Nombre, PedidoId = p?.Id }
        ).ToListAsync();

    Mucho más legible. La intención queda clara en el código sin tener que descifrar la cascada de lambdas.

    8. Flexibilidad en ExecuteUpdateAsync (Lambdas)

    Las actualizaciones masivas ahora permiten lambdas regulares en lugar de árboles de expresión estrictos. Esto permite agrupar setters en bloques más limpios, especialmente cuando actualizás múltiples propiedades a la vez.

    await context.Productos.Where(p => p.Stock < 10)
        .ExecuteUpdateAsync(s => {
            s.SetProperty(p => p.RequiereReabastecimiento, true);
            s.SetProperty(p => p.FechaAviso, DateTime.UtcNow);
        });

    9. Columnas JSON Avanzadas para Tipos Complejos

    Combinando lo mejor de dos mundos: los Complex Types de EF8 ahora pueden mapearse directamente como documentos JSON en una sola columna, con capacidad de consulta LINQ nativa dentro de ese JSON.

    modelBuilder.Entity<Usuario>()
        .ComplexProperty(u => u.Preferencias, p => p.ToJson());
    
    // Consulta dentro del JSON sin traer todo a memoria
    var usuarios = await context.Usuarios
        .Where(u => u.Preferencias.Tema == "Dark")
        .ToListAsync();

    10. Filtros de Consulta Nombrados (Named Query Filters)

    Los global query filters (soft delete, multitenancy) son muy útiles, pero el problema siempre fue que para ignorar uno tenías que ignorarlos todos. EF Core 10 le pone nombre a cada filtro, permitiéndote desactivar solo el que necesitás.

    modelBuilder.Entity<Blog>().HasQueryFilter("FiltroBorrados", b => !b.EstaBorrado);
    
    // Solo ignoro el filtro de borrados, el de tenant sigue activo
    var blogs = await context.Blogs
        .IgnoreQueryFilter("FiltroBorrados")
        .ToListAsync();

    En aplicaciones multitenant con soft delete, esto es oro puro. Ya no hay que elegir entre ver los registros borrados o perder el filtro de tenant.

    11. Búsqueda Híbrida y Full-Text (Cosmos DB)

    Para los que trabajan con Cosmos DB y motores de búsqueda basados en IA, EF Core 10 introduce soporte nativo para Full-Text Search y Hybrid Search. Esta última combina similitud vectorial con búsqueda de palabras clave usando Reciprocal Rank Fusion, obteniendo lo mejor de ambos enfoques en una sola query.

    var resultados = await context.Documentos
        .OrderBy(x => EF.Functions.VectorDistance(x.Embedding, vectorUsuario)) 
        .ThenBy(x => EF.Functions.FullTextRank(x.Contenido, "término exacto")) 
        .Take(5)
        .ToListAsync();

    Resumen: La Evolución de EF Core en Tres Versiones

    Mirando el camino recorrido de .NET 8 a .NET 10, la evolución es clara:

    • .NET 8: Mejoras en modelado DDD (Complex Types), soporte nativo de colecciones primitivas, SQL más flexible.
    • .NET 9: Rendimiento extremo con Native AOT, primer soporte real para IA con embeddings vectoriales.
    • .NET 10: Ergonomía del desarrollador: sintaxis más limpia, JSON avanzado, filtros nombrados, búsqueda híbrida.

    Si tu proyecto aún corre en EF Core 6 o 7, te recomiendo migrar. No solo por las features nuevas, sino porque el rendimiento en operaciones masivas mejoró notablemente. Personalmente, lo que más uso en el día a día son los ExecuteUpdate/ExecuteDelete para evitar cargar entidades innecesariamente, y los Complex Types para modelar Value Objects sin el overhead de las Owned Entities.

    ¿Tenés dudas sobre alguna feature específica o querés ver un caso de uso más complejo? Dejalo en los comentarios.

Tags

tsql (27)mssql (26)devops (21)sql (20)dotnet (18)docker (16)performance (14)contenedores (11)dotnet10 (10)linux (9)csharp (8)microservicios (8)angular (8)angular21 (7)sql server (6)issabel (6)kubernetes (6)docker-compose (6)typescript (6)mysql (5)