Question

Dynamic OrderBy Strings for IEnumerable and IQueryable in C#

csharplinqienumerableiqueryablelinq-to-objects

Question

How can I apply Dynamic LINQ-style ordering, such as OrderBy("Name, Age DESC"), to an IEnumerable<T> in C#? The Dynamic LINQ example I found provides this functionality only for IQueryable<T>. Is there a way to achieve the same result for in-memory collections?

Short Answer

Dynamic LINQ can parse a property name and direction from a string, allowing runtime sorting such as "Name, Age DESC". This page explains why many Dynamic LINQ APIs target IQueryable<T>, how AsQueryable() enables the same style for an IEnumerable<T>, and when a safer explicit sorting approach is better.

Concept

LINQ normally requires a strongly typed key selector when sorting:

people.OrderBy(person => person.Name)

The compiler can check that Name exists and determine the key type. However, a string such as "Name, Age DESC" is only known at runtime. Dynamic LINQ libraries parse that string and build the required ordering expressions dynamically.

Many Dynamic LINQ implementations expose OrderBy for IQueryable<T> because IQueryable<T> represents a query as an expression tree. The library can add a dynamically generated OrderBy expression to that query.

An IEnumerable<T> is different: it represents values that can be enumerated in memory. Standard LINQ-to-Objects expects a compiled delegate such as person => person.Name, not a property-name string.

A practical bridge is to call AsQueryable() on the in-memory sequence and then use the Dynamic LINQ OrderBy extension:

var sorted = people
    .AsQueryable()
    .OrderBy("Name, Age DESC");

This performs sorting in memory. Calling AsQueryable() does not turn an in-memory list into a database query; it only exposes the sequence through the IQueryable<T> interface so the Dynamic LINQ API can be used.

Mental Model

Think of normal OrderBy as giving a sorter precise written instructions:

Sort each person using the value in their Name label.

people.OrderBy(person => person.Name)

Dynamic LINQ receives the instructions as a note written at runtime:

Sort by Name, then by Age from highest to lowest.

"Name, Age DESC"

IQueryable<T> is like a planning board that can hold and transform those instructions before execution. IEnumerable<T> is like a box of actual objects ready to process. AsQueryable() puts an in-memory collection behind a planning-board-shaped interface, but the objects remain in the same box and are still sorted locally.

Take Quiz

Syntax and Examples

Dynamic LINQ syntax typically uses property names separated by commas. Use DESC for descending order; ascending order is the default.

using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;

public sealed class Person
{
    public string Name { get; init; } = "";
    public int Age { get; init; }
}

var people = new List<Person>
{
    new() { Name = "Mina", Age = 30 },
    new() { Name = "Adam", Age = 40 },
    new() { Name = "Adam", Age = 25 }
};

string ordering = "Name, Age DESC";

var sortedPeople = people
    .AsQueryable()
    .OrderBy(ordering)
    .ToList();

The resulting order is:

  1. Adam, age 40
  2. Adam, age 25
  3. Mina, age 30

Name is sorted ascending first. When two people have the same name, Age DESC sorts the tied items from oldest to youngest.

For a true database-backed query, do not call AsQueryable() unnecessarily:

IQueryable<Person> query = dbContext.People;
var sortedPeople = query.OrderBy("Name, Age DESC");

In that case, a compatible query provider may translate the order into SQL.

Step by Step Execution

Consider this small example:

var people = new[]
{
    new Person { Name = "Zoe", Age = 22 },
    new Person { Name = "Ana", Age = 19 },
    new Person { Name = "Ana", Age = 35 }
};

var query = people.AsQueryable();
var ordered = query.OrderBy("Name, Age DESC");
var result = ordered.ToList();

Step by step:

  1. people is an IEnumerable<Person> containing objects in memory.

  2. AsQueryable() returns an IQueryable<Person> view over that sequence.

  3. Dynamic LINQ parses "Name, Age DESC".

  4. It creates ordering logic equivalent in intent to:

    people.OrderBy(person => person.Name)
          .ThenByDescending(person => person.Age)
    
  5. ToList() enumerates the ordered query and creates a list.

  6. The final sequence is Ana (35), Ana (19), then Zoe (22).

The actual sorting is deferred until enumeration, such as calling ToList(), looping with foreach, or otherwise consuming the sequence.

Real World Use Cases

Dynamic ordering is useful when the sort choice comes from outside the code path.

  • Data grids: A user clicks a Name, CreatedAt, or Price column header.
  • REST APIs: An endpoint accepts a query parameter such as ?sort=name,-age and maps it to an allowed ordering expression.
  • Admin dashboards: Staff choose a primary and secondary sort order for reports.
  • Import tools: A configurable import pipeline sorts records by a field selected in settings.
  • In-memory reporting: A desktop or command-line program loads data and lets users choose how results are ordered.

For publicly accessible APIs, do not pass arbitrary client input directly to a dynamic expression parser. Validate or map the input to a small list of allowed fields first.

Take Quiz

Real Codebase Usage

In production code, sorting input is usually validated and translated before it reaches Dynamic LINQ. This avoids exposing internal properties and makes the API contract clear.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;

static IQueryable<Person> ApplySorting(
    IEnumerable<Person> people,
    string? sortBy,
    bool descending)
{
    var allowedFields = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
    {
        "name",
        "age"
    };

    var field = string.IsNullOrWhiteSpace(sortBy) ? "name" : sortBy;

    if (!allowedFields.Contains(field))
    {
        throw new ArgumentException("Unsupported sort field.", nameof(sortBy));
    }

    var propertyName = field.Equals("name", StringComparison.OrdinalIgnoreCase)
        ? "Name"
        : "Age";

    var ordering = descending ? propertyName + " DESC" : propertyName;

    return people.AsQueryable().OrderBy(ordering);
}

