EINFÜHRUNG · LINQeQuantic.LinqeQuantic.Linq baut die Abfrage als Objekt: Filter, Sortierung und Spezifikationen, die sich kombinieren, zwischen Schichten wandern und sich ohne Datenbank testen lassen.
Überblick
Sie löst das Problem, das eine dynamische Abfrage sonst schafft — einen Controller, der die Form der Daten kennt — indem die Präsentationsschicht der Dienstschicht einen Filter übergibt und keine Klausel.Drei Implementierungen greifen ineinander: C# mit Quellgeneratoren, TypeScript für den Browser und ein typsicheres Umwandlungssystem zwischen DTOs und Entitäten.
Installieren
shell
dotnet add package eQuantic.Linq
Was sie tut
QuellgeneratorenSpezifikationen, Filter und Sortierer entstehen beim Bauen, ab .NET 6, sodass nichts per Reflection ermittelt wird, während die Anfrage wartet.
Das Specification-MusterDas ganze Muster, mit AND und OR, sodass eine Geschäftsregel einmal geschrieben und danach kombiniert wird, statt in die nächste Abfrage kopiert.
Entitätsfilter und -sortiererFilterung und Sortierung wandern als Werte von der Präsentations- zur Dienstschicht, sodass keine Schicht wissen muss, was die andere abfragt.
Umwandlung mit erhaltenen TypenUmwandlung zwischen DTOs und Entitäten, flüssig konfiguriert und beim Bauen geprüft statt beim Ausführen.
Ausdrücke im CacheLambda-Ausdrücke werden zwischengespeichert, sodass eine Abfrage in einer Schleife einmal kompiliert wird und nicht bei jedem Durchlauf.
Durchgehend asynchronAsynchrone APIs für Entitätsfilter und -sortierer, CancellationToken inbegriffen.
Dasselbe in TypeScriptEine parallele Implementierung für den Browser, mit Generics und Typprüfung intakt, sodass ein Filter auf beiden Seiten dasselbe bedeutet.
Eine flüssige Abfrage-APIIQuery, IValueQuery und IComparisonQuery, um eine Abfrage Schritt für Schritt zu bauen und sie fertig auszuführen.
QuellgeneratorenSpezifikationen, Filter und Sortierer entstehen beim Bauen, ab .NET 6, sodass nichts per Reflection ermittelt wird, während die Anfrage wartet.
Das Specification-MusterDas ganze Muster, mit AND und OR, sodass eine Geschäftsregel einmal geschrieben und danach kombiniert wird, statt in die nächste Abfrage kopiert.
Entitätsfilter und -sortiererFilterung und Sortierung wandern als Werte von der Präsentations- zur Dienstschicht, sodass keine Schicht wissen muss, was die andere abfragt.
Umwandlung mit erhaltenen TypenUmwandlung zwischen DTOs und Entitäten, flüssig konfiguriert und beim Bauen geprüft statt beim Ausführen.
Ausdrücke im CacheLambda-Ausdrücke werden zwischengespeichert, sodass eine Abfrage in einer Schleife einmal kompiliert wird und nicht bei jedem Durchlauf.
Durchgehend asynchronAsynchrone APIs für Entitätsfilter und -sortierer, CancellationToken inbegriffen.
Dasselbe in TypeScriptEine parallele Implementierung für den Browser, mit Generics und Typprüfung intakt, sodass ein Filter auf beiden Seiten dasselbe bedeutet.
Eine flüssige Abfrage-APIIQuery, IValueQuery und IComparisonQuery, um eine Abfrage Schritt für Schritt zu bauen und sie fertig auszuführen.
Schnellstart
Beide Pakete installieren — Die Bibliothek und daneben den Generator. Der Generator ist eine Abhängigkeit für den Build und wird mit nichts ausgeliefert.
shell
dotnet add package eQuantic.Linqdotnet add package eQuantic.Linq.SourceGenerator
Das Modell markieren — Ein Attribut an der Klasse und eines pro Eigenschaft, das sagt, welche Operatoren sie bekommen soll.
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; }}
Nutzen, was der Generator geschrieben hat — Spezifikationen und Filter kommen als echte Mitglieder an, werden also vervollständigt, und ein Tippfehler ist ein Kompilierfehler.
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)); }}
Den Filter über die Schichten geben — Der Dienst nimmt einen Filter und sieht nie eine Abfragezeichenfolge; der Endpunkt baut den Filter und sieht nie die Datenbank.
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));});
Zwischen Modellen umwandeln — Bilden Sie eine verschachtelte Eigenschaft ab, benennen Sie eine um, und lassen Sie den Build scheitern, wenn etwas unabgebildet blieb.
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); }}
Dieselbe API im Browser — Die TypeScript-Seite baut dieselben Filter und serialisiert sie in die Zeichenfolge, die die API erwartet.
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
Eine Abfrage Schritt für Schritt bauen — Die flüssige API für die Fälle, in denen die Abfrage im Code entsteht: Bedingungen, Reihenfolge, Paginierung und am Ende eine Aggregation.
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);
Öffentliche Schnittstelle
NAMENSRAUM
ÖFFENTLICHE SCHNITTSTELLE
GenerateSpecificationsAttribute
Markiert eine Klasse, für die der Generator Spezifikationen schreiben soll.
SpecPropertyAttribute
Sagt, welche Filteroperatoren eine Eigenschaft bekommen soll.
ISpecification<T>
Der Spezifikationsvertrag, über einem Lambda-Ausdruck.
IEntityFilter<T>
Der Filter, den eine Schicht einer anderen übergibt.
IEntitySorter<T>
Dasselbe für die Sortierung, mit flüssiger Oberfläche.
IAsyncEntityFilter<T>
Der asynchrone Filter, mit CancellationToken.
Filtering<TData, TColumn>
Filterung über eine Spalte, verschachtelte Eigenschaften eingeschlossen.
FilterOperator
Die Operatoren, die ein Filter nutzen kann: gleich, enthält, beginnt mit, größer als und die übrigen.
IExpressionCache
Wo ein kompilierter Lambda-Ausdruck zwischen Aufrufen aufbewahrt wird.
Query<T>
Der Einstiegspunkt in die flüssige Abfrage-API.
Lesen Sie lieber Code?
Jedes Paket ist MIT-lizenziert und öffentlich, und die Beispiele sind zum Kopieren bereit.
Bereit, etwas Echtes zu liefern?
Buchen Sie ein 30-minütiges Erstgespräch. Innerhalb einer Stunde sagen wir Ihnen, ob wir der richtige Partner sind — oder wer es ist.
Bereit, etwas Echtes zu liefern?
Buchen Sie ein 30-minütiges Erstgespräch. Innerhalb einer Stunde sagen wir Ihnen, ob wir der richtige Partner sind — oder wer es ist.
eQuantic
Wir entwickeln Produkte, Bibliotheken und Software auf Unternehmensniveau, die weltweit skalieren. Ein Technologielabor mit Sitz in Portugal.
eQuantic
Wir entwickeln Produkte, Bibliotheken und Software auf Unternehmensniveau, die weltweit skalieren. Ein Technologielabor mit Sitz in Portugal.
eQuantic
Wir entwickeln Produkte, Bibliotheken und Software auf Unternehmensniveau, die weltweit skalieren. Ein Technologielabor mit Sitz in Portugal.
© 2026 eQuantic Tech. Alle Rechte vorbehalten.