DynamicPredicateBuilder 1.0.2

There is a newer version of this package available.
See the version list below for details.
dotnet add package DynamicPredicateBuilder --version 1.0.2
                    
NuGet\Install-Package DynamicPredicateBuilder -Version 1.0.2
                    
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="DynamicPredicateBuilder" Version="1.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DynamicPredicateBuilder" Version="1.0.2" />
                    
Directory.Packages.props
<PackageReference Include="DynamicPredicateBuilder" />
                    
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 DynamicPredicateBuilder --version 1.0.2
                    
#r "nuget: DynamicPredicateBuilder, 1.0.2"
                    
#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 DynamicPredicateBuilder@1.0.2
                    
#: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=DynamicPredicateBuilder&version=1.0.2
                    
Install as a Cake Addin
#tool nuget:?package=DynamicPredicateBuilder&version=1.0.2
                    
Install as a Cake Tool

DynamicPredicateBuilder 使用說明

支援環境:.NET 9;.NET 8; .NET 7 核心特色:動態過濾、排序、分頁、欄位查詢權限、巢狀/多組條件、NOT 取反、重複條件自動去除。


1. 欄位查詢權限設定

1-1. 程式碼指定可查詢欄位

[HttpPost("people")]
public IActionResult QueryPeople([FromBody] QueryRequest request)
{
    var options = new FilterOptions
    {
        AllowedFields = new HashSet<string> { "Name", "Age", "Address.City" }
    };

    var filterGroup = FilterGroupFactory.FromDictionary(request.Filter);
    var predicate   = FilterBuilder.Build<Person>(filterGroup, options);

    var data = _db.People
                 .Where(predicate)
                 .ApplySort(request.Sort)
                 .ApplyPaging(request.Page, request.PageSize, out var total);

    return Ok(new QueryResult<Person> { TotalCount = total, Items = data });
}

1-2. 使用 Attribute 自動擷取可查詢欄位

public class Person
{
    [Queryable] public string Name  { get; set; } = string.Empty;
    [Queryable] public int    Age   { get; set; }
                 public string Password { get; set; } = string.Empty;   // ❌ 未標註,不可查
    [Queryable] public Address Address { get; set; } = new();
}

public class Address
{
    [Queryable] public string City { get; set; } = string.Empty;
                 public string SecretNote { get; set; } = string.Empty; // ❌
}

[HttpPost("people")]
public IActionResult QueryPeople([FromBody] QueryRequest request)
{
    var options = new FilterOptions
    {
        AllowedFields = QueryableFieldHelper.GetQueryableFields<Person>()
    };

    var filterGroup = FilterGroupFactory.FromDictionary(request.Filter);
    var predicate   = FilterBuilder.Build<Person>(filterGroup, options);

    // …其餘程式碼同上
}

2. 進階條件組合功能

2-1. 多組條件 (List<FilterGroup>)

FilterBuilder.Build<T>(IEnumerable<FilterGroup> groups, FilterOptions?) 允許在「群組與群組」之間再指定 AND / OR(InterOperator),例如:

var groups = new List<FilterGroup>
{
    // Group 1: !(Name == "Boss") AND Age > 40
    new()
    {
        LogicalOperator = LogicalOperator.And,
        InterOperator   = LogicalOperator.Or,
        Rules =
        [
            new FilterRule { Property = "Name", Operator = Equal, Value="Boss", IsNegated=true },
            new FilterRule { Property = "Age",  Operator = GreaterThan, Value=40 }
        ]
    },

    // Group 2:NOT (Status == "Retired")
    new()
    {
        IsNegated       = true,
        LogicalOperator = LogicalOperator.And,
        Rules =
        [
            new FilterRule { Property = "Status", Operator = Equal, Value="Retired" }
        ]
    }
};

var predicate = FilterBuilder.Build<Person>(groups).Compile();

邏輯相當於
( !(Name == "Boss") && Age > 40 ) OR !(Status == "Retired")

2-2. 巢狀群組 (Nested Group)

FilterGroup.Rules 可再放子 FilterGroup,自然形成括號優先:

{
  "LogicalOperator": "And",
  "Rules": [
    { "Property": "Age", "Operator": "GreaterThan", "Value": 25 },
    {
      "LogicalOperator": "Or",
      "IsNegated": true,                   
      "Rules": [
        { "Property": "Status", "Operator": "Equal", "Value": "Retired" },
        { "Property": "Status", "Operator": "Equal", "Value": "Fired"   }
      ]
    }
  ]
}

等同 SQL:Age > 25 AND NOT (Status = 'Retired' OR Status = 'Fired')

2-3. NOT 取反

  • 單條件FilterRule.IsNegated = true
  • 整組FilterGroup.IsNegated = true

3. API 使用範例

3-1. Request 範例(單組簡易)

{
  "Filter": {
    "LogicalOperator": "And",
    "Rules": [
      { "Property": "Age",          "Operator": "GreaterThanOrEqual", "Value": 25        },
      { "Property": "Address.City", "Operator": "Equal",              "Value": "Taipei" }
    ]
  },
  "Sort": [
    { "Property": "Name", "Descending": false },
    { "Property": "Age",  "Descending": true  }
  ],
  "Page": 1,
  "PageSize": 5
}

3-2. Request 範例(多組 + NOT + 巢狀)

{
  "FilterGroups": [
    {
      "LogicalOperator": "And",
      "InterOperator":   "Or",
      "Rules": [
        { "Property": "Name", "Operator": "Equal", "Value": "Boss", "IsNegated": true },
        { "Property": "Age",  "Operator": "GreaterThan", "Value": 40 }
      ]
    },
    {
      "IsNegated": true,
      "LogicalOperator": "And",
      "Rules": [
        { "Property": "Status", "Operator": "Equal", "Value": "Retired" }
      ]
    }
  ],
  "Sort": [],
  "Page": 1,
  "PageSize": 20
}

Controller 收到 FilterGroups 時,呼叫 FilterBuilder.Build<Person>(request.FilterGroups, options)

3-3. Response 範例

{
  "totalCount": 45,
  "items": [
    { "name": "Alice", "age": 30, "address": { "city": "Taipei" } }
  ]
}

4. 常用 Extension

方法 說明
ApplySort(this IQueryable<T>, IEnumerable<SortRule>) 依多欄位排序 (Linq.Dynamic)。
ApplyPaging(this IQueryable<T>, int page, int size, out int total) 取得總筆數並套用 Skip/Take。
FilterGroupFactory.FromDictionary(IDictionary<string, object>) 把前端 JSON 轉成 FilterGroup 物件。
QueryableFieldHelper.GetQueryableFields<T>() 解析 [Queryable] 標籤產生欄位白名單。

5. 單元測試

DynamicPredicate.Tests 專案示範:

  • FilterBuilderTests:Equal、GreaterThan、NOT、巢狀、多組 AND/OR。
  • DeduplicationTests:重複條件自動去除。
dotnet test

即可看到覆蓋率與測試結果。


持續優化中,歡迎 Issue/PR!

Product Compatible and additional computed target framework versions.
.NET net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  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 was computed.  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.1.12 103 1/21/2026
1.1.11 101 1/19/2026
1.1.8 214 10/13/2025
1.1.0 200 10/3/2025
1.0.6 286 9/24/2025
1.0.5 274 9/24/2025
1.0.4 200 9/2/2025
1.0.3 232 8/28/2025
1.0.2 209 7/8/2025
1.0.1 202 7/8/2025
1.0.0 210 7/8/2025