Q.CTCore
1.7.0
dotnet add package Q.CTCore --version 1.7.0
NuGet\Install-Package Q.CTCore -Version 1.7.0
<PackageReference Include="Q.CTCore" Version="1.7.0" />
<PackageVersion Include="Q.CTCore" Version="1.7.0" />
<PackageReference Include="Q.CTCore" />
paket add Q.CTCore --version 1.7.0
#r "nuget: Q.CTCore, 1.7.0"
#:package Q.CTCore@1.7.0
#addin nuget:?package=Q.CTCore&version=1.7.0
#tool nuget:?package=Q.CTCore&version=1.7.0
CTCore.DynamicQuery - Kiến trúc tổng quan
Package:
Q.CTCorev1.7.0 Target:.NET 9.0/.NET 10.0
Mục đích: Dynamic query provider cho EF Core – hỗ trợ query linh hoạt, dynamic projection, OData auto-endpoints
1. Sơ đồ kiến trúc
graph TB
subgraph "API Layer"
REQ["BaseAPIRequest / BaseAPIPageRequest"]
BIND["BindAsync (MinimalAPI)"]
end
subgraph "Core Layer"
QRV3["QueryRequestV3"]
QPRV3["QueryPageRequestV3"]
CD["ConditionDescriptor"]
SQI["SQueryItem (key, val)"]
end
subgraph "Query Engine"
HLQ["HandleLinqQueryRequestV2"]
HLI["HandlerLinqQueryItem"]
BQC["BuildQueryWithCondition"]
BLQ["BuildLikeQuery"]
BSW["BuildStartWithQuery"]
BEW["BuildEndWithQuery"]
HLP["HandleLinqQueryPageRequestV2"]
end
subgraph "Mediator Layer (CQRS)"
BQ["CTBaseQuery"]
PQH["PageQueryHandler"]
OQH["ObjectQueryHandler"]
IQH["IQueryHandler / ICommandHandler"]
end
subgraph "Populate Module"
PB["ProjectionBuilder"]
EB["ExpressionBuilder"]
PA["PopulateAnalyzer"]
TM["TypeMapper"]
PM["PropertyMapper"]
PD["ProjectDynamic Extension"]
end
subgraph "OData Module"
OC["ODataContainerCollection"]
AH["ApiHandler<T>"]
SC["ServicesCollectionExtensions"]
ES["EntitySetsController"]
end
subgraph "Response Layer"
BR["CTBaseResult<T>"]
OK["OkResponse / OkPageResponse"]
OKDYN["OkDynamicResponse / OkDynamicPageResponse"]
ER["ErrorResponse"]
end
REQ --> BIND --> QRV3
QRV3 --> CD --> SQI
QRV3 --> HLQ
QPRV3 --> HLQ
HLQ --> HLI
HLI --> BQC & BLQ & BSW & BEW
QPRV3 --> HLP
BQ --> PQH & OQH
PQH --> HLQ & HLP & PD
OQH --> HLQ & PD
PD --> PB --> EB & PA & TM & PM
AH --> PQH & OQH
SC --> AH & OC
PQH & OQH --> BR --> OK & OKDYN & ER
2. Cấu trúc thư mục
CTCore.DynamicQuery/
├── Common/
│ ├── Definations/
│ │ └── ErrorCodes.cs # Enum mã lỗi (BadRequest, NotFound, Server...)
│ ├── Exceptions/
│ │ └── NotFoundException.cs # Exception cho entity không tìm thấy
│ └── Types/
│ └── ConditionDescriptor.cs # SQueryItem record + ConditionDescriptor wrapper
│
├── Core/
│ ├── APIRequest.cs # BaseAPIRequest, BaseAPIPageRequest (BindAsync)
│ ├── APIResponse.cs # OkResponse, OkPageResponse, OkDynamic*, ErrorResponse
│ ├── Domain/
│ │ ├── CTBaseEntity.cs # Base entity với Id (Guid v7)
│ │ └── Interfaces/
│ │ ├── IRepository.cs # Add, Remove, GetById, BuildQuery
│ │ └── IUnitOfWork.cs # SaveChange, Transaction (hỗ trợ MongoDB)
│ ├── Extension/
│ │ ├── LinqUtilsV2.HandleQuery.cs # HandleLinqQueryRequestV2, ApplyQueryConditions
│ │ └── LinqUtilsV2.BuildQuery.cs # BuildQueryWithCondition, Build*Query
│ ├── Mediators/
│ │ ├── Abstraction/
│ │ │ ├── BaseQuery.cs # CTBaseQuery<TQuery, TResponse>
│ │ │ ├── ObjectQueryHandler.cs # Single entity query handler
│ │ │ └── PageQueryHandler.cs # Paged list query handler
│ │ └── Interfaces/
│ │ ├── ICommand.cs / ICommandHandler.cs
│ │ └── IQuery.cs / IQueryHandler.cs
│ └── Primitivies/
│ ├── CTBaseResult.cs # Result pattern (Success/Failure)
│ ├── Pagination.cs # IPagingRequest, IPagingResponse
│ ├── QueryRequestV3.cs # Conditions + Populate + ToCacheKey
│ └── QueryPageRequestV3.cs # + Limit, Offset, Page, OrderBy
│
└── Modules/
├── OData/ # Auto-generate OData endpoints từ Entity
│ ├── Controllers/EntitySetsController.cs
│ ├── Core/ (Container, Metadata, Routing, Template)
│ ├── Handlers/ApiHandler.cs # Generic CRUD handler
│ └── ServicesCollectionExtensions.cs
│
└── Populate/ # Dynamic projection (select chỉ fields cần thiết)
├── Builders/ (ExpressionBuilder, ProjectionBuilder)
├── Definations/ (PopulateConstant, PopulateOptions)
├── Exceptions/ (PopulateNotHandleException, QueryBuilderException)
├── Extensions/ (AnonymousType, Method, Projection, Regex, Type)
├── Internal/ (PrimitiveMappers, Projection, Queries)
├── Public/ (Attributes, Descriptors, MemberPath, MetaDataRegistry, PropertyAnalyzer, QueryOptions)
├── Visitors/ (7 visitors cho expression tree manipulation)
└── ProjectionExtension.cs
3. Data Flow chi tiết
3.1 Request Parsing
Client Request → BaseAPIPageRequest.BindAsync(HttpContext)
├── Parse query params: p, s, orderBy, isAsc, page, limit, offset
└── .ToQueryContext() → QueryPageRequestV3
├── ConditionDescriptor(JsonDeserialize(S))
└── Populate list
Cấu trúc JSON parameter s (conditions):
{
"$and": {
"$eq": [
{ "key": "Status", "val": "Active" },
{ "key": "DepartmentId", "val": "dept-001" }
],
"$fli": [
{ "key": "FullName", "val": "Nguyen" }
]
},
"$and_or": {
"$fli": [
{ "key": "FullName", "val": "Tran" },
{ "key": "Email", "val": "tran" }
],
"$eq": [
{ "key": "Status", "val": "Active" },
{ "key": "Status", "val": "Pending" }
]
}
}
Cấu trúc dữ liệu: Dictionary<string, Dictionary<string, List<SQueryItem>>>
- Level 1 (
$and,$and_or): Loại condition group - Level 2 (
$eq,$fli,$fsw,$few,$gt...): Toán tử - Level 3 (
SQueryItem): key-value pair (property name → giá trị so sánh)
3.2 Query Building Pipeline
IQueryable<TEntity>
│
├─── HandleLinqQueryRequestV2(request)
│ ├── Nếu KHÔNG có "$and_or":
│ │ └── foreach Values → HandlerLinqQueryItem()
│ │ ├── $fli → BuildLikeQuery() → .Contains()
│ │ ├── $fsw → BuildStartWithQuery() → .StartsWith()
│ │ ├── $few → BuildEndWithQuery() → .EndsWith()
│ │ └── $eq/$neq/$gt/$gte/$lt/$lte → BuildQueryWithCondition()
│ │
│ └── Nếu CÓ "$and_or":
│ └── foreach nodes → BuildQuery + $or connector
│ ├── $fli → BuildLikeQuery(_, "$or")
│ ├── $fsw → BuildStartWithQuery(_, "$or")
│ ├── $few → BuildEndWithQuery(_, "$or")
│ └── $eq/$neq... → BuildQueryWithCondition(_, "$or", operator)
│
├─── HandleLinqQueryPageRequestV2(request)
│ ├── OrderBy (dynamic string)
│ ├── Count() → totalRecords
│ ├── Math.Ceiling → totalPages
│ └── Skip().Take() → pagination
│
└─── ProjectDynamic<TEntity>(mapper, populate, cacheKey)
└── AutoMapper + dynamic projection (chỉ select fields được yêu cầu)
3.3 Condition Operators
| Operator | Ký hiệu | SQL tương đương | Method |
|---|---|---|---|
$eq |
== |
WHERE x = @v |
BuildQueryWithCondition |
$neq |
!= |
WHERE x != @v |
BuildQueryWithCondition |
$gt |
> |
WHERE x > @v |
BuildQueryWithCondition |
$gte |
>= |
WHERE x >= @v |
BuildQueryWithCondition |
$lt |
< |
WHERE x < @v |
BuildQueryWithCondition |
$lte |
<= |
WHERE x <= @v |
BuildQueryWithCondition |
$fli |
Contains | WHERE x LIKE '%v%' |
BuildLikeQuery |
$fsw |
StartsWith | WHERE x LIKE 'v%' |
BuildStartWithQuery |
$few |
EndsWith | WHERE x LIKE '%v' |
BuildEndWithQuery |
3.4 Condition Groups
| Group Key | Ý nghĩa | Connector giữa các items |
|---|---|---|
$and |
AND group | && giữa các conditions |
$and_or |
OR group | Tìm key $and_or, dùng \|\| giữa items |
4. Populate Module (Dynamic Projection)
Cho phép client chỉ lấy các field cần thiết thay vì toàn bộ entity:
GET /api/employees?p=Id,FullName,Department.Name
Sử dụng AutoMapper + Expression Tree để tạo projection:
PopulateAnalyzerphân tích danh sách field pathsTypeMapper+PropertyMappermap giữa source → destination typesProjectionBuildertạoExpression<Func<TSource, TDest>>- Kết quả cache bằng
IMemoryCachetheoToCacheKey()
Đặc biệt: Hỗ trợ nested navigation (Department.Name), collection projection (Employees.FullName), và auto split query khi depth ≥ 3.
5. OData Module (Auto-Generated Endpoints)
Tự động tạo CRUD endpoints cho bất kỳ entity nào:
[ODataRouting(RoutePrefix = "api/v1")]
public class Employee : CTBaseEntity { ... }
Tự động generate:
GET /api/v1/employees→ paginated listGET /api/v1/employees/{id}→ single entityPOST /api/v1/employees→ createPATCH /api/v1/employees/{id}→ partial update (Delta<T>)DELETE /api/v1/employees/{id}→ delete
6. Dependencies
| Package | Version | Mục đích |
|---|---|---|
AutoMapper |
13.0.1 | Dynamic projection mapping |
FluentValidation |
11.11.0 | Input validation |
Humanizer |
2.14.1 | String transformation (pluralize entity names) |
MediatR |
12.4.1 | CQRS mediator pattern |
Microsoft.AspNetCore.OData |
9.1.1 | OData support (Delta<T>) |
Microsoft.EntityFrameworkCore |
9.0.0 | EF Core base |
Microsoft.EntityFrameworkCore.Relational |
9.0.0 | Relational DB support |
System.Linq.Dynamic.Core |
1.6.0.2 | Dynamic LINQ string queries |
CTCore.DynamicQuery - Hướng dẫn sử dụng
1. Cài đặt
dotnet add package Q.CTCore --version 1.7.0
2. Setup cơ bản
2.1 Entity
public class Employee : CTBaseEntity
{
public string FullName { get; set; }
public string Email { get; set; }
public string DepartmentId { get; set; }
public Department Department { get; set; }
public ICollection<Skill> Skills { get; set; }
}
2.2 Repository
public interface IEmployeeRepository : IRepository<Employee> { }
public class EmployeeRepository(DbContext context) : IEmployeeRepository
{
public IQueryable<Employee> BuildQuery => context.Set<Employee>();
public void Add(params Employee[] entities) => context.Set<Employee>().AddRange(entities);
public void Remove(params Employee[] entities) => context.Set<Employee>().RemoveRange(entities);
public async Task<Employee?> GetByIdAsync(string id, CancellationToken ct)
=> await context.Set<Employee>().FindAsync([id], ct);
}
3. Sử dụng Query API
3.1 Query cơ bản (MinimalAPI)
app.MapGet("/employees", async (BaseAPIPageRequest request, ISender sender) =>
{
var query = request.ToQueryContext();
var result = await sender.Send(new GetAllEmployeesQuery(query));
return result.Match(ok => Results.Ok(ok), err => Results.BadRequest(err.ToErrorResponse()));
});
3.2 Cấu trúc Query String
GET /employees?s={"$and":{"$eq":[{"key":"Status","val":"Active"}]}}&p=Id,FullName&page=1&limit=10
| Param | Mô tả | Ví dụ |
|---|---|---|
s |
JSON conditions | {"$and":{"$eq":[...]}} |
p |
Populate (select fields) | Id,FullName,Department.Name |
page |
Số trang | 1 |
limit |
Records per page | 10 |
offset |
Skip records | 0 |
orderBy |
Sort field | FullName |
isAsc |
Sort direction | true |
4. Condition Syntax
4.1 AND conditions
Tất cả conditions trong 1 group kết nối bằng &&:
{
"$and": {
"$eq": [
{ "key": "Status", "val": "Active" },
{ "key": "DepartmentId", "val": "dept-001" }
],
"$fli": [
{ "key": "FullName", "val": "Nguyen" }
]
}
}
→ SQL: WHERE Status == 'Active' AND DepartmentId == 'dept-001' AND FullName LIKE '%Nguyen%'
4.2 OR conditions ($and_or)
Tất cả conditions kết nối bằng ||:
{
"$and_or": {
"$fli": [
{ "key": "FullName", "val": "Nguyen" },
{ "key": "Email", "val": "nguyen" }
]
}
}
→ SQL: WHERE FullName LIKE '%Nguyen%' OR Email LIKE '%nguyen%'
4.3 Kết hợp AND + OR
{
"$and": {
"$eq": [
{ "key": "Status", "val": "Active" }
]
},
"$and_or": {
"$fli": [
{ "key": "FullName", "val": "Nguyen" },
{ "key": "Email", "val": "nguyen" }
]
}
}
→ SQL: WHERE Status == 'Active' AND (FullName LIKE '%Nguyen%' OR Email LIKE '%nguyen%')
4.4 Comparison Operators
{
"$and": {
"$gt": [{ "key": "Age", "val": "18" }],
"$lte": [{ "key": "Salary", "val": "50000" }],
"$neq": [{ "key": "Status", "val": "Deleted" }]
}
}
4.5 String Operators
| Operator | SQL equivalent | Mô tả |
|---|---|---|
$fli |
LIKE '%v%' |
Contains (tìm chuỗi con) |
$fsw |
LIKE 'v%' |
StartsWith (bắt đầu bằng) |
$few |
LIKE '%v' |
EndsWith (kết thúc bằng) |
4.6 $or_ prefix operators (trong $and_or group)
Các operator với prefix $or_ cho phép mix nhiều loại condition trong cùng 1 OR group:
| Operator | Ý nghĩa |
|---|---|
$or_fli |
OR + Contains |
$or_fsw |
OR + StartsWith |
$or_few |
OR + EndsWith |
{
"$and_or": {
"$eq": [
{ "key": "Status", "val": "Active" },
{ "key": "Status", "val": "Pending" }
],
"$or_fli": [
{ "key": "FullName", "val": "Nguyen" },
{ "key": "Email", "val": "nguyen" }
],
"$or_fsw": [
{ "key": "Code", "val": "EMP" }
]
}
}
→ SQL: WHERE (Status == 'Active' OR Status == 'Pending') AND (FullName LIKE '%Nguyen%' OR Email LIKE '%nguyen%') AND (Code LIKE 'EMP%')
5. Populate (Dynamic Selection)
5.1 Select all fields
GET /employees?p=*
5.2 Select specific fields
GET /employees?p=Id,FullName,Email
5.3 Nested navigation
GET /employees?p=Id,FullName,Department.Name,Department.Code
5.4 Collection projection
GET /employees?p=Id,FullName,Skills.Name,Skills.Level
6. OData Auto-Endpoints
Đánh dấu entity bằng [ODataRouting]:
[ODataRouting(RoutePrefix = "api/v1")]
public class Employee : CTBaseEntity { ... }
Đăng ký trong Program.cs:
builder.Services.AddGenericODataEndpoints(defaultRoutePrefix: "api/v1");
var app = builder.Build();
app.UseGenericODataEndpoints();
Auto-generated endpoints:
GET /api/v1/employees→ paginated listGET /api/v1/employees/{id}→ get by IDPOST /api/v1/employees→ createPATCH /api/v1/employees/{id}→ partial updateDELETE /api/v1/employees/{id}→ delete
7. CQRS Pattern (Custom Handlers)
7.1 Query definition
public class GetAllEmployeesQuery(QueryPageRequestV3 query)
: CTBaseQuery<QueryPageRequestV3, OkDynamicPageResponse>(query) { }
7.2 Query handler
public class GetAllEmployeesHandler(IRepository<Employee> repos, IMapper mapper)
: PageQueryHandler<GetAllEmployeesQuery, Employee>(repos, mapper)
{
// Override Handle() nếu cần custom logic
}
7.3 Với DTO mapping
public class GetAllEmployeesHandler(IRepository<Employee> repos, IMapper mapper)
: PageQueryHandler<GetAllEmployeesQuery, Employee, EmployeeDto>(repos, mapper) { }
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- AutoMapper (>= 13.0.1)
- FluentValidation (>= 11.11.0)
- Humanizer (>= 2.14.1)
- MediatR (>= 12.4.1)
- Microsoft.AspNetCore.OData (>= 9.1.1)
- Microsoft.EntityFrameworkCore (>= 9.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.0)
- System.Linq.Dynamic.Core (>= 1.6.0.2)
-
net9.0
- AutoMapper (>= 13.0.1)
- FluentValidation (>= 11.11.0)
- Humanizer (>= 2.14.1)
- MediatR (>= 12.4.1)
- Microsoft.AspNetCore.OData (>= 9.1.1)
- Microsoft.EntityFrameworkCore (>= 9.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.0)
- System.Linq.Dynamic.Core (>= 1.6.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 1.7.0 | 86 | 2/17/2026 | |
| 1.6.1.1 | 417 | 11/18/2025 | |
| 1.6.1 | 712 | 11/18/2025 | |
| 1.6.0.1 | 709 | 11/18/2025 | |
| 1.6.0 | 711 | 11/18/2025 | |
| 1.5.3.4 | 505 | 10/2/2025 | |
| 1.5.3.3 | 155 | 10/2/2025 | |
| 1.5.3.2 | 522 | 7/16/2025 | |
| 1.5.3.1 | 384 | 7/9/2025 | |
| 1.5.3 | 199 | 7/7/2025 | |
| 1.5.2 | 668 | 5/6/2025 | |
| 1.5.1 | 186 | 5/2/2025 | |
| 1.5.0 | 244 | 4/17/2025 | |
| 1.0.4.9 | 353 | 2/14/2025 | |
| 1.0.4.8 | 304 | 1/14/2025 | |
| 1.0.4.7 | 155 | 1/14/2025 | |
| 1.0.4.6 | 145 | 1/13/2025 | |
| 1.0.4.5 | 158 | 1/8/2025 | |
| 1.0.4.4 | 172 | 1/8/2025 | |
| 1.0.4.3 | 174 | 12/27/2024 |