.ToListAsync<T>(Segment)
Async-only segment operation. Combines every ConditionSet with set operations (Union / Intersect / Except) into one query, then orders, pages and projects it in the database exactly as a Filter is.
.ToListAsync<T>(Segment) is the only entry point for segment queries — there is no synchronous .ToList<T>(Segment) variant. It needs an EF Core provider. On a type with a primary key, only Except needs one that translates a correlated EXISTS.Signature
public static Task<SegmentResult<T>> ToListAsync<T>(
this IQueryable<T> query,
Segment segment)
where T : class
// 3.2.0
public static Task<SegmentResult<T>> ToListAsync<T>(
this IQueryable<T> query,
Segment segment,
CancellationToken cancellationToken)
where T : class| Parameter | Type | Description |
|---|---|---|
segment | Segment | Composition object — ConditionSets, Selects, Orders, Page |
cancellationToken | CancellationToken | Cancels the count and the read. New in 3.2.0; the overload without it passes CancellationToken.None |
Pipeline
- Combine the sets in
Sortorder, left to right, using each later set'sIntersection. The first set'sIntersectionis ignored. UnionandIntersectjoin the sets' own conditions withORandAND.Exceptremoves the rows of its set withNOT EXISTS, matched onT's primary key as EF Core maps it — composite, value-converted and inherited keys included.- Apply
Orders, thenPage, thenSelectsto the combined query, and count it forTotalCount— the same steps, in the same order, asToListAsync(Filter). Only the requested page is read, and an order field need not be selected. - The overload that takes a
CancellationTokenpasses it to that count and that read. A canceled token stops whichever of the two is running, and the call throwsOperationCanceledException.
A segment takes no getQueryString, so ToListAsync(segment, default) is not ambiguous: it binds the token overload and passes CancellationToken.None. The 3.1 signature is unchanged, so code compiled against 3.1 still binds.
Which rows belong is decided in the database, not by object reference, so a tracking query, an AsNoTracking() query and a query with Selects all return the same rows. Ordering is the database's: text sorts by its collation, and NULLs fall where the provider puts them.
Validations
ConditionSetsSortvalues must be unique —SetsUniqueSort.- Sets at index 1+ must have
Intersectionspecified —RequiredIntersection. - Each
ConditionSet.ConditionGroupis validated as in.Where<T>. Orders(if provided): eachFieldmust be non-empty and valid onT.Page(if provided): bothPageNumberandPageSizemust be > 0.
Returns
Task<SegmentResult<T>> — inherits all properties from FilterResult<T>.
Example
var segment = new Segment
{
ConditionSets = new List<ConditionSet>
{
new ConditionSet
{
Sort = 1,
Intersection = null,
ConditionGroup = new ConditionGroup
{
Connector = Connector.And,
Conditions = new List<Condition>
{
new Condition { Sort = 1, Field = "Category.Name", DataType = DataType.Text, Operator = Operator.Equal, Values = new List<object> { "Electronics" } }
}
}
},
new ConditionSet
{
Sort = 2,
Intersection = Intersection.Union,
ConditionGroup = new ConditionGroup
{
Connector = Connector.And,
Conditions = new List<Condition>
{
new Condition { Sort = 1, Field = "Price", DataType = DataType.Number, Operator = Operator.LessThan, Values = new List<object> { 20 } }
}
}
},
new ConditionSet
{
Sort = 3,
Intersection = Intersection.Except,
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> { false } }
}
}
}
},
Selects = new List<string> { "Id", "Name", "Price" },
Orders = new List<OrderBy> { new OrderBy { Sort = 1, Field = "Name", Direction = Direction.Ascending } },
Page = new PageBy { PageNumber = 1, PageSize = 20 }
};
SegmentResult<Product> result = await dbContext.Products.ToListAsync(segment);{
"conditionSets": [
{
"sort": 1,
"intersection": null,
"conditionGroup": {
"connector": "And",
"conditions": [
{ "sort": 1, "field": "Category.Name", "dataType": "Text", "operator": "Equal", "values": ["Electronics"] }
],
"subConditionGroups": []
}
},
{
"sort": 2,
"intersection": "Union",
"conditionGroup": {
"connector": "And",
"conditions": [
{ "sort": 1, "field": "Price", "dataType": "Number", "operator": "LessThan", "values": ["20"] }
],
"subConditionGroups": []
}
},
{
"sort": 3,
"intersection": "Except",
"conditionGroup": {
"connector": "And",
"conditions": [
{ "sort": 1, "field": "IsActive", "dataType": "Boolean", "operator": "Equal", "values": ["false"] }
],
"subConditionGroups": []
}
}
],
"selects": ["Id", "Name", "Price"],
"orders": [
{ "sort": 1, "field": "Name", "direction": "Ascending" }
],
"page": { "pageNumber": 1, "pageSize": 20 }
}Logic: (Electronics) UNION (Price < 20) EXCEPT (Inactive) → order → paginate.
T, has no key to match on. Its sets are combined with SQL UNION / INTERSECT / EXCEPT, which compare every column: identical rows collapse into one, a column the database cannot compare (PostgreSQL json, SQL Server xml) fails the query even when it is not selected, and the provider must support the operators the sets use.AsNoTracking(), with Selects, and under ApplyPolicy, which is always untracked, Intersect returned nothing, Except removed nothing and Union counted a row once per set. Ordering and paging ran in memory after projection, and every row of every set was read. See breaking changes.A typed row is a whole Product, not a trimmed object: the members outside Selects are still present, holding their defaults.
{
"pageNumber": 1,
"pageSize": 20,
"pageCount": 2,
"totalCount": 35,
"data": [
{
"id": 1,
"name": "Adapter Cable",
"price": 9.99,
"isActive": false,
"createdAt": "0001-01-01T00:00:00",
"category": null
}
],
"queryString": null
}See also
Segmentshape.ConditionSetshape andIntersectionvalues.- Segment validation.
- JSON Cookbook: Segment.