Expression Trees

Four different ways to express a filter, from a plain anonymous object to fully composable query trees.

The four forms

A filter can be an anonymous object, a LINQ expression, an ExpandoObject/dictionary, or a QueryField/QueryGroup tree — each trades some expressiveness for simplicity, or vice versa. The first three only support equality; the last supports every comparison operator.

Anonymous object

var customer = connection.Query<Customer>(new { Id = 10045 }).FirstOrDefault();

Or with multiple columns:

var customer = connection.Query<Customer>(new { FirstName = "John", LastName = "Doe" }).FirstOrDefault();

This is the simplest and most direct form, but the compiler doesn’t validate it against the entity model — a renamed column won’t trigger a compile error — and it only supports equality comparisons.

LINQ expressions

var customers = connection.Query<Customer>(e =>
    states.Contains(e.State) && e.IsActive == false);

This is the most common form for entity-model operations, though its parser is intentionally narrower than a full LINQ-to-SQL provider — complex expressions are where QueryGroup takes over.

ExpandoObject and Dictionary

var where = new Dictionary<string, object>
{
    { nameof(Customer.FirstName), "John" },
    { nameof(Customer.LastName), "Doe" }
};
var customer = connection.Query<Customer>(where).FirstOrDefault();

An ExpandoObject works the same way as an IDictionary<string, object> here — both are handy when the filter columns are only known at runtime. Like the anonymous-object form, they only support equality; reach for QueryField/QueryGroup for anything else.

QueryField and QueryGroup

var where = new[]
{
    new QueryField(nameof(Customer.State), Operation.In, states),
    new QueryField(nameof(Customer.IsActive), false),
    new QueryField(nameof(Customer.DateOfBirth), Operation.GreaterThanOrEqual, DateTime.Parse("1970-01-01"))
};
var customers = connection.Query<Customer>(where);

More verbose than a LINQ expression, but it’s the most powerful and explicit way to compose a filter, and the one to reach for once a LINQ expression gets awkward to write or maintain.