DynamicWhere.ex
DynamicWhere.exv3.4.0·docs

Summary

A Summary is the shape for aggregate reporting. It runs the full pipeline where → group → having → order → page and returns dynamic rows of group keys plus aggregate alias values.

Properties

PropertyTypeDescription
ConditionGroupConditionGroup?Optional where-clause (pre-grouping).
GroupByGroupBy?Required. Grouping and aggregation config.
HavingConditionGroup?Optional post-group filter. Each condition's Field must reference an AggregateBy.Alias.
OrdersList<OrderBy>?Sort on grouped result. Fields must be GroupBy fields or aggregate aliases.
PagePageBy?Optional pagination on grouped result.
Having fields are aliases, not entity properties
Inside Having every Condition.Field refers to an AggregateBy.Alias — not to a property on the underlying entity. Trying to reference an entity property in Having is a validation error.

C# example

var summary = new Summary
{
    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 }
            }
        }
    },
    GroupBy = new GroupBy
    {
        Fields = new List<string> { "Country" },
        AggregateBy = new List<AggregateBy>
        {
            new AggregateBy { Aggregator = Aggregator.Count, Alias = "Total" },
            new AggregateBy { Field = "Amount", Aggregator = Aggregator.Sumation, Alias = "Revenue" }
        }
    },
    Having = new ConditionGroup
    {
        Connector = Connector.And,
        Conditions = new List<Condition>
        {
            new Condition
            {
                Sort = 1, Field = "Total",
                DataType = DataType.Number, Operator = Operator.GreaterThan,
                Values = new List<object> { 10 }
            }
        }
    },
    Orders = new List<OrderBy>
    {
        new OrderBy { Sort = 1, Field = "Revenue", Direction = Direction.Descending }
    },
    Page = new PageBy { PageNumber = 1, PageSize = 20 }
};

SummaryResult result = await dbContext.Orders.ToListAsync(summary);

JSON example

{
  "conditionGroup": {
    "connector": "And",
    "conditions": [
      { "sort": 1, "field": "IsActive", "dataType": "Boolean", "operator": "Equal", "values": [true] }
    ],
    "subConditionGroups": []
  },
  "groupBy": {
    "fields": ["Country"],
    "aggregateBy": [
      { "aggregator": "Count", "alias": "Total" },
      { "field": "Amount", "aggregator": "Sumation", "alias": "Revenue" }
    ]
  },
  "having": {
    "connector": "And",
    "conditions": [
      { "sort": 1, "field": "Total", "dataType": "Number", "operator": "GreaterThan", "values": [10] }
    ],
    "subConditionGroups": []
  },
  "orders": [
    { "sort": 1, "field": "Revenue", "direction": "Descending" }
  ],
  "page": { "pageNumber": 1, "pageSize": 20 }
}

Clone

Summary.Clone() is public since 3.3.0. It returns a deep copy — the condition group, the group-by with its aggregates, the having clause, 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.

Summary page2 = caller.Clone();
page2.Page!.PageNumber = 2;          // the caller's own summary 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