2

I am new to Mongo and have a project that uses the C# MongoDb driver and Linq for retrieving data from MongoDb.

I have an object named Instance that I retrieve from the Mongo collection just fine. However, the object's Template property belongs in a separate collection and is null when querying the Instance collection. I would like to eager load the template data when querying the instance.

This would be the equivalent of Entity Framework's Include method which eager loads related entities. I have searched the net for an equivalent approach using the C# Mongo driver but no luck.

How can I accomplish this using the C# MongoDb driver and Linq?

public class Instance
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int? TemplateId { get; set; }
    public Template Template { get; set; }
}
public class Template
{
    public int Id { get; set; }
    public string Name { get; set; }
}

var collection = MongoDatabase.GetCollection<Instance>("Instances").AsQueryable()
var instance = collection.First(i => i.Id == 1);
var template = instance.Template; //always null
Canica
  • 2,650
  • 3
  • 18
  • 34
  • How do you retrieve `instances` collection? – dododo Oct 30 '19 at 13:06
  • @dododo I've updated the post, I use this to get the collection: `MongoDatabase.GetCollection("Instances").AsQueryable()` – Canica Oct 30 '19 at 13:12
  • 1
    Since `Template` is a separate collection, you need to use `lookup` for your target:https://stackoverflow.com/questions/35638372/how-to-lookup-with-mongodb-c-sharp-driver – dododo Oct 30 '19 at 13:44
  • @dododo Agreed. However, that is projecting to an anonymous type. https://mongodb.github.io/mongo-csharp-driver/2.2/reference/driver/crud/linq/#lookup I need to return the data as the `Instance` type (in this case). I am hoping there is something more flexible that can also be used for other collections of different types (eg. Order => OrderDetail) without manual projections. – Canica Oct 30 '19 at 14:07

1 Answers1

2

You can have Typed Lookup like this>

public class Instance
{
    public ObjectId Id { get; set; }
    public string Name { get; set; }
    public ObjectId TemplateId { get; set; }
    public Template Template { get; set; }
}

public class Template
{
    public ObjectId Id { get; set; }
    public string Name { get; set; }
}


string connectionString = "mongodb://localhost:27017";
var client = new MongoClient(connectionString);

var db = client.GetDatabase("test");
var instances = db.GetCollection<Instance>("Instances");
var resultOfJoin = instances.Aggregate()
    .Lookup("Templates", "TemplateId", "_id", @as: "Template")
    .Unwind("Template")
    .As<Instance>()
    .ToList();

EDIT: Unwind part edited by jcruz.

ntohl
  • 2,067
  • 1
  • 28
  • 32
  • this seems promising. For some reason the parameter-less `Aggregate` method only works on `IMongoCollection` and I can't cast from `MongoCollection`. Any suggestions? – Canica Oct 31 '19 at 13:21
  • Which versions? – ntohl Oct 31 '19 at 13:44
  • v2.2 https://api.mongodb.com/csharp/2.2/html/M_MongoDB_Driver_IMongoCollectionExtensions_Aggregate__1.htm – Canica Oct 31 '19 at 14:47
  • 1
    I figured it out, I was using `MongoServer` instead of `MongoClient`. However, your solution returns an array of Templates where I need a single object (I can't change this) so I get the following error: "Expected a nested document representing the serialized form of a Namespace.Template value, but found a value of type Array instead.". Any way to adjust this to return a single Template object? – Canica Oct 31 '19 at 15:19
  • 1
    I was able to use `Unwind` to get a single Template object returned. I added my edit to your answer. Thank you for the help!!! – Canica Oct 31 '19 at 15:33