LINQ
The driver contains an implementation of LINQ that targets the aggregation framework. The aggregation framework holds a rich query language that maps very easily from a LINQ expression tree making it straightforward to understand the translation from a LINQ statement into an aggregation framework pipeline. To see a more complicated uses of LINQ from the driver, see the AggregationSample source code.
For the rest of this page, we’ll use the following class:
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public IEnumerable<Pet> Pets { get; set; }
public int[] FavoriteNumbers { get; set; }
public HashSet<string> FavoriteNames { get; set; }
public DateTime CreatedAtUtc { get; set; }
}
class Pet
{
public string Name { get; set; }
}
Queryable
Hooking into the LINQ provider requires getting access to an IQueryable
instance. The driver provides an AsQueryable
extension method on IMongoCollection
.
var collection = db.GetCollection<Person>("people");
var queryable = collection.AsQueryable();
Once you have an IQueryable
instance, you can begin to compose a query:
var query = from p in collection.AsQueryable()
where p.Age > 21
select new { p.Name, p.Age };
// or, using method syntax
var query = collection.AsQueryable()
.Where(p => p.Age > 21)
.Select(p => new { p.Name, p.Age });
… which maps to the following aggregation framework pipeline:
[
{ $match: { Age: { $gt: 21 } } },
{ $project: { Name: 1, Age: 1, _id: 0 } }
]
Also, there is a list of aggregate options that can be applied for the whole pipeline via AggregationOptions
. Below is an example of configuring a Collation
:
var collation = new Collation("en_US", strength: CollationStrength.Secondary);
var aggregateOptions = new AggregateOptions { Collation = collation };
var query = collection
.AsQueryable(aggregateOptions)
.Where(p => p.Name == "tom");
Stages
We’ll walk through the supported stages below:
$project
To generate a $project
stage, use the Select
method. To see the list of expressions supported in the Select
method, see Aggregation Projections.
var query = from p in collection.AsQueryable()
select new { p.Name, p.Age };
// or
var query = collection.AsQueryable()
.Select(p => new { p.Name, p.Age });
[
{ $project: { Name: 1, Age: 1, _id: 0 } }
]
Note
When projecting scalars, the driver will wrap the scalar into a document with a generated field name because MongoDB requires that output from an aggregation pipeline be documents.
var query = from p in collection.AsQueryable()
select p.Name;
var query = collection.AsQueryable()
.Select(p => p.Name);
[
{ $project: { __fld0: "$Name", _id: 0 } }
]
The driver will know how to read the field out and transform the results properly.
Note
By default, MongoDB will include the _id
field in the output unless explicitly excluded. The driver will automatically add this exclusion when necessary.
$match
The Where
method is used to generate a $match
stage. To see the list of expressions supported inside a Where
, see Filters.
var query = from p in collection.AsQueryable()
where p.Age > 21
select p;
// or
var query = collection.AsQueryable()
.Where(p => p.Age > 21);
[
{ $match: { Age: { $gt: 21 } } }
]
$redact
The $redact
stage is not currently supported using LINQ.
$limit
The Take
method is used to generate a $limit
stage.
var query = collection.AsQueryable().Take(10);
[
{ $limit: 10 }
]
$skip
The Skip
method is used to generate a $skip
stage.
var query = collection.AsQueryable().Skip(10);
[
{ $skip: 10 }
]
$unwind
The SelectMany
method is used to generate an $unwind
stage. In addition, because of how $unwind
works, a $project
stage will also be rendered. To see the list of expressions supported in the SelectMany
method, see Aggregation Projections.
var query = from p in collection.AsQueryable()
from pet in p.Pets
select pet;
// or
var query = collection.AsQueryable()
.SelectMany(p => p.Pets);
[
{ $unwind: "$Pets" }
{ $project: { Pets: 1, _id: 0 } }
]
var query = from p in collection.AsQueryable()
from pet in p.Pets
select new { Name = pet.Name, Age = p.Age};
// or
var query = collection.AsQueryable()
.SelectMany(p => p.Pets, (p, pet) => new { Name = pet.Name, Age = p.Age});
[
{ $unwind: "$Pets" }
{ $project: { Name: "$Pets.Name", Age: "$Age", _id: 0 } }
]
$group
The GroupBy
method is used to generate a $group
stage. In general, the GroupBy
method will be followed by the Select
containing the accumulators although that isn’t required. To see the list of supported accumulators, see Accumulators.
var query = from p in collection.AsQueryable()
group p by p.Name into g
select new { Name = g.Key, Count = g.Count() };
//or
var query = collection.AsQueryable()
.GroupBy(p => p.Name)
.Select(g => new { Name = g.Key, Count = g.Count() });
[
{ $group: { _id: "$Name", __agg0: { $sum: 1 } } },
{ $project: { Name: "$_id", Count: "$__agg0", _id: 0 } }
]
var query = collection.AsQueryable()
.GroupBy(p => p.Name, (k, s) => new { Name = k, Count = s.Count()});
[
{ $group: { _id: "$Name", Count: { $sum: 1 } } },
]
$sort
The OrderBy
and ThenBy
methods are used to generate a $sort
stage.
var query = from p in collection.AsQueryable()
orderby p.Name, p.Age descending
select p;
//or
var query = collection.AsQueryable()
.OrderBy(p => p.Name)
.ThenByDescending(p => p.Age);
[
{ $sort: { Name: 1, Age: -1 } },
]
$geoNear
The $geoNear
stage is not currently supported using LINQ.
$out
The $out
stage is not currently supported using LINQ.
$lookup
The GroupJoin
method is used to generate a $lookup
stage.
This operator can take on many forms, many of which are not supported by MongoDB. Therefore, only 2 forms are supported.
First, you may project into most anything as long as it is supported by the $project
operator and you do not project the original collection variable. Below is an example of a valid query:
var query = from p in collection.AsQueryable()
join o in otherCollection on p.Name equals o.Key into joined
select new { p.Name, AgeSum: joined.Sum(x => x.Age) };
[
{ $lookup: { from: "other_collection", localField: 'Name', foreignField: 'Key', as: 'joined' } }",
{ $project: { Name: "$Name", AgeSum: { $sum: "$joined.Age" }, _id: 0 } }
]
Second, you may project into a type with two constructor parameters, the first being the collection variable and the second being the joined variable. Below is an example of this:
var query = from p in collection.AsQueryable()
join o in otherCollection on p.Name equals o.Key into joined
select new { p, joined };
[
{ $lookup: { from: "other_collection", localField: 'Name', foreignField: 'Key', as: 'joined' } }"
]
Note
An anonymous type, as above, has a constructor with two parameters as required.Sometimes, the compiler will also generate this two-parameter anonymous type transparently. Below is an example of this with a custom projection:
var query = from p in collection.AsQueryable()
join o in otherCollection on p.Name equals o.Key into joined
from sub_o in joined.DefaultIfEmpty()
select new { p.Name, sub_o.Age };
[
{ $lookup: { from: "other_collection", localField: 'Name', foreignField: 'Key', as: 'joined' } }",
{ $unwind: "$joined" },
{ $project: { Name: "$Name", Age: "$joined.Age", _id: 0 }}
]
Supported Methods
The method examples are shown in isolation, but they can be used and combined with all the other methods as well. You can view the tests for each of these methods in the MongoQueryableTests.
Any
All forms of Any
are supported.
var result = collection.AsQueryable().Any();
[
{ $limit: 1 }
]
var result = collection.AsQueryable().Any(p => p.Age > 21);
[
{ $match: { Age: { $gt: 21 } },
{ $limit: 1 }
]
Note
Any
has a boolean return type. Since MongoDB doesn’t support this, the driver will pull back at most 1 document. If one document was retrieved, then the result is true. Otherwise, it’s false.
Average
All forms of Average
are supported.
var result = collection.AsQueryable().Average(p => p.Age);
// or
var result = collection.AsQueryable().Select(p => p.Age).Average();
[
{ $group: { _id: 1, __result: { $avg: "$Age" } } }
]
Count and LongCount
All forms of Count
and LongCount
are supported.
var result = collection.AsQueryable().Count();
// or
var result = collection.AsQueryable().LongCount();
[
{ $group: { _id: 1, __result: { $sum: 1 } } }
]
var result = collection.AsQueryable().Count(p => p.Age > 21);
// or
var result = collection.AsQueryable().LongCount(p => p.Age > 21);
[
{ $match : { Age { $gt: 21 } } },
{ $group: { _id: 1, __result: { $sum: 1 } } }
]
Distinct
Distinct
without an equality comparer is supported.
var query = collection.AsQueryable().Distinct();
[
{ $group: { _id: "$$ROOT" } }
]
Using a distinct in isolation as shown above is non-sensical. Since each document in a collection contains a unique _id field, then there will be as many groups as their are documents. To properly use distinct, it should follow some form of a projection like $project or $group.
var query = collection.AsQueryable()
.Select(p => new { p.Name, p.Age })
.Distinct();
[
{ $group: { _id: { Name: "$Name", Age: "$Age" } } }
]
First and FirstOrDefault
All forms of First
and FirstOrDefault
are supported.
var result = collection.AsQueryable().First();
// or
var result = collection.AsQueryable().FirstOrDefault();
[
{ $limit: 1 }
]
var result = collection.AsQueryable().First(p => p.Age > 21);
// or
var result = collection.AsQueryable().FirstOrDefault(p => p.Age > 21);
[
{ $match : { Age { $gt: 21 } } },
{ $limit: 1 }
]
GroupBy
See $group
.
GroupJoin
See $lookup
.
Max
All forms of Max
are supported.
var result = collection.AsQueryable().Max(p => p.Age);
// or
var result = collection.AsQueryable().Select(p => p.Age).Max();
[
{ $group: { _id: 1, __result: { $max: "$Age" } } }
]
OfType
All forms of OfType
are supported.
// assuSumg Customer inherits from Person
var result = collection.AsQueryable().OfType<Customer>();
[
{ $match: { _t: "Customer" } }
]
Note
Based on configuration, the discriminator name_t
may be different as well as the value "Customer"
.
OrderBy, OrderByDescending, ThenBy, and ThenByDescending
See $sort
.
Select
See $project
.
SelectMany
See $unwind
.
Single and SingleOrDefault
All forms of Single
and SingleOrDefault
are supported.
var result = collection.AsQueryable().Single();
// or
var result = collection.AsQueryable().SingleOrDefault();
[
{ $limit: 2 }
]
var result = collection.AsQueryable().Single(p => p.Age > 21);
// or
var result = collection.AsQueryable().SingleOrDefault(p => p.Age > 21);
[
{ $match : { Age { $gt: 21 } } },
{ $limit: 2 }
]
Note
The limit here is 2 because the behavior ofSingle
is to throw when there is more than 1 result. Therefore, we pull back at most 2 documents and throw when 2 documents were retrieved. If this is not the behavior you wish, then First
is the other choice.
Skip
See $skip
.
Sum
All forms of Sum
are supported.
var result = collection.AsQueryable().Sum(p => p.Age);
// or
var result = collection.AsQueryable().Select(p => p.Age).Sum();
[
{ $group: { _id: 1, __result: { $sum: "$Age" } } }
]
Take
See $limit
.
Where
See $match
.