QueryLink is a NuGet package designed to simplify the integration of UI components such as datagrids and datatables with backend IQueryable-based data sources. This library provides a seamless way to link these two parts of a system with minimal code, making it easier to manage filters and sorting operations.
- Filter Definitions: Define filters with various operators to refine your data queries.
- Order Definitions: Specify sorting criteria to order your data.
- Overrides: Customize filter and order operations with expression-based overrides.
- Query String Conversion: Easily convert filter and order definitions to and from query strings.
- IQueryable Extensions: Apply filter and order definitions directly to
IQueryablesources.
Install the latest stable package via NuGet:
dotnet add package ByteAether.QueryLinkUse the --version option to specify a preview version to install.
The Definitions class allows you to specify filters and orders for your data queries.
This example demonstrates how to create filter and order definitions using the Definitions class.
vardefinitions=newDefinitions{Filters=[new("Name",FilterOperator.Eq,"John"),new("Age",FilterOperator.Gt,30)],Orders=[new("Name",false),new("Age",true)]};The Overrides class allows you to customize filter and order operations using expression-based overrides.
This example shows how to create overrides for filter and order operations using the Overrides class.
varoverrides=newOverrides<Person>{Filter=[new(p =>p.Name, p =>p.FullName)],Order=[new(p =>p.Name, p =>p.FullName)]};Convert filter and order definitions to and from query strings using the HttpExtensions class.
This example demonstrates how to convert filter and order definitions to and from query strings using the HttpExtensions class.
stringqueryString=definitions.ToQueryString();DefinitionsparsedDefinitions=Definitions.FromQueryString(queryString);Apply filter and order definitions directly to IQueryable sources using the QueryableExtensions class.
This example shows how to apply filter and order definitions to an IQueryable source using the QueryableExtensions class.
IQueryable<Person>query=dbContext.People.AsQueryable();query=query.Apply(definitions,overrides);This example demonstrates filtering and sorting using the Definitions class and applying them to an IQueryable source.
vardefinitions=newDefinitions{Filters=[new("Name",FilterOperator.Eq,"John"),new("Age",FilterOperator.Gt,30)],Orders=[new("Name",false),new("Age",true)]};IQueryable<Person>query=dbContext.People.AsQueryable();query=query.Apply(definitions);This example shows how to use overrides to customize filter and order operations and apply them to an IQueryable source.
varoverrides=newOverrides<Person>{Filter=[new(p =>p.Name, p =>p.FullName)],Order=[new(p =>p.Name, p =>p.FullName)]};IQueryable<Person>query=dbContext.People.AsQueryable();query=query.Apply(definitions,overrides);This example demonstrates how to integrate QueryLink with MudBlazor DataGrid and EF Core. The LoadServerData method reads the state of the MudBlazor DataGrid, creates a QueryLink definition set out of it, and sends the definitions over an HTTP API using ToQueryString and FromQueryString. The PersonService class contains the overrides and applies the definitions to the IQueryable source. The PeopleController handles the API requests, reads the full query string from the request, and returns the filtered and sorted data. The produced query string is directly included in the URL, and the definitions are parsed from the full query string.
// Define your EF Core DbContext and entitypublicclassApplicationDbContext:DbContext{publicDbSet<Person>People{get;set;}}publicclassPerson{publicintId{get;set;}publicstringName{get;set;}publicintAge{get;set;}publicstringFullName=>$"{Name} Doe";}// In your service or controllerpublicclassPersonService{privatereadonlyApplicationDbContext_context;publicPersonService(ApplicationDbContextcontext){_context=context;}publicIQueryable<Person>GetPeople(Definitionsdefinitions){varoverrides=newOverrides<Person>{Filter=[new(p =>p.Name, p =>p.FullName)],Order=[new(p =>p.Name, p =>p.FullName)]};varquery=_context.People.AsQueryable();returnquery.Apply(definitions,overrides);}}// In your API controller[ApiController][Route("api/[controller]")]publicclassPeopleController:ControllerBase{privatereadonlyPersonService_personService;publicPeopleController(PersonServicepersonService){_personService=personService;}[HttpGet]publicIActionResultGetPeople(){varqueryString=Request.QueryString.ToString();vardefinitions=Definitions.FromQueryString(queryString);varpeople=_personService.GetPeople(definitions);returnOk(people);}}// In your Blazor component
@page "/people"
@inject HttpClientHttp<MudDataGrid
T="Person"Items="people"Hover="true"Sortable="true"Filterable="true"Striped="true"Pagination="true"ServerData="LoadServerData"><ToolBarContent><MudText typo="Typo.h6">People</MudText></ToolBarContent><Columns><Column T="Person" Field="@nameof(Person.Name)"Title="Name"Sortable="true"Filterable="true"/><Column T="Person" Field="@nameof(Person.Age)"Title="Age"Sortable="true"Filterable="true"/></Columns></MudDataGrid>@code{private IEnumerable<Person>people=newList<Person>();privateasyncTask<GridData<Person>>LoadServerData(GridState<Person>state){vardefinitions=newDefinitions{Filters=state.Filters.Select(f =>newFilterDefinition<object?>(f.Field,GetFilterOperator(f.Operator),f.Value)).ToList(),Orders=state.Sorts.Select(s =>newOrderDefinition(s.Field,s.Direction==SortDirection.Descending)).ToList()};varqueryString=definitions.ToQueryString();varresponse=awaitHttp.GetFromJsonAsync<List<Person>>($"api/people?{queryString}");vartotalItems=response.Count();varitems=response.Skip(state.Page*state.PageSize).Take(state.PageSize).ToList();returnnewGridData<Person>{Items=items,TotalItems=totalItems};}privateFilterOperatorGetFilterOperator(FilterOperatormudOperator){returnmudOperatorswitch{FilterOperator.Contains=>FilterOperator.Has,FilterOperator.Equals=>FilterOperator.Eq,FilterOperator.GreaterThan=>FilterOperator.Gt,FilterOperator.GreaterThanOrEqual=>FilterOperator.Gte,FilterOperator.LessThan=>FilterOperator.Lt,FilterOperator.LessThanOrEqual=>FilterOperator.Lte,FilterOperator.NotEqual=>FilterOperator.Neq,FilterOperator.StartsWith=>FilterOperator.Sw,FilterOperator.EndsWith=>FilterOperator.Ew,
_ =>thrownewArgumentOutOfRangeException(nameof(mudOperator),mudOperator,null)};}}The library provides a variety of filter operators to refine your data queries. Here is a list of all the available filter operators:
- Eq
=: Equals - Neq
!=: Not equals - Gt
>: Greater than - Gte
>=: Greater than or equal to - Lt
<: Less than - Lte
<=: Less than or equal to - Has
=*: Contains - Nhas
!*: Does not contain - In
[]: In a list - Nin
![]: Not in a list - Sw
^: Starts with - Nsw
!^: Does not start with - Ew
$: Ends with - New
!$: Does not end with
Pagination depends heavily on the underlying data persistence technology and requires specific implementations for each technology. It is easy to write your own pagination logic and apply it to IQueryable on top of what our library provides.
The full functionality of LINQ is still available. You are free to write any .Where() conditions and apply them to IQueryable. Our library does not block you from doing that.
You can use any library that can map objects from one to another or use your own mapper code. Our library does not limit you in any way and will work with the dataset you provide in the form of IQueryable<T>, whatever the T may be.
We welcome all contributions! You can:
- Open a Pull Request: Fork the repository, create a branch, make your changes, and submit a pull request to the
mainbranch. - Report Issues: Found a bug or have a suggestion? Open an issue with details.
Thank you for helping improve the project!
This project is licensed under the MIT License. See the LICENSE file for details.
QueryLink simplifies the integration of UI components with backend data sources, making it easier to manage filters and sorting operations with minimal code.