Common real-project patterns include:

  • Defaults: Use a predictable default sort when no sort option is supplied.
  • Allowlists: Permit only documented fields such as Name, Age, or CreatedAt.
  • Early validation: Reject invalid sort fields before building the query.
  • Stable secondary ordering: Add a unique field such as Id after the user-selected sort to make pagination consistent.
  • Provider-aware queries: Keep database queries as IQueryable<T> until paging and sorting have been applied, then materialize them with ToListAsync() or ToList().

Common Mistakes

Calling string OrderBy without Dynamic LINQ

Standard LINQ does not include an OrderBy(string) overload.

// Does not compile with standard System.Linq alone.
var sorted = people.OrderBy("Name");

Install and import a Dynamic LINQ implementation, then call it on IQueryable<T>:

using System.Linq.Dynamic.Core;

var sorted = people.AsQueryable().OrderBy("Name");

Expecting AsQueryable() to create a database query

var sorted = people.AsQueryable().OrderBy("Name");

If people is a List<Person>, sorting still happens in application memory. AsQueryable() does not send anything to a database.

Materializing a database query too early

// Sorting happens in memory after every row has been loaded.
var people = dbContext.People.ToList();
var sorted = people.AsQueryable().OrderBy("Name");

Prefer applying sorting before materializing:

var sorted = dbContext.People.OrderBy("Name").ToList();

Passing unvalidated user input directly

var sorted = people.AsQueryable().OrderBy(request.Query["sort"]);

Even when an expression parser restricts dangerous operations, arbitrary expressions can expose fields you did not intend to support or cause errors. Map external names to approved property names.

Misspelling a property name

var sorted = people.AsQueryable().OrderBy("Nmae");

String-based sorting loses compile-time member checking. Use constants, an allowlist, tests, or non-dynamic selectors when the set of fields is known.

Comparisons

ApproachExampleBest useCompile-time property checking
Strongly typed LINQOrderBy(p => p.Name)The sort field is known in codeYes
Dynamic LINQOrderBy("Name DESC")Sort field is selected at runtimeNo
IEnumerable<T>list.OrderBy(p => p.Name)Data already loaded in memoryYes, with typed selectors
IQueryable<T>db.People.OrderBy(...)Query providers such as ORMs and remote data sourcesDepends on expression/provider
AsQueryable() over a listlist.AsQueryable()Reusing an API that requires IQueryableNo additional database capability

When only a few sortable fields exist, a switch with strongly typed selectors is often clearer and safer than parsing a string:

IEnumerable<Person> sorted = sortBy switch
{
    "name" => people.OrderBy(person => person.Name),
    "age" => people.OrderByDescending(person => person.Age),
    _ => people.OrderBy(person => person.Name)
};
Take Quiz

Cheat Sheet

// Normal, strongly typed sorting
var sorted = people.OrderBy(person => person.Name);

// Dynamic LINQ sorting over an in-memory sequence
var sorted = people.AsQueryable().OrderBy("Name, Age DESC");

// Dynamic LINQ sorting over an existing provider query
IQueryable<Person> query = dbContext.People;
var sorted = query.OrderBy("Name DESC");
  • OrderBy("Name") means ascending order.
  • OrderBy("Name DESC") means descending order.
  • Separate multiple keys with commas: "Name, Age DESC".
  • Dynamic LINQ is an additional library; it is not part of standard LINQ.
  • AsQueryable() lets an in-memory IEnumerable<T> use APIs requiring IQueryable<T>.
  • AsQueryable() does not make a list database-backed or improve its performance.
  • Validate runtime sort fields with an allowlist.
  • Add a secondary unique sort key for stable paginated results.

FAQ

Can IEnumerable<T> use OrderBy("Name") directly?

Not with standard LINQ. A Dynamic LINQ library commonly provides the string overload for IQueryable, so use source.AsQueryable().OrderBy("Name") when the source is in memory.

Does AsQueryable() change where the query executes?

No. When the source is an in-memory collection, it remains an in-memory query.

Do I need Dynamic LINQ for runtime sorting?

No. If your application has a small fixed set of sort fields, a switch that chooses strongly typed OrderBy calls is often simpler and safer.

Can I sort by more than one property?

Yes. Use a comma-separated ordering string, for example "LastName, FirstName, CreatedAt DESC".

Is Dynamic LINQ safe with user-provided sort strings?

Treat user input as untrusted. Validate it against allowed sort fields or map public field names to known internal expressions before calling Dynamic LINQ.

Why does sorting fail at runtime when the property name is wrong?

Property names in a dynamic ordering string are not checked by the C# compiler. The parser can only report the mistake when the code runs.

Should I call ToList() before sorting a database query?

Usually no. Keep the data as IQueryable<T>, apply ordering and paging, then materialize the result. This lets the provider perform the work efficiently when it supports the expression.

Take Quiz

Mini Project

Description

Build an in-memory employee directory that can sort its results by a user-selected field. The project demonstrates how to convert an IEnumerable<Employee> to IQueryable<Employee> for Dynamic LINQ ordering while restricting sorting to approved fields.

Goal

Create a method that sorts employees by name, department, or salary, in ascending or descending order.

Requirements

  • Create an Employee type with Name, Department, and Salary properties.
  • Store several employees in an in-memory collection.
  • Accept a sort field and descending flag as method inputs.
  • Allow only name, department, and salary as sort fields.
  • Use Dynamic LINQ to apply the final ordering.
  • Display the sorted employees.
Take Quiz

Keep learning

Related questions

View more