Projection of mongodb subdocument using C# .NET driver 2.0

36,061

Solution 1

As Avish said, you have to use the aggregation API to get the resulting document to look like you are wanting. However, the driver can make some of that disappear for you if you use the expression tree API for project as you have done for Find. For instance, I believe the following should work for you:

var taskNames = await Categores.Find(x => x.CategoryName == catName)
    .Project(x => x.Tasks.Select(y => y.Name))
    .ToListAsync();

This should just bring back an enumerable of strings (tasks.name) for each category. The driver will be inspecting this projection and only pull back the tasks.name field.

Solution 2

MongoDB doesn't really support projections the way SQL databases do; you can ask for a partial document, but you'd still get back something that matches the schema of the document you were querying. In your case, you're getting back only the tasks field, and for each task, only the name field.

You can easily transform this into a list of strings using plain LINQ:

var categoryTasks = Categories.Find<Category>(x => x.CategoryName == catName)
                     .Project(Builders<Category>.Projection
                                                .Include("tasks.name")
                                                .Exclude("_id"))
                     .ToListAsync()
                     .Result;   

var taskNames = categoryTasks.Tasks.Select(task => task.Name).ToList();

Alternatively, you can do some fancy stuff with the aggregations API (which does support custom projections, kinda), but that would probably be overkill for you case.

Share:
36,061
Lynchy
Author by

Lynchy

Updated on January 23, 2020

Comments

  • Lynchy
    Lynchy over 4 years

    I have the following structure:

    public class Category
    {
        [BsonElement("name")]
        public string CategoryName { get; set; }
    
        [BsonDateTimeOptions]
        [BsonElement("dateCreated")]
        public DateTime DateStamp { get; set; }
    
        [BsonElement("tasks")]        
        public List<TaskTracker.Task> Task { get; set; }
    }
    
    public class Task
    {
        [BsonElement("name")]
        public string TaskName { get; set; }
    
        [BsonElement("body")]
        public string TaskBody { get; set; }
    }
    

    I am trying to query a Category to get all the TaskName values and then return them to a list to be displayed in a list box.

    I have tried using this query:

    var getTasks = Categories.Find<Category>(x => x.CategoryName == catName)
                             .Project(Builders<Category>.Projection
                                                        .Include("tasks.name")
                                                        .Exclude("_id"))
                             .ToListAsync()
                             .Result;   
    

    But what get returned is: {"tasks": [{"name: "test"}]}.

    Is there anyway to just return the string value?

  • Lynchy
    Lynchy about 9 years
    I have tried your query and have tried adding it to a List<IEnumerable<string>> but what gets returned is: '{System.Linq.Enumerable.WhereSelectEnumerableIterator<Mongo‌​DB.Driver.Linq.Trans‌​lators.ProjectedObje‌​ct,string>}'
  • Craig Wilson
    Craig Wilson about 9 years
    Yes, that is convertible to an IEnumerable<string>. Could you provide some more code about what you are doing?
  • Lynchy
    Lynchy about 9 years
    I think I have found a solution. I returned taskNames query like this: return taskNames[0].ToList(); and that seemed it work. Is this good practice?
  • Craig Wilson
    Craig Wilson about 9 years
    Sure... at his point, all the data is in memory, so do with it what you want.
  • Admin
    Admin over 8 years
    @CraigWilson So, there is no way of doing projections on the MongoDB server before the result set is retrieved?
  • Craig Wilson
    Craig Wilson over 8 years
    There is. This answer demonstrates exactly that. It will only bring back the "tasks.name" field.
  • Alex 75
    Alex 75 almost 5 years
    Can I force the query to return Distinct ? (I want to do it IN the query, not using .Result.Distinct())