1

I have a C# model which has nested list array and this is implemented as document into Mongo.

I want to return the data into the structure of the nested array, but I want to select the data based on a field on the parent and a field within the array.

I've tried dozens of combinations of ElemMatch and Project, but I'm completely lost now and not even sure if what I'm trying to do is possible using the driver.

public class Models
{
    public ObjectId _id { get; set; }
    public string ModelType{ get; set; }
    public List<ModelList> ModelList { get; set; } = new List<ModelList>();
}
public class ModelList
{
    public string ModelHashKey { get; set; }
    public string ModelName { get; set; }
    public string ModelAttribute { get; set; }
}

Data model shown above.

I would like to select all records from ModelList where: Models.ModelType = "Player" AND ModelList.ModelAttribute = "Male"

I want to return the data into the object List ModelList, even though this is an element of Models.

How would you go about solving this problem, or would you just select the data from the parent and then loop it in C#?

Many thanks,

Dave

2 Answers 2

1

So let's start from the drawing board and get some data in via the mongo console:

db.models.insertMany([
  {
     _id : 1,
     ModelType: "Player",
     ModelList: [
       { ModelHashKey: "1", ModelName: "1", ModelAttribute: "Male" },
       { ModelHashKey: "2", ModelName: "2", ModelAttribute: "Male" },
       { ModelHashKey: "3", ModelName: "3", ModelAttribute: "Female" }
     ]
 },
 {
     _id : 2,
     ModelType: "NotPlayer",
     ModelList: [
       { ModelHashKey: "4", ModelName: "4", ModelAttribute: "Male" },
       { ModelHashKey: "5", ModelName: "5", ModelAttribute: "Male" },
       { ModelHashKey: "6", ModelName: "6", ModelAttribute: "Female" }
     ]
 }
]);

from the question I'm assuming you want to only select the document with the _id of 1 and also filter the list of ModelLiet to the 2 Males (1, 2).

So let's start by filting down the documents, this can be done by a simple find:

db.models.find({"ModelType": "Player", "ModelList.ModelAttribute": "Male"}).pretty()

Note that we can add an index of {"ModelType" : 1, "ModelList.ModelAttribute": 1 } to also support this query.

However, if we execute this find we'll get the whole document back:

{
        "_id" : 1,
        "ModelType" : "Player",
        "ModelList" : [
                {
                        "ModelHashKey" : "1",
                        "ModelName" : "1",
                        "ModelAttribute" : "Male"
                },
                {
                        "ModelHashKey" : "2",
                        "ModelName" : "2",
                        "ModelAttribute" : "Male"
                },
                {
                        "ModelHashKey" : "3",
                        "ModelName" : "3",
                        "ModelAttribute" : "Female"
                }
        ]
}

So here we want to flip this into an aggregation query and project the data with a filter.

db.models.aggregate([
  { $match: {"ModelType": "Player", "ModelList.ModelAttribute": "Male" } },
  { $addFields: { "ModelList" : {
            $filter: {
               input: "$ModelList",
               as: "item",
               cond: { $eq: [ "$$item.ModelAttribute", "Male" ] }
           }
        }
    }
  } 
]);

If we execute the above we get the expected results back (try it here - https://mongoplayground.net/p/j6bP9TE6aTD):

{
        "_id" : 1,
        "ModelType" : "Player",
        "ModelList" : [
                {
                        "ModelHashKey" : "1",
                        "ModelName" : "1",
                        "ModelAttribute" : "Male"
                },
                {
                        "ModelHashKey" : "2",
                        "ModelName" : "2",
                        "ModelAttribute" : "Male"
                }
        ]
}

So let's covert this to C# using the MongoDB Driver, it's all fairly similar just bit different syntax:

public class Models
{
    public int _id { get; set; }
    public string ModelType { get; set; }
    public List<ModelList> ModelList { get; set; } = new List<ModelList>();
}

public class ModelList
{
    public string ModelHashKey { get; set; }
    public string ModelName { get; set; }
    public string ModelAttribute { get; set; }
}

public class Program
{
    public static void Main(string[] args)
    {
        var client = new MongoClient();
        var db = client.GetDatabase("test");
        var collection = db.GetCollection<Models>("models");

        var models = collection.Aggregate()
            .Match(Builders<Models>.Filter.Eq(x => x.ModelType, "Player") & Builders<Models>.Filter.ElemMatch(x => x.ModelList, Builders<ModelList>.Filter.Eq(x => x.ModelAttribute, "Male")))
            .AppendStage<Models>(BsonDocument.Parse(@"{ $addFields: { ""ModelList"" : { $filter: { input: ""$ModelList"", as: ""item"", cond: { $eq: [""$$item.ModelAttribute"", ""Male""] } } } } }"))
            .ToList();

        foreach (var model in models)
        {
            foreach (var item in model.ModelList)
            {
                Console.WriteLine(item.ToJson());
            }
        }
    }
}

Notice that we're using AppendStage<> as the C# driver doesn't support this aggregation stage natively yet.

Now if we run this C# code we'll get the following results:

{ "ModelHashKey" : "1", "ModelName" : "1", "ModelAttribute" : "Male" }
{ "ModelHashKey" : "2", "ModelName" : "2", "ModelAttribute" : "Male" }
Sign up to request clarification or add additional context in comments.

Comments

1

if you don't mind storing the ModelList items in it's own collection, this is my solution using MongoDB.Entities that doesn't use any magic strings.

using System;
using MongoDB.Entities;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using System.Linq;

public class Models : Entity
{
    public string ModelType { get; set; }
    public Many<ModelList> ModlList { get; set; }

    public Models() => this.InitOneToMany(() => ModlList);
}

public class ModelList : Entity
{
    public string ModelHashKey { get; set; }
    public string ModelName { get; set; }
    public string ModelAttribute { get; set; }
    public One<Models> Parent { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        new DB("test");

        var parent = new Models { ModelType = "Player" };
        parent.Save();

        var ml1 = new ModelList
        {
            ModelAttribute = "Male",
            ModelName = "i am one",
            ModelHashKey = "secret",
            Parent = parent.ToReference()
        };
        ml1.Save();

        var ml2 = new ModelList
        {
            ModelAttribute = "Female",
            ModelName = "i am two",
            ModelHashKey = "secret",
            Parent = parent.ToReference()
        };
        ml2.Save();

        parent.ModlList.Add(ml1);
        parent.ModlList.Add(ml2);

        var result = (from m in DB.Collection<Models>()
                      where m.ModelType == "Player"
                      join l in DB.Collection<ModelList>() on m.ID equals l.Parent.ID into lists
                      from ml in lists
                      select ml).Where(l => l.ModelAttribute == "Male");

        var modellists = result.ToArray();

        Console.Write(modellists.First().ModelName);
        Console.ReadKey();
    }
}

resulting mongodb aggregate:

aggregate([{ 
"$match" : 
    { "ModelType" : "Player" } }, 
    { "$lookup" : {"from" : "ModelLists", 
                   "localField" : "_id", 
                   "foreignField" : "Parent.ID", "as" : "lists" } }, 
                    { "$unwind" : "$lists" }, 
                    { "$project" : { "lists" : "$lists", "_id" : 0 } }, 
                    { "$match" : { "lists.ModelAttribute" : "Male" } }])

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.