INTRODUÇÃO · LINQeQuantic.LinqO eQuantic.Linq constrói a consulta como um objeto: filtros, ordenação e especificações que se combinam, viajam entre camadas e podem ser testados sem base de dados.
Visão geral
Responde ao problema que uma consulta dinâmica costuma criar — um controlador que conhece a forma dos dados — deixando a camada de apresentação entregar um filtro à camada de serviço, e não uma cláusula.Três implementações encaixam entre si: C# com geradores de código, TypeScript para o navegador e um sistema de conversão com tipos seguros entre DTOs e entidades.
Instalar
shell
dotnet add package eQuantic.Linq
O que faz
Geradores de códigoAs especificações, os filtros e os ordenadores são escritos na compilação, em .NET 6 e posteriores, pelo que nada se resolve por reflexão enquanto o pedido espera.
O padrão SpecificationO padrão completo, com AND e OR, para que uma regra de negócio se escreva uma vez e se combine depois, em vez de ser copiada para a consulta seguinte.
Filtro e ordenador de entidadesA filtragem e a ordenação viajam da camada de apresentação para a de serviço como valores, pelo que nenhuma camada tem de saber o que a outra consulta.
Conversão com os tipos intactosTransformação entre DTOs e entidades, configurada de forma fluente e verificada na compilação e não na execução.
Expressões em cacheAs expressões lambda ficam em cache, pelo que uma consulta executada num ciclo é compilada uma vez e não a cada volta.
Assíncrono de ponta a pontaAPIs assíncronas para o filtro e o ordenador de entidades, com token de cancelamento incluído.
O mesmo em TypeScriptUma implementação paralela para o navegador, com os genéricos e a verificação de tipos intactos, de modo que um filtro significa o mesmo dos dois lados.
Uma API de consultas fluenteIQuery, IValueQuery e IComparisonQuery, para construir uma consulta passo a passo e executá-la quando estiver terminada.
Geradores de códigoAs especificações, os filtros e os ordenadores são escritos na compilação, em .NET 6 e posteriores, pelo que nada se resolve por reflexão enquanto o pedido espera.
O padrão SpecificationO padrão completo, com AND e OR, para que uma regra de negócio se escreva uma vez e se combine depois, em vez de ser copiada para a consulta seguinte.
Filtro e ordenador de entidadesA filtragem e a ordenação viajam da camada de apresentação para a de serviço como valores, pelo que nenhuma camada tem de saber o que a outra consulta.
Conversão com os tipos intactosTransformação entre DTOs e entidades, configurada de forma fluente e verificada na compilação e não na execução.
Expressões em cacheAs expressões lambda ficam em cache, pelo que uma consulta executada num ciclo é compilada uma vez e não a cada volta.
Assíncrono de ponta a pontaAPIs assíncronas para o filtro e o ordenador de entidades, com token de cancelamento incluído.
O mesmo em TypeScriptUma implementação paralela para o navegador, com os genéricos e a verificação de tipos intactos, de modo que um filtro significa o mesmo dos dois lados.
Uma API de consultas fluenteIQuery, IValueQuery e IComparisonQuery, para construir uma consulta passo a passo e executá-la quando estiver terminada.
Início rápido
Instalar os dois pacotes — A biblioteca e, ao lado, o gerador. O gerador é uma dependência de compilação, pelo que não é distribuído com nada.
shell
dotnet add package eQuantic.Linqdotnet add package eQuantic.Linq.SourceGenerator
Marcar o modelo — Um atributo na classe e um por propriedade a dizer que operadores essa propriedade deve receber.
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 o que o gerador escreveu — As especificações e os filtros chegam como membros reais, pelo que se autocompletam e um erro de escrita é um erro de compilação.
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)); }}
Passar o filtro entre as camadas — O serviço recebe um filtro e nunca vê uma cadeia de consulta; o endpoint constrói o filtro e nunca vê a base de dados.
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));});
Converter entre modelos — Mapeie uma propriedade aninhada, mude o nome de outra e recuse compilar se algo ficou sem mapeamento.
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); }}
A mesma API no navegador — O lado TypeScript constrói os mesmos filtros e serializa-os para a cadeia que a API espera.
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 uma consulta passo a passo — A API fluente para os casos em que a consulta é montada em código: condições, ordem, paginação e uma agregação no fim.
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);
Superfície pública
NAMESPACE
SUPERFÍCIE PÚBLICA
GenerateSpecificationsAttribute
Marca uma classe para a qual o gerador deve escrever especificações.
SpecPropertyAttribute
Diz que operadores de filtro uma propriedade deve receber.
ISpecification<T>
O contrato de especificação, sobre uma expressão lambda.
IEntityFilter<T>
O filtro que uma camada entrega a outra.
IEntitySorter<T>
O mesmo, para a ordenação, com uma superfície fluente.
IAsyncEntityFilter<T>
O filtro assíncrono, com token de cancelamento.
Filtering<TData, TColumn>
Filtragem sobre uma coluna, propriedades aninhadas incluídas.
FilterOperator
Os operadores que um filtro pode usar: igual, contém, começa por, maior que e os restantes.
IExpressionCache
Onde uma expressão lambda compilada fica guardada entre chamadas.
Query<T>
O ponto de entrada para a API de consultas fluente.
Prefere ler código?
Todos os pacotes são MIT e públicos, e os exemplos são para copiar e colar.
Pronto para entregar algo de verdade?
Agende 30 minutos de conversa. Dizemos na mesma hora se somos o parceiro certo — ou quem é.
Pronto para entregar algo de verdade?
Agende 30 minutos de conversa. Dizemos na mesma hora se somos o parceiro certo — ou quem é.
© 2026 eQuantic Tech. Todos os direitos reservados.