DynamicWhere.ex
DynamicWhere.exv3.4.0·docs

Filter

A Filter is the most common top-level shape. It combines filtering, projection, ordering, and pagination in a single object, and is the input to ToListAsync<T>(Filter) and its dynamic / sync siblings.

Properties

PropertyTypeDescription
ConditionGroupConditionGroup?Optional where-clause.
SelectsList<string>?Optional field projection (like SQL SELECT col1, col2).
OrdersList<OrderBy>?Optional sort criteria.
PagePageBy?Optional pagination.
Order of operations
Internally the pipeline is where → order → page → select. Ordering and paging run on the full entity, then projection is applied to the trimmed page — so you can sort by a field you don't include in Selects.

C# example

var filter = new Filter
{
    ConditionGroup = new ConditionGroup
    {
        Connector = Connector.And,
        Conditions = new List<Condition>
        {
            new Condition
            {
                Sort = 1,
                Field = "IsActive",
                DataType = DataType.Boolean,
                Operator = Operator.Equal,
                Values = new List<object> { true }
            }
        }
    },
    Selects = new List<string> { "Id", "Name", "Email" },
    Orders = new List<OrderBy>
    {
        new OrderBy { Sort = 1, Field = "CreatedAt", Direction = Direction.Descending }
    },
    Page = new PageBy { PageNumber = 1, PageSize = 25 }
};

FilterResult<Customer> result = await dbContext.Customers.ToListAsync(filter);

JSON example

{
  "conditionGroup": {
    "connector": "And",
    "conditions": [
      { "sort": 1, "field": "IsActive", "dataType": "Boolean", "operator": "Equal", "values": [true] }
    ],
    "subConditionGroups": []
  },
  "selects": ["Id", "Name", "Email"],
  "orders": [
    { "sort": 1, "field": "CreatedAt", "direction": "Descending" }
  ],
  "page": { "pageNumber": 1, "pageSize": 25 }
}

Clone

Filter.Clone() is public since 3.3.0. It returns a deep copy — the condition tree with its groups and conditions, the Selects list, each order and the page — so nothing either request is given afterwards reaches the other.

Every node is new. The values a condition carries stay the caller's own objects, in a new list: they are scalars decoded from JSON and nothing in the pipeline writes to them.

Filter page2 = caller.Clone();
page2.Page!.PageNumber = 2;          // the caller's own filter is untouched

Reading one request again with a part changed, the next page or another order, used to mean rebuilding it around the caller's own clauses, which leaves both requests holding one condition tree: a rewrite of either reaches both. The library has cloned before rewriting anything since 3.0; callers could not until now. A branch the caller left null stays null, and a null entry inside a list is copied as a null entry rather than failing on it (3.3.0), so the refusal belongs to the method that runs the request and reads the same for a copy.

See also