efcore-queries
is a lightweight, powerful, and flexible extension for Entity Framework Core (EF Core), designed to simplify query composition and execution. It enables the construction of dynamic queries in a more readable and maintainable way, making it easier to interact with databases. This project is specifically designed to receive HTTP requests in JSON format and parse those queries to perform simple database queries.
- Features
- Installation
- Configuration
- Initialization
- Usage
- Request Structure
- Response Structure
- Example Request
- Contributing
- License
- Dynamic Query Building: Easily build complex queries from JSON payloads in HTTP requests.
- Filter & Sort Operations: Supports dynamic filtering and sorting based on input in the JSON request.
- Predicate Handling: Allows for flexible predicate expressions to filter or project data.
- Extensive LINQ Support: Seamless integration with LINQ to leverage all its features.
- Async Support: Execute queries asynchronously with ease.
To install the package, run the following command in your project:
dotnet add package efcore-queries
To configure the behavior of the query parser, add the following section to your appsettings.json
:
"Query": {
"Limits": {
"FilterCompositionDepth": 5,
"FilterConditionDepth": 4,
"SortLimit": 4,
"PageSizeLimit": 100
},
"DefaultPageSize": 10
}
FilterCompositionDepth
: Maximum depth for nested filters.FilterConditionDepth
: Maximum depth for filter conditions.SortLimit
: Maximum number of sorting operations allowed.PageSizeLimit
: Maximum page size for queries.DefaultPageSize
: Default page size if not specified in the request.
To initialize efcore-queries
, update your Program.cs
as follows:
public class Program
{
public static void Main(string[] args)
{
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Initialize efcore-queries
builder.AddEFCoreQueries();
}
}
This setup ensures the library is ready to parse and handle incoming JSON queries.
This example demonstrates how to use QuerySpecification
to map a Role
entity to a RoleDto
:
public class RoleMappingExtension
{
public static readonly QuerySpecification<Role, RoleDto> builder = new QuerySpecificationBuilder<Role, RoleDto>()
.Register(e => e.RoleId, d => d.Id)
.Register(e => e.Name, d => d.Name)
.Register(e => e.Permissions, RolePermissionMappingExtension.Expression, d => d.Permissions)
.Build();
public static Expression<Func<Role, RoleDto>> Expression => builder.ConstructorExperssion;
}
This example shows how to integrate the RoleMappingExtension
with a query service in a service layer:
public class RoleService
{
private readonly QueryBuilder<Role, RoleDto> queryBuilder;
public RoleService(QueryService queryService)
{
queryBuilder = queryService.For(RoleMappingExtension.builder);
}
public async Task<QueryResponse<RoleDto>> QueryRoleList(QueryState query, CancellationToken cancel)
{
return await db.Roles.Query(queryBuilder, query, cancel);
}
}
The following example demonstrates how to use the RoleService
in a controller to handle HTTP requests:
public class RoleController
{
[HttpGet("search")]
public Task<QueryResponse<RoleDto>> GetRolesSearch([FromBody] QueryState state, CancellationToken cancel)
{
return service.QueryRoleList(state, cancel);
}
}
If the TotalCount
is included in the request, the response will remain unchanged.
However, if you wish to get the updated TotalCount
, simply omit the field in the request, and the response will include a recalculated count.
This approach is particularly useful for multiple search requests, as it eliminates the need to recalculate the TotalCount
with each request.
{
"filter": [ "<filter>" ],
"sorts": [
{
"key": "<string>",
"direction": "<direction>"
}
],
"pagination": {
"pageIndex": "<integer>",
"pageSize": "<integer>",
"totalCount": "<integer>" // !!! read the above comment !!!
}
}
{
"filterType": "condition",
"key": "<string>",
"value": "<string>|<date>|<float>|<boolean>|<null>",
"operator": "<operator>", // see below (Condition Operators)
"options": "<option>" // see below (Condition Operators Options)
}
{
"filterType": "composition",
"operator": "<operator>", // see below (Composition Operators)
"conditions: [ "<filter>" ] // see above (Filter)
}
- Equals
- NotEquals
- GreaterThan
- GreaterThanOrEqual
- LessThan
- LessThanOrEqual
- Contains
- NotContains
- StartsWith
- EndsWith
- Includes
- NotIncludes
- None
- IgnoreCase
- And
- Or
- Descending
- Ascending
The TotalCount
will only be calculated if it has not already been provided in the request.
If the request includes a TotalCount
, the response will return the same value without recalculating it.
{
"results": [
"<your json parsed DTO>"
]
"state": {
{
"filter": "<filter>",
"sorts": [
{
"key": "<string>",
"direction": "<direction>"
}
],
"pagination": {
"pageIndex": "<integer>",
"pageSize": "<integer>",
"totalCount": "<integer>"
}
}
}
}
{
"filter": {
"filterType": "composition",
"operator": "and",
"": [
{
"filterType": "condition",
"key": "Name",
"value": "ngloader",
"operator": "contains",
"options": "ignorecase"
},
{
"filterType": "condition",
"key": "Age",
"value": "24",
"operator": "equals"
}
]
},
"sorts": [
{
"key": "Name",
"direction": "descending"
}
],
"pagination": {
"pageIndex": 0,
"pageSize": 5
}
}
We welcome contributions to enhance this project! If you have ideas or fixes, feel free to:
- Fork the repository.
- Create a new branch.
- Submit a pull request with detailed explanations of your changes.
This project is licensed under the GNU General Public License.