✂️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.
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();var bsonCollection = database
.GetCollection<BsonDocument>(Constants.UsersCollection);
var bsonSimpleProjection = Builders<BsonDocument>.Projection
.Exclude("_id")
.Include("gender")
.Include("dateOfBirth");
var bsonSimpleProjectionResults = await bsonCollection
.Find(Builders<BsonDocument>.Filter.Empty)
.Project(bsonSimpleProjection)
.ToListAsync();db.users.aggregate()
.project({_id: 0, gender: 1, dateOfBirth: 1})
---------------------------
// sample result
{
"gender" : 0,
"dateOfBirth" : ISODate("1972-04-08T20:56:03.950+02:00")
}public class User
{
[BsonId]
[BsonIgnoreIfDefault] // required for replace documents
public ObjectId Id { get; set; }
public Gender Gender { get; set; }
public string FirstName {get; set; }
public string LastName {get; set; }
public string UserName {get; set; }
public string Avatar {get; set; }
public string Email {get; set; }
public DateTime DateOfBirth {get; set; }
public AddressCard Address {get; set; }
public string Phone {get; set; }
[BsonIgnoreIfDefault]
public string Website {get; set; }
public CompanyCard Company {get; set; }
public decimal Salary { get; set; }
public int MonthlyExpenses { get; set; }
public List<string> FavoriteSports { get; set; }
public string Profession { get; set; }
}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:
fullName: equal to firstName and lastName concatenated
fullNameUpper: equal to fullName in upper case
gender: equal to the string value of the Gender enum
age: equal to current year minus the dateOfBirth's field year
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();
}// sample result
{
"fullName": "Calvin Lindgren",
"fullNameUpper": "CALVIN LINDGREN",
"gender": "Male",
"age": 21
}public class User
{
[BsonId]
[BsonIgnoreIfDefault] // required for replace documents
public ObjectId Id { get; set; }
public Gender Gender { get; set; }
public string FirstName {get; set; }
public string LastName {get; set; }
public string UserName {get; set; }
public string Avatar {get; set; }
public string Email {get; set; }
public DateTime DateOfBirth {get; set; }
public AddressCard Address {get; set; }
public string Phone {get; set; }
[BsonIgnoreIfDefault]
public string Website {get; set; }
public CompanyCard Company {get; set; }
public decimal Salary { get; set; }
public int MonthlyExpenses { get; set; }
public List<string> FavoriteSports { get; set; }
public string Profession { get; set; }
}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()// 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();// query created by the driver
db.users.aggregate([
{ "$project" :
{
"lastName" : { "$concat" : ["$firstName", " ", "$lastName"] },
"gender" : {
"$cond" : [{ "$eq" : ["$gender", 0] }, "Male", "Female"]
},
"age" : { "$subtract" : [2020, { "$year" : "$dateOfBirth" }] },
"_id" : 0
}
}])
---------------------
// sample results
/* 1 */
{
"lastName" : "Natasha Predovic",
"gender" : "Female",
"age" : 46
},
/* 2 */
{
"lastName" : "Natasha Dooley",
"gender" : "Female",
"age" : 45
}
public class User
{
[BsonId]
[BsonIgnoreIfDefault] // required for replace documents
public ObjectId Id { get; set; }
public Gender Gender { get; set; }
public string FirstName {get; set; }
public string LastName {get; set; }
public string UserName {get; set; }
public string Avatar {get; set; }
public string Email {get; set; }
public DateTime DateOfBirth {get; set; }
public AddressCard Address {get; set; }
public string Phone {get; set; }
[BsonIgnoreIfDefault]
public string Website {get; set; }
public CompanyCard Company {get; set; }
public decimal Salary { get; set; }
public int MonthlyExpenses { get; set; }
public List<string> FavoriteSports { get; set; }
public string Profession { get; set; }
}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