βœ‚οΈProject

Include and Exclude

You can use a projection stage into your pipeline to either exclude existing fields or add new ones. To add a projection stage use a ProjectionDefinitionBuilder<T> and start excluding or including fields. The end result is a instance of ProjectionDefinition<T>. Finally, use it after the Find method since the IFindFluent interface allows you to chain multiple methods calls.

// Create a ProjectDefinition<T>

var definition = Builders<T>.Projection
    .Exclude(doc => doc.<field1>)
    .Include(doc => doc.<field2>)
    ...
    .Include(doc => doc.<fieldN>)
    
// Get results
var simpleProjectionResults = await usersCollection
    .Find(Builders<User>.Filter.Empty)
    .Project(simpleProjection) // projection stage
    .ToListAsync();

The sample uses a projection stage to re-format User documents by excluding the Id field and only include the Gender and DateOfBirth.

Projection.cs
var collection = database
    .GetCollection<User>(Constants.UsersCollection);

// exclude id, return only gender and date of birth
var simpleProjection = Builders<User>.Projection
    .Exclude(u => u.Id)
    .Include(u => u.Gender)
    .Include(u => u.DateOfBirth);

var simpleProjectionResults = await usersCollection
    .Find(Builders<User>.Filter.Empty)
    .Project(simpleProjection)
    .ToListAsync();

While you don't have to explicitly exclude all the fields you don't want to include in the final result, you do have to do it for the identifier field _id, otherwise it will be included

Include custom fields

You can use projection to include new custom calculated fields in the final result. The sample uses a projection stage to re-format User documents and project the following new fields:

  1. fullName: equal to firstName and lastName concatenated

  2. fullNameUpper: equal to fullName in upper case

  3. gender: equal to the string value of the Gender enum

  4. age: equal to current year minus the dateOfBirth's field year

Projection.cs
var collection = database
    .GetCollection<User>(Constants.UsersCollection);

var customProjection = Builders<User>.Projection.Expression(u =>
    new
    {
        fullName = $"{u.FirstName} {u.LastName}",
        fullNameUpper = ToUpperCase($"{u.FirstName} {u.LastName}"),
        gender = u.Gender.ToString(),
        age = DateTime.Today.Year - u.DateOfBirth.Year
    });

var results = await collection.Find(Builders<User>.Filter.Empty)
    .Project(customProjection)
    .ToListAsync();

// private method    
private string ToUpperCase(string value)
{
    return value.ToUpper();
}

The driver didn't build any complex query to the MongoDB database, instead it created a projection with the fields used in the projection stage and their values used to build the new anonymous result for each document retrieved

Projection with LINQ

If you want the driver to create the exact query that matches your projection you can build it using an IMongoQueryable<T> reference. There are some limitations though: you cannot use any custom C# functions you want as you did using a ProjectDefinition<T>, but only those functions that are supported by the driver. You can get an IMongoQueryable<T> reference by calling the AsQueryable method on a IMongoCollection<T> reference.

IMongoCollection<T>
    .AsQueryable()
Projection.cs
// get an IMongoQueryable<T> reference
var usersQueryableCollection = personsDatabase
    .GetCollection<User>(Constants.UsersCollection)
    .AsQueryable();

// 'select new' creates a projection stage
var linqSimpleProjection = 
    from u in usersQueryableCollection
    select new
    {
       fullName = u.FirstName + " " + u.LastName,
       gender = u.Gender == Gender.Male ? "Male" : "Female",
       age = DateTime.Now.Year - u.DateOfBirth.Year
    };

var linqSimpleProjectionResults = 
    await linqSimpleProjection.ToListAsync();

This time the driver did built the exact query defined in the projection stage but if you try to use a custom C# function despite the fact it compiles you will get a runtime error.

var linqSimpleProjection = 
    from u in usersQueryableCollection
   select new
   {
       fullName = u.FirstName + " " + u.LastName,
       fullNameUpper = // this will cause an exception
           ToUpperCase($"{u.FirstName} {u.LastName}"),
       gender = u.Gender == Gender.Male ? "Male" : "Female",
       age = DateTime.Now.Year - u.DateOfBirth.Year
   };

Exception: System.NotSupportedException: ToUpperCase of type MongoDb.Csharp.Samples.Aggregation.ProjectionStage is not supported in the expression tree value(MongoDb.Csharp.Samples.Aggregation.ProjectionStage).ToUpperCase(Format("{0} {1}", {document}{firstName}, {document}{lastName})).

Last updated