LINQ (Language Integrated Query) is a set of language features and library methods that allow you to query and transform data using a consistent syntax regardless of the data source — whether it's an in-memory collection, a database, XML, or a web service.
Query syntax vs method syntax
LINQ can be written in two ways. Query syntax resembles SQL and is transformed by the compiler into method syntax at compile time. They are equivalent and can be mixed.
// Query syntax
var results = from c in customers
where c.City == "London"
orderby c.Name
select c.Name;
// Method syntax (what the compiler generates)
var results = customers
.Where(c => c.City == "London")
.OrderBy(c => c.Name)
.Select(c => c.Name);Most developers prefer method syntax for simple queries and query syntax when join, let, or group by makes the intent clearer.
Deferred execution
Most LINQ methods do not execute immediately. They build up a pipeline that only runs when the results are actually consumed — typically by foreach, ToList(), ToArray(), Count(), or similar.
var query = orders.Where(o => o.Total > 100); // Nothing happens yet
var list = query.ToList(); // NOW the filter runsThis means the data source is read at the point of consumption, not at the point of definition. If the underlying data changes between defining and consuming the query, the results reflect the newer state.
Methods that return a single value (First, Count, Sum, Any) or a collection (ToList, ToArray, ToDictionary) execute immediately.
IEnumerable vs IQueryable
This is the key distinction that trips people up.
IEnumerable<T> is used by LINQ to Objects. Each method like Where receives a Func<T, bool> — a compiled delegate — and runs it in-memory against each element. The filtering happens in your process.
IQueryable<T> is used by LINQ providers like Entity Framework. Each method like Where receives an Expression<Func<T, bool>> — an expression tree — which the provider translates into something else entirely, such as SQL. The filtering happens on the server.
// LINQ to Objects — filter runs in your process
IEnumerable<Order> expensive = orders.Where(o => o.Total > 100);
// LINQ to Entities — filter is translated to SQL and runs on the database
IQueryable<Order> expensive = db.Orders.Where(o => o.Total > 100);The lambda o => o.Total > 100 looks identical in both cases but is compiled into completely different things depending on the target type.
Expression trees
An expression tree is a data structure that represents code as a tree of nodes rather than as executable instructions. Each node represents an operation — a method call, a comparison, a property access, a constant, and so on.
When you assign a lambda to Expression<Func<...>> instead of Func<...> the compiler does not compile the lambda into IL. Instead it emits code that builds a tree of Expression objects describing what the lambda does.
// Compiled to a delegate — executable code
Func<int, bool> func = x => x > 5;
// Compiled to a tree — inspectable data
Expression<Func<int, bool>> expr = x => x > 5;The expression tree for x => x > 5 looks roughly like:
GreaterThan
/ \
Parameter Constant
(x) (5)A LINQ provider like Entity Framework walks this tree and translates it. GreaterThan becomes >, Parameter becomes a column reference, Constant becomes a SQL literal, and the whole thing becomes part of a WHERE clause.
Why this matters
The most common mistake is accidentally pulling an entire database table into memory by using IEnumerable<T> when you meant IQueryable<T>:
// BAD — fetches ALL orders then filters in memory
IEnumerable<Order> orders = db.Orders;
var expensive = orders.Where(o => o.Total > 100);
// GOOD — filter is translated to SQL
IQueryable<Order> orders = db.Orders;
var expensive = orders.Where(o => o.Total > 100);This happens because once you have an IEnumerable<T>, LINQ resolves to the Enumerable.Where extension method which takes a Func and runs in-process. The database has no choice but to return every row.
Similarly, calling .AsEnumerable() or .ToList() in the middle of a query chain forces everything after that point to run in memory.
Building expression trees manually
Expression trees can also be built by hand. This is useful in libraries or code generators that need to construct queries dynamically — for example, building a search filter from user input.
var param = Expression.Parameter(typeof(Order), "o");
var property = Expression.Property(param, "Total");
var constant = Expression.Constant(100m);
var comparison = Expression.GreaterThan(property, constant);
// Equivalent to: o => o.Total > 100
var lambda = Expression.Lambda<Func<Order, bool>>(comparison, param);
var results = db.Orders.Where(lambda);This is verbose but powerful — it allows filters, sort orders, and projections to be assembled at runtime while still being translated to efficient SQL by the provider.