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
| Property | Type | Description |
|---|---|---|
ConditionGroup | ConditionGroup? | Optional where-clause (pre-grouping). |
GroupBy | GroupBy? | Required. Grouping and aggregation config. |
Having | ConditionGroup? | Optional post-group filter. Each condition's Field must reference an AggregateBy.Alias. |
Orders | List<OrderBy>? | Sort on grouped result. Fields must be GroupBy fields or aggregate aliases. |
Page | PageBy? | Optional pagination on grouped result. |
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 untouchedReading 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.