Sahil Sharma
Sahil Sharma

Reputation: 1899

MongoDb C# Driver with Group and Project using Aggregate() query giving exception

I'm working on application that uses MongoDb as the database and .Net Core 3.0 as the framework. To fetch data from the database, I have created a DbContext class and using the Aggregation() feature of MongoDb. I'm not able to pass the appropriate projection. Following is the code for DbContext.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using Microsoft.Extensions.Options;
    using MongoDB.Driver;

    namespace Test.DbContext
    {
        /// <summary>
        /// Standard CRUD Operations with MongoDb  
        /// </summary>
        public class MongoDbContext   
        {
            #region Properties
            private readonly IMongoClient _mongoDbClient = null;

            private readonly IMongoDatabase _mongoDb = null;
            #endregion

            #region Constructor
            public MongoDbContext(IOptions<MongoSetting> mongoConfigs)
            {
                _mongoDbClient = new MongoClient(mongoConfigs.Value.ConnectionString);
                _mongoDb = _mongoDbClient.GetDatabase(mongoConfigs.Value.DatabaseName);
            }
            #endregion

            #region Grouping
            public IList<TProjection> GroupBy<TDocument, TGroupKey, TProjection> 
            (FilterDefinition<TDocument> filter, 
             Expression<Func<TDocument, TGroupKey>> selector, 
             Expression<Func<IGrouping<TGroupKey, TDocument>, TProjection>> projection){
                   return _mongoDb.GetCollection<TDocument>("collectionName").Aggregate().Match(filter).Group(selector, projection).ToList();
               }   
            #endregion
        }
    }

To call the function GroupBy(), I have to pass the filter, selector and projection but I'm not able to build the appropriate expression. Following are the data model and calling function:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.Extensions.Options;
using MongoDB.Driver;

namespace Test 
{
    [BsonIgnoreExtraElements]
    public class Employee   
    {
        [BsonId]
        [BsonElement("_id")]
        [BsonRepresentation(BsonType.ObjectId)]
        public ObjectId Id { get; set; }

        [BsonElement("type")]
        [JsonProperty("type")]
        public string Type { get; set; }

        [BsonElement("id")]
        public string CustomerId { get; set; }

        [BsonElement("name")]
        public string CustomerName { get; set; }
    }
}

I'm calling the dbContext in Customer Repository as:

using System;
using System.Linq.Expressions;
using MongoDB.Driver;

namespace Test.Repositories
{
    public class CustomerRepository : ICustomerRepository
    {
        #region Properties
        private readonly IMongoDbContext _dbContext = null;
        #endregion

        #region Constructor
        public CustomerRepository(IMongoDbContext dbContext)
        {
            _dbContext = dbContext;
        }
        #endregion

        #region Methods
        public EmployeeCollection GetSpecificData()
        {

            Expression<Func<Employee, dynamic>> filter = x => x.Employee.CustomerId == "11";
            Expression<Func<Employee, dynamic>> selector = x => new { typeName = x.Employee.Type };
            Expression<Func<IGrouping<dynamic, Employee>, dynamic>> projection = x => new
            {
                Key = x.Key,
                count = x.Count(),
                avgValue = x.Average(x => Convert.ToInt32(x.Employee.CustomerId))
            };


            var result = _dbContext.GroupBy<Employee, dynamic, dynamic>(filter, selector, projection);

            // Giving exception
            // "Value type of serializer is <>f__AnonymousType0`1[[System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, 
            //PublicKeyToken=7cec85d7bea7798e]] and does not match member type System.Object. (Parameter 'serializer')"
        }
        #endregion
    }
}

Exception:

"Value type of serializer is <>f__AnonymousType0`1[[System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]] and does not match member type System.Object. (Parameter 'serializer')"

Upvotes: 2

Views: 9995

Answers (1)

Dĵ ΝιΓΞΗΛψΚ
Dĵ ΝιΓΞΗΛψΚ

Reputation: 5669

i don't think what you're trying to do is feasible. as an alternative i can suggest to expose the .Aggregate() from the dbContext and query from the repo like below.

Employee Class

using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace Test
{
    [BsonIgnoreExtraElements]
    public class Employee
    {
        [BsonId]
        [BsonElement("_id")]
        [BsonRepresentation(BsonType.ObjectId)]
        public ObjectId Id { get; set; }

        [BsonElement("type")]
        public string Type { get; set; }

        [BsonElement("id")]
        [BsonRepresentation(BsonType.String)] // to avoid manual conversion
        public int CustomerId { get; set; }

        [BsonElement("name")]
        public string CustomerName { get; set; }
    }
}

DB Context

using MongoDB.Driver;
using System.Linq;

namespace Test.DbContext
{
    public class MongoDbContext
    {
        private readonly IMongoClient _mongoDbClient = null;

        private readonly IMongoDatabase _mongoDb = null;

        public MongoDbContext()
        {
            _mongoDbClient = new MongoClient("mongodb://localhost");
            _mongoDb = _mongoDbClient.GetDatabase("test");
        }

        public IAggregateFluent<TDocument> Aggregate<TDocument>() =>
            _mongoDb.GetCollection<TDocument>(nameof(TDocument)).Aggregate();

    }
}

Repository

using MongoDB.Driver;
using System.Collections.Generic;
using System.Linq;
using Test.DbContext;

namespace Test.Repositories
{
    public class CustomerRepository
    {
        private static readonly MongoDbContext _dbContext = new MongoDbContext();

        public List<dynamic> GetSpecificData()
        {
            var result = _dbContext.Aggregate<Employee>()
                             .Match(e => e.CustomerId == 11)
                             .Group(e => e.Type, g => new { Key = g.Key, Count = g.Count(), Average = g.Average(e => e.CustomerId) })
                             .ToList();

            return result.Cast<dynamic>().ToList();
        }
    }
}

i'd also recommend you to not cast anonymous type to dynamic. so, create and use another class (for group result) to retain type-safety.

i can also recommend for you to have a look at a library i've written which eradicates the need to write your own dbContext.

then have a look at this starter template project to see it in use.

Upvotes: 9

Related Questions