Search code examples
c#.netmongodbaggregation-frameworkmongodb-.net-driver

MongoDB.net driver - project into different class


When trying to project a typed document into a different class, I get the following: Could not find a member match for constructor parameter "origItem" on type "NewItem" in the expression tree new NewItem({document}, 1021).

A simplified example of the classes are as follows:

public class OriginalItem
{
    public ObjectId Id { get; set; }
    public string Name { get; set; }
    public IList<double> Data { get; set; }
    
    public OriginalItem() { }
}
public class NewItem
{
    public ObjectId Id { get; set; }
    public string Name { get; set; }
    public double Value { get; set; }
    
    public NewItem() { }
    public NewItem( OriginalItem origItem, int targetIdx )
    {
        Id = origItem.Id;
        Name = origItem.Name;
        Value = origItem.Data[targetIdx];
    }
}

An example of where the issue occurs is as follows:

IList<ObjectId> ids; // list of OriginalItem Ids to get
IMongoCollection<OriginalItem> collection = _db.GetCollection<OriginalItem>("items");
int targetIdx = 50;

IList<NewItem> newItems = await collection.Aggregate()
                         .Match( item => ids.Contains( item.Id ) )
                         .Project( origItem => new NewItem( origItem, targetIdx ) )
                         .ToListAsync();

I looked around and it seems like my only option would be project & transform the the origItem into a BsonDocument, and deserialize that into a NewItem. I've also tested changing new NewItem( origItem, targetIdx ) to new NewItem { //... } works.

I know I can simply read the item and perform the necessary transformations outside of the mongo server, but the real use case is slightly more complicated and I would like to at least figure out what I'm failing to understand.

Thank you


Solution

  • I have run into this issue as well. After scouring the internet for a while, I finally found that using the Builder's Project.Expression method was the only way that worked. For your scenario it would result in something like the following

    var project = Builders<OriginalItem>.Projection.Expression(item => new NewItem(origItem, targetInx));
    
    IList<NewItem> newItems = await collection.Aggregate()
                             .Match( item => ids.Contains( item.Id ) )
                             .Project(project)
                             .ToListAsync();
    

    I also removed all of the constructor logic, and instead had it doing straight assignments. So instead of new NewItem(origItem, targetInx) you would end up with something like new NewItem(item.Id, item.Name, item.Data[targetIdx]). I'm not certain if this step is necessary, but if the above doesn't work then I would definitely try this as well.