DynamicWhere.ex
DynamicWhere.exv3.4.0·docs

.ToListAsync<T>(Filter)

Async version of .ToList<T>(Filter). Uses EF Core's CountAsync() and ToListAsync() under the hood. Since 3.2.0 two more overloads take a CancellationToken, which reaches both.

Signature

public static Task<FilterResult<T>> ToListAsync<T>(
    this IQueryable<T> query,
    Filter filter,
    bool getQueryString = false)
    where T : class

// 3.2.0
public static Task<FilterResult<T>> ToListAsync<T>(
    this IQueryable<T> query,
    Filter filter,
    CancellationToken cancellationToken)
    where T : class

public static Task<FilterResult<T>> ToListAsync<T>(
    this IQueryable<T> query,
    Filter filter,
    bool getQueryString,
    CancellationToken cancellationToken)
    where T : class
ParameterTypeDefaultDescription
filterFilter–Composition object
getQueryStringboolfalseWhen true, captures the generated SQL on FilterResult.QueryString
cancellationTokenCancellationToken–Cancels the count and the read. The overload without it passes CancellationToken.None

Pipeline

  • Where applied on the typed query.
  • CountAsync(cancellationToken) on the typed query → TotalCount.
  • Order applied on the typed query.
  • Page applied on the typed query.
  • Select projection applied last.
  • ToListAsync(cancellationToken) materializes the result.
Warning
Ordering and pagination are applied on the strongly-typed IQueryable<T> before the select projection so that field names referenced in orders always resolve against the original entity type T.
Note
getQueryString: true calls .ToQueryString() which requires an active EF Core database provider.

Cancellation

The two overloads that take a CancellationToken are new in 3.2.0. The token reaches the count and the read, so a canceled token stops whichever of the two is running, and the call throws OperationCanceledException. EF Core's TaskCanceledException derives from it. The overload without a token passes CancellationToken.None.

They are overloads, not an optional parameter added to the old signature. The 3.1 signature is unchanged, so code compiled against 3.1 still binds. The guarded handle that ApplyPolicy returns has the same overloads.

ToListAsync(filter, default) does not compile
default fits both bool getQueryString and CancellationToken, so the compiler reports the call as ambiguous (CS0121). Write false, a token, or a named argument.
await db.Customers.ToListAsync(filter, default);                   // CS0121: ambiguous
await db.Customers.ToListAsync(filter, cancellationToken);         // the token overload
await db.Customers.ToListAsync(filter, getQueryString: false);     // no token
await db.Customers.ToListAsync(filter, true, cancellationToken);   // the SQL and a token

Returns

Task<FilterResult<T>>.

Example

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

Console.WriteLine($"{result.TotalCount} customers");
foreach (var c in result.Data)
{
    Console.WriteLine($"- {c.Name}");
}

Capture generated SQL.

var result = await dbContext.Customers.ToListAsync(filter, getQueryString: true);
Console.WriteLine(result.QueryString);

Minimal ASP.NET Core endpoint.

app.MapPost("/customers/search", async (Filter filter, AppDbContext db) =>
{
    var result = await db.Customers.ToListAsync(filter);
    return Results.Ok(result);
});

Cancel with the request. A minimal API binds a CancellationToken parameter to HttpContext.RequestAborted, so a client that disconnects cancels the count or the read.

app.MapPost("/customers/search", async (Filter filter, AppDbContext db, CancellationToken cancellationToken) =>
{
    var result = await db.Customers.ToListAsync(filter, cancellationToken);
    return Results.Ok(result);
});

See also