Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Query

ToList

<query>.ToList() → List<Entity> · <query>.ToArray() → <Entity>[]

Runs the query and materialises the rows as a `List<Entity>`. Until you call it, a query is a description of what you want; ToList is the moment it becomes rows you can walk, count and index. `.ToArray()` does the same and answers the fixed-size `Entity[]` instead.

stable4 examples compiled by CIquerydata

Summary#

.ToList() runs the query. Up to that point you have built a description — a set of conditions, an order, a page — and nothing has touched the database. ToList is where it executes and you get rows back, typed as List<Entity> exactly as in C#.

Signature#

<Entity>.Where(…).OrderBy(…).ToList()    →   List<Entity>    // materialised, and mutable
<Entity>.Where(…).OrderBy(…).ToArray()   →   <Entity>[]      // materialised, and fixed-size
<Entity>.Where(…).OrderBy(…)             →   <Entity>[]      // a chain that simply ends

Description#

When does the query actually run?#

The chain composes without executing. Only ToList (and the scalar calls — Count, Any, Single) go to the database:

entity Order {
  [Required] string Code;
  decimal Total;
}

List<Order> TopOrders(decimal min, int take) {
  return Order
    .Where(o => o.Total >= min)      // nothing has run yet
    .OrderByDescending(o => o.Total) // still nothing
    .Take(take)
    .ToList();                       // NOW it runs — one query, one round trip
}

What you get back#

A List<Entity> — materialised rows. It has a .Count, it can be indexed, and it can be walked:

decimal SumOfTop(decimal min, int take) {
  var top = TopOrders(min, take);

  var total = 0m;
  foreach (var o in top) { total += o.Total; }   // walk it

  var first = top.Count > 0 ? top[0].Total : 0m;  // index it
  return total + first * 0m;
}

Why does calling it twice do the work twice?#

Because ToList is the moment work happens, calling it twice does the work twice. Materialise into a local and use that:

string Describe(decimal min) {
  var orders = Order.Where(o => o.Total >= min).ToList();   // one query
  var count = orders.Count;
  var total = 0m;
  foreach (var o in orders) { total += o.Total; }
  return Convert.ToString(count) + " orders, " + Convert.ToString(total);
}

Writing Order.Where(…).ToList() twice in that function would run two identical queries — and, if a row changed in between, give you two different answers to the same question.

ToList or ToArray — which materialiser#

Both run the query and bring back the same rows. They differ only in the type you are left holding, and that is the same difference C# draws:

  • .ToList() answers a List<Entity> — you can .Add to it, so it is what you want when the rows are the start of something you are still assembling.
  • .ToArray() answers an Entity[] — fixed-size, and the plainer statement when the rows are the answer.

A List<T> is accepted anywhere a T[] is asked for, because giving up .Add is always safe. The reverse is not: a T[] is not a List<T>, and a function that wants one says so by materialising with .ToList().

List<Order> Growing(decimal min) { return Order.Where(o => o.Total >= min).ToList(); }
Order[]     Fixed(decimal min)   { return Order.Where(o => o.Total >= min).ToArray(); }

// A List goes where an array is wanted — no conversion written, none needed.
Order[] Widened(decimal min) { return Order.Where(o => o.Total >= min).ToList(); }

// …and `.ToArray()` says it out loud, on a list you built yourself.
string[] Codes(decimal min) {
  var codes = new List<string>();
  foreach (var o in Order.Where(o => o.Total >= min).ToList()) { codes.Add(o.Code); }
  return codes.ToArray();
}

.ToArray() takes a COPY. Adding to the list afterwards does not change the array you already took — that is what makes "fixed-size" mean anything.

When you only want a number#

If all you need is how many, do not materialise the rows to count them. Count() asks the database for the number and brings back one integer instead of ten thousand rows. See Where / Single / Count.

See also#

Related

Where / Single / Count

Query an entity by writing a predicate over it. The query runs in the database — not a filter over rows you already…

Skip / Take (paging)

Page a query with Skip(n) (OFFSET) and Take(m) (LIMIT). The count can be a compile-time constant OR a runtime integer —…

foreach

Walks a collection — a query result, a list, or a parent's children. The normal way to iterate; reach for a for loop…

Array literals

An array literal [a, b, c] is a value: a list you can return, assign, pass as an argument, or supply as a UI prop. Its…