1

Filter the Collection in DB instead of Memory

I'm having a Model Class, Save it in a MongoDB Collection then Query the same as per my expectation mentioned below.

My Model Class:

public Class Employee
{
    public ObjectId Id { get; set; }
    public string EmpID { get; set; }
    public string EmpName { get; set; }
    public List<Mobile> EmpMobile { get; set; }
    public bool IsLive { get; set; }
}

public Class Mobile
{
    public string MobID { get; set; }
    public string MobNumber { get; set; }
    public bool IsPreferred { get; set; }
    public bool IsLive { get; set; }
}

The Values are

List<Employee> EmpInfo = new List<Employee>() {
new Employee()
{
    EmpID = "100",
    EmpName = "John",
    EmpMobile = new List<Mobile>()
    {
        new Mobile() { MobNumber = "55566610", IsPreferred = true, IsLive = false },
        new Mobile() { MobNumber = "55566611", IsPreferred = false, IsLive = true },
    },
    IsLive = true
},

new Employee()
{
    EmpID = "101",
    EmpName = "Peter",
    EmpMobile = new List<Mobile>()
    {
        new Mobile() { MobNumber = "55566610", IsPreferred = true, IsLive = false },
        new Mobile() { MobNumber = "55566611", IsPreferred = false, IsLive = false },
    },
    IsLive = true
},

new Employee()
{
    EmpID = "102",
    EmpName = "Jack",
    EmpMobile = new List<Mobile>()
    {
        new Mobile() { MobNumber = "55566610", IsPreferred = true, IsLive = true },
        new Mobile() { MobNumber = "55566611", IsPreferred = false, IsLive = true },
    },
    IsLive = false
}

}

collectionEmpInfo.InsertMany(EmpInfo);
var empList = collectionEmpInfo.Find(new BsonDocument()).ToList();

Now I wish to Select Only EmpInfo.IsLive == true inside the embedded document I need only EmpInfo.EmpMobile.IsLive == true satisfied Mobile documents

My Expected Output:

List<Employee> EmpInfo = new List<Employee>() {
new Employee()
{
    EmpID = "100",
    EmpName = "John",
    EmpMobile = new List<Mobile>()
    {
        new Mobile() { MobNumber = "55566611", IsPreferred = false, IsLive = true }
    },
    IsLive = true
},

new Employee()
{
    EmpID = "101",
    EmpName = "Peter",
    EmpMobile = new List<Mobile>()
    {

    },
    IsLive = true
}

}

Kindly assist me how to write a Where Clause Query for my expected output using c# MongoDB.

Note: Filter the Collection in DB instead of Memory

My MongoDB Libraries and Connections are

IMongoClient _client = new MongoClient();
IMongoDatabase _database = _client.GetDatabase("Test");
  • Is your question how to write the code that uses the client libraries to query MongoDB? If so, would you indicate which library are you using? Also, would you mind to expand with what you already tried and why it didn't work? – Alpha Jun 14 '16 at 00:48
  • @Alpha - I used IMongoDatabase and IMongoClient - I updated my post. Kindly refer and assist me. –  Jun 14 '16 at 00:52

3 Answers3

0

I think this would solve the problem that you have:

var collection = _database.GetCollection<Employee>("employee"); // (1)

var filterBuilder = Builders<BsonDocument>.Filter;
var filter = filterBuilder.Eq("IsLive", true) & filterBuilder.Eq("EmpMobile.IsLive", true); // (2)

var results = await collection.FindAsync(filter).ToListAsync(); // (3)

(1): You will need to change the collection name with the collection name that has the data you want to query. Also, notice that it requests a TDocument as the generic parameter. It seems that Employee does not inherit from TDocument, but you can do so or you can create another DTO class for this purpose.

(2): You can combine conditions by using the bit AND operator (&).

Also, you can query directly against internal values of an array (which you have in your classes as a list). As it turns out, it will return the document if any of the array values satisfies the condition. In your example, this should return the document for EmpIDs 100, but it will contain both of the MobileNumbers in it. You retrieved the document that satisfied the condition, but you retrieved the document in its entirety.

(3) Finally, rendering the results to a list so you have them in memory. Alternatively, you can walk through the results using cursor.MoveNextAsync(), but this will keep your connection to MongoDB open longer.

You can find most of the information with examples in the Find or Query Data with C# Driver of the MongoDB docs.

Alpha
  • 7,586
  • 8
  • 59
  • 92
  • It's returning empty list. –  Jun 14 '16 at 01:42
  • Try switching the "true" strings with true literals. I'm not entirely sure how types are matched, but if it's doing a JavaScript strict comparison, that should make a difference. – Alpha Jun 14 '16 at 01:47
  • I changed the filter to `filterBuilder.Eq("IsLive", true) & filterBuilder.Eq("EmpMobile.IsLive", true)`, now its returning only one document **`Emp_Id = 100`**. Kindly assist me with the expected output which is mentioned above. –  Jun 14 '16 at 01:51
  • @IRPunch Thanks for the feedback. As I explained, that is expected. You'll have to adjust the query logic to retrieve the right values and perform processing. For example, you could completely remove the second condition (EmpMobile.IsLive) and then filter them in memory. – Alpha Jun 14 '16 at 02:06
0

You have several int for your approach.

first you are using collectionEmpInfo.InsertOne(EmpInfo); i am assuming you want to use InsertMany instead.

as for filtering over the collection you have to know that your filter will affect if the whole object is retrieved, so adding a filter on an embedded array within an entity will not filter the array but determine if the whole object is retrieved or not based on whether it matches the query condition on the embedded array. My suggestion is to only apply the filter over the employees in the query and filter the result set in memory.

var filter = filterBuilder.Eq("IsLive", true);

var results = await collection.FindAsync(filter).ToListAsync();

now filter the results collection in memory as in

var filteredResults = results.ForEach(employee => employee.EmpMobile = employee.EmpMobile.Where(mob => mob.isLive).ToList());
sm_
  • 2,572
  • 2
  • 17
  • 34
0

EDIT

Added projection - so selected array contains only documents where IsLive==true

I think it is easier to use typed queries as c# is strongly typed language. I used ElemMatch as this is designed to scan an array and looks for a matching element.

var filterDef = new FilterDefinitionBuilder<Employee>();
var filter = filterDef.Eq(x => x.IsLive, true);

var projectDef = new ProjectionDefinitionBuilder<Employee>();
var projection = projectDef.ElemMatch<Mobile>("EmpMobile", "{IsLive:true}");            

var empList = collectionEmpInfo.Find(filter).Project<Employee>(projection).ToList();
profesor79
  • 9,213
  • 3
  • 31
  • 52
  • Its returning only **`EmpID = 100`**, not as like my requirement. can you please review the logic once. –  Jun 14 '16 at 08:36
  • OK - just to have good understanding as this can go different ways . 1. employee.IsLive ==true, then you want to project an array or filter and project? – profesor79 Jun 14 '16 at 08:38
  • @IRPunch added projection – profesor79 Jun 14 '16 at 09:03
  • Yes. first we have to check the `EmpInfo.IsLive == true`, if it is true then filter the inner collection with the condition `EmpInfo.EmpMobile.IsLive == true` (This condition is only for Inner collection not for the entire). –  Jun 14 '16 at 09:04
  • One more information. If I want to filter more than one inner collection means how could I do that. For example consider Email as like Mobile. How could I do both ? –  Jun 14 '16 at 09:13
  • 1
    method chaining: `empList = collectionEmpInfo.Find(filter).Project(projection).Project(anotherProjection).ToList();` – profesor79 Jun 14 '16 at 09:15