Q.CTCore 1.7.0

dotnet add package Q.CTCore --version 1.7.0
                    
NuGet\Install-Package Q.CTCore -Version 1.7.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Q.CTCore" Version="1.7.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Q.CTCore" Version="1.7.0" />
                    
Directory.Packages.props
<PackageReference Include="Q.CTCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Q.CTCore --version 1.7.0
                    
#r "nuget: Q.CTCore, 1.7.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Q.CTCore@1.7.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Q.CTCore&version=1.7.0
                    
Install as a Cake Addin
#tool nuget:?package=Q.CTCore&version=1.7.0
                    
Install as a Cake Tool

CTCore.DynamicQuery - Kiến trúc tổng quan

Package: Q.CTCore v1.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:

  1. PopulateAnalyzer phân tích danh sách field paths
  2. TypeMapper + PropertyMapper map giữa source → destination types
  3. ProjectionBuilder tạo Expression<Func<TSource, TDest>>
  4. Kết quả cache bằng IMemoryCache theo ToCacheKey()

Đặ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 list
  • GET /api/v1/employees/{id} → single entity
  • POST /api/v1/employees → create
  • PATCH /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 list
  • GET /api/v1/employees/{id} → get by ID
  • POST /api/v1/employees → create
  • PATCH /api/v1/employees/{id} → partial update
  • DELETE /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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.1 is deprecated because it has critical bugs.
1.6.0.1 709 11/18/2025 1.6.0.1 is deprecated because it has critical bugs.
1.6.0 711 11/18/2025 1.6.0 is deprecated because it has critical bugs.
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.4 is deprecated because it has critical bugs.
1.0.4.3 174 12/27/2024
Loading failed