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 of Single 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.