INTRODUCCIÓN · LINQeQuantic.LinqeQuantic.Linq construye la consulta como un objeto: filtros, ordenación y especificaciones que se combinan, viajan entre capas y se pueden probar sin base de datos.
Resumen
Responde al problema que suele crear una consulta dinámica — un controlador que conoce la forma de los datos — dejando que la capa de presentación entregue un filtro a la capa de servicio, y no una cláusula.Tres implementaciones encajan entre sí: C# con generadores de código, TypeScript para el navegador y un sistema de conversión con tipos seguros entre DTOs y entidades.
Instalar
shell
dotnet add package eQuantic.Linq
Qué hace
Generadores de códigoLas especificaciones, los filtros y los ordenadores se escriben al compilar, en .NET 6 y posteriores, así que nada se resuelve por reflexión mientras la petición espera.
El patrón SpecificationEl patrón completo, con AND y OR, para que una regla de negocio se escriba una vez y se combine después, en lugar de copiarse en la consulta siguiente.
Filtro y ordenador de entidadesEl filtrado y la ordenación viajan de la capa de presentación a la de servicio como valores, así que ninguna capa tiene que saber qué consulta la otra.
Conversión con los tipos intactosTransformación entre DTOs y entidades, configurada de forma fluida y comprobada al compilar y no al ejecutar.
Expresiones en cachéLas expresiones lambda se guardan en caché, así que una consulta que se ejecuta en un bucle se compila una vez y no en cada vuelta.
Asíncrono de principio a finAPIs asíncronas para el filtro y el ordenador de entidades, con token de cancelación incluido.
Lo mismo en TypeScriptUna implementación paralela para el navegador, con los genéricos y la comprobación de tipos intactos, de modo que un filtro significa lo mismo a los dos lados.
Una API de consultas fluidaIQuery, IValueQuery e IComparisonQuery, para construir una consulta paso a paso y ejecutarla cuando esté terminada.
Generadores de códigoLas especificaciones, los filtros y los ordenadores se escriben al compilar, en .NET 6 y posteriores, así que nada se resuelve por reflexión mientras la petición espera.
El patrón SpecificationEl patrón completo, con AND y OR, para que una regla de negocio se escriba una vez y se combine después, en lugar de copiarse en la consulta siguiente.
Filtro y ordenador de entidadesEl filtrado y la ordenación viajan de la capa de presentación a la de servicio como valores, así que ninguna capa tiene que saber qué consulta la otra.
Conversión con los tipos intactosTransformación entre DTOs y entidades, configurada de forma fluida y comprobada al compilar y no al ejecutar.
Expresiones en cachéLas expresiones lambda se guardan en caché, así que una consulta que se ejecuta en un bucle se compila una vez y no en cada vuelta.
Asíncrono de principio a finAPIs asíncronas para el filtro y el ordenador de entidades, con token de cancelación incluido.
Lo mismo en TypeScriptUna implementación paralela para el navegador, con los genéricos y la comprobación de tipos intactos, de modo que un filtro significa lo mismo a los dos lados.
Una API de consultas fluidaIQuery, IValueQuery e IComparisonQuery, para construir una consulta paso a paso y ejecutarla cuando esté terminada.
Inicio rápido
Instalar los dos paquetes — La biblioteca y, al lado, el generador. El generador es una dependencia de compilación, así que no se distribuye con nada.
shell
dotnet add package eQuantic.Linqdotnet add package eQuantic.Linq.SourceGenerator
Marcar el modelo — Un atributo en la clase y uno por propiedad que dice qué operadores debe recibir esa propiedad.
C#
builder.Services.AddLinqServices(); [GenerateSpecifications]public class Product{ public int Id { get; set; } [SpecProperty(FilterOperators = FilterOperator.Equals | FilterOperator.Contains)] public string Name { get; set; } = string.Empty; [SpecProperty(FilterOperators = FilterOperator.GreaterThan | FilterOperator.LessThan)] public decimal Price { get; set; } public bool IsActive { get; set; }}
Usar lo que escribió el generador — Las especificaciones y los filtros llegan como miembros reales, así que se autocompletan y una errata es un error de compilación.
C#
public partial class Product{ public static class Specifications { public static ISpecification<Product> NameEquals(string value) => new DirectSpecification<Product>(p => p.Name == value); public static ISpecification<Product> NameContains(string value) => new DirectSpecification<Product>(p => p.Name.Contains(value)); public static ISpecification<Product> PriceGreaterThan(decimal value) => new DirectSpecification<Product>(p => p.Price > value); } public static class Filters { public static IEntityFilter<Product> ByName(string name) => EntityFilter<Product>.Where(NameEquals(name)); public static IEntityFilter<Product> ByPriceRange(decimal min, decimal max) => EntityFilter<Product>.Where(PriceGreaterThan(min).And(p => p.Price <= max)); }}
Pasar el filtro entre las capas — El servicio recibe un filtro y nunca ve una cadena de consulta; el endpoint construye el filtro y nunca ve la base de datos.
C#
public class ProductService{ private readonly IRepository<Product> _repository; public async Task<IEnumerable<Product>> GetProductsAsync(IEntityFilter<Product> filter) { return await _repository.GetAsync(filter.Specification); }} app.MapGet("/products", async ( [AsParameters] ProductSearchRequest request, ProductService products) =>{ var filter = EntityFilter<Product>.Empty(); if (!string.IsNullOrEmpty(request.Name)) filter = filter.And(Product.Specifications.NameContains(request.Name)); if (request.MinPrice.HasValue) filter = filter.And(Product.Specifications.PriceGreaterThan(request.MinPrice.Value)); return Results.Ok(await products.GetProductsAsync(filter));});
Convertir entre modelos — Asigne una propiedad anidada, cambie el nombre de otra y niéguese a compilar si algo quedó sin asignar.
C#
public class ProductDto{ public string Name { get; set; } = string.Empty; public string Category { get; set; } = string.Empty; public bool IsAvailable { get; set; }} var products = await _repository.GetAsync<Product>() .Cast<ProductDto>(options => options .Map(src => src.Name, dest => dest.Name) .Map(src => src.Category.Name, dest => dest.Category) .Map(src => src.IsActive, dest => dest.IsAvailable) .ExcludeUnmapped()); public class ProductCastConfiguration : IFilteringCastConfiguration<ProductDto>{ public void Configure(FilteringCastOptions<ProductDto> options) { options.Map(src => src.Name, dest => dest.Name) .Map(src => src.IsActive, dest => dest.IsAvailable); }}
La misma API en el navegador — El lado TypeScript construye los mismos filtros y los serializa a la cadena que espera la API.
TypeScript
import { Filtering, FilteringCollection, Sorting, SortingCollection } from 'equantic-linq'; interface User { id: number; name: string; email: string; age: number; isActive: boolean;} const filters = new FilteringCollection<User>([ Filtering.ct<User, 'name'>('name', 'John'), Filtering.gte<User, 'age'>('age', 25), Filtering.eq<User, 'isActive'>('isActive', true),]); const sorts = new SortingCollection<User>([ Sorting.desc<User>('age'), Sorting.asc<User>('name'),]); filters.toString(); // name:ct(John),age:gte(25),isActive:eq(true)sorts.toString(); // age:desc,name:asc
Construir una consulta paso a paso — La API fluida para los casos en que la consulta se monta en código: condiciones, orden, paginación y una agregación al final.
C#
var query = Query.For<Product>() .Where(p => p.IsActive) .OrderBy(p => p.Name) .Skip(10) .Take(20); var totalPrice = await ValueQuery.For<Product>() .Where(p => p.IsActive) .Sum(p => p.Price) .ExecuteAsync(); var hasExpensiveProducts = await ComparisonQuery.For<Product>() .Any(p => p.Price > 1000) .ExecuteAsync(); var complexQuery = Query.For<Product>() .Where(p => p.IsActive) .And(p => p.Price > 100) .Or(p => p.Category == "Premium") .OrderBy(p => p.Price) .ThenBy(p => p.Name);
Superficie pública
NAMESPACE
SUPERFICIE PÚBLICA
GenerateSpecificationsAttribute
Marca una clase para la que el generador debe escribir especificaciones.
SpecPropertyAttribute
Dice qué operadores de filtro debe recibir una propiedad.
ISpecification<T>
El contrato de especificación, sobre una expresión lambda.
IEntityFilter<T>
El filtro que una capa entrega a otra.
IEntitySorter<T>
El mismo, para la ordenación, con una superficie fluida.
IAsyncEntityFilter<T>
El filtro asíncrono, con token de cancelación.
Filtering<TData, TColumn>
Filtrado sobre una columna, propiedades anidadas incluidas.
FilterOperator
Los operadores que puede usar un filtro: igual, contiene, empieza por, mayor que y los demás.
IExpressionCache
Donde se guarda una expresión lambda compilada entre llamadas.
Query<T>
El punto de entrada a la API de consultas fluida.
¿Prefieres leer código?
Todos los paquetes son MIT y públicos, y los ejemplos son para copiar y pegar.
¿Listo para entregar algo real?
Agenda 30 minutos de conversación. Te decimos en la misma hora si somos el socio adecuado — o quién lo es.
¿Listo para entregar algo real?
Agenda 30 minutos de conversación. Te decimos en la misma hora si somos el socio adecuado — o quién lo es.
© 2026 eQuantic Tech. Todos los derechos reservados.