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 } }
]
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" } } }
]
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" } } }
]
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.