The other answers are close, but not completely correct.
I assume that _db
is an Entity Framework DbContext
, and _db.Posts
is a DbSet<Post>
.
As such the .FirstOrDefault()
method you are seeing is actually an Extension method and the x => x.Key == key
part is an Expression tree.
What happens behind the scenes is that the call to _db.Posts.FirstOrDefault(x => x.Key == key)
is translated to a SQL statement like SELECT TOP(1) Key, Content, ... FROM posts WHERE Key = @key
, the result of which is mapped into a Post
entity.
There are a lot of language features at play to make all this work, so let's have a look!
Extension methods
Extension methods are static methods, but can be called like instance methods.
They are defined in static classes and have a 'receiver' argument. In the case of FirstOrDefault
the extension method looks like this:
public static class Queryable {
public static T FirstOrDefault<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate = null) {
// do something with source and predicate and return something as a result
}
}
It's usage _db.Posts.FirstOrDefault(...)
is actually syntactic sugar and will be translated by the C# compiler to a static method call a la Queryable.FirstOrDefault(_db.Posts, ...)
.
Note that extension methods are, despite the syntactic sugar, still static methods do not have have access to their receiver's internal state. They can only access public members.
Delegates
C# has support for pseudo-first-class functions, called delegates. There are several ways to instantiate a delegate.
They can be used to capture
existing methods or they can be initialized with an anonymous function.
The most elegant way to initialize a delegate with an anonymous function is to use lambda style functions like x => x + 10
or (x, y) => x + y
.
The reason you don't see type annotations in these examples is that the compiler can infer the types of the arguments in many common situations.
Here is another example:
// This is a normal function
bool IsEven(int x) {
return x % 2 == 0;
}
// This is an anonymous function captured in a delegate of type `Func<T1, TResult>`
Func<int, bool> isEven = x => x % 2 == 0;
// You can also capture methods in delegates
Func<int, bool> isEven = IsEven;
// Methods can be called
int a = IsEven(5); // result is false
// Delegates can be called as well
int b = isEven(4); // result is true
// The power of delegates comes from being able to pass them around as arguments
List<int> Filter(IEnumerable<int> array, Func<int, bool> predicate) {
var result = new List<int>();
foreach (var n in array) {
if (predicate(n)) {
result.Add(n);
}
}
return result;
}
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
var evenNumbers = Filter(numbers, isEven); // result is a list of { 2, 4, 6 }
var numbersGt4 = Filter(numbers, x => x > 4); // result is a list of { 5, 6 }
Expression trees
The C# compiler has a feature that allows you to create an Expression tree with normal-looking code.
For example Expression<Func<int, int>> add10Expr = (x => x + 10);
will initialize add10Expr
not with an actual function but with an expression tree, which is an object graph.
Initialized by hand it would look like this:
Expression xParameter = Expression.Parameter(typeof(int), "x");
Expression<Func<int, int>> add10Expr =
Expression.Lambda<Func<int, int>>(
Expression.Add(
xParameter,
Expression.Constant(10)
),
xParameter
);
(which is super cumbersome)
The power of expression trees comes from being able to create, inspect and transform them at runtime.
Which is what Entity Framework does: it translates these C# expression trees to SQL code.
Entity Framework
With all of these features together you can write predicates and other code in C# which gets translated by Entity Framework to SQL, the results of which are "materialized" as normal C# objects.
You can write complex queries to the database all within the comfort of C#.
And best of all, your code is statically typed.