Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 18

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.

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.

You might also like