Question
Dynamic OrderBy Strings for IEnumerable and IQueryable in C#
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
Namelabel.
people.OrderBy(person => person.Name)
Dynamic LINQ receives the instructions as a note written at runtime:
Sort by
Name, then byAgefrom 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.
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:
Adam, age40Adam, age25Mina, age30
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:
-
peopleis anIEnumerable<Person>containing objects in memory. -
AsQueryable()returns anIQueryable<Person>view over that sequence. -
Dynamic LINQ parses
"Name, Age DESC". -
It creates ordering logic equivalent in intent to:
people.OrderBy(person => person.Name) .ThenByDescending(person => person.Age) -
ToList()enumerates the ordered query and creates a list. -
The final sequence is
Ana (35),Ana (19), thenZoe (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, orPricecolumn header. - REST APIs: An endpoint accepts a query parameter such as
?sort=name,-ageand 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.
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, orCreatedAt. - Early validation: Reject invalid sort fields before building the query.
- Stable secondary ordering: Add a unique field such as
Idafter 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 withToListAsync()orToList().
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
| Approach | Example | Best use | Compile-time property checking |
|---|---|---|---|
| Strongly typed LINQ | OrderBy(p => p.Name) | The sort field is known in code | Yes |
| Dynamic LINQ | OrderBy("Name DESC") | Sort field is selected at runtime | No |
IEnumerable<T> | list.OrderBy(p => p.Name) | Data already loaded in memory | Yes, with typed selectors |
IQueryable<T> | db.People.OrderBy(...) | Query providers such as ORMs and remote data sources | Depends on expression/provider |
AsQueryable() over a list | list.AsQueryable() | Reusing an API that requires IQueryable | No 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)
};
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-memoryIEnumerable<T>use APIs requiringIQueryable<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.
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
Employeetype withName,Department, andSalaryproperties. - Store several employees in an in-memory collection.
- Accept a sort field and descending flag as method inputs.
- Allow only
name,department, andsalaryas sort fields. - Use Dynamic LINQ to apply the final ordering.
- Display the sorted employees.
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
Best Way to Repeat a Character in C#: Building Repeated Strings Efficiently
Learn the best way to repeat a character in C#, compare StringBuilder, string concatenation, and simpler built-in options.
C# Access Modifiers and static: public, private, protected, and Defaults
Learn how C# public, private, protected, and default access control visibility, and how static differs from instance members.