Using RavenDB, how do I efficiently retrieve a list of items related by a 'foreign key'

I store User objects in RavenDB. Each User has a User.Id property.

I also have a Relationship class that links two User.Ids together to create a Mentor/Mentee relationship, like this:

public class User
{
    public string Id { get; set; }
    public string UserName { get; set; }
    ... more properties
}

public class Relationship
{
    public string Id { get; set; }
    public string MentorId { get; set; }
    public string MenteeId { get; set; }
    public RelationshipStatus Status { get; set; }
}

Now I want to retrieve a list of Mentees for a given Mentor. I have done this in the following way:

public static List<User> GetMentees(IDocumentSession db, string mentorId)
{
    var mentees = new List<User>();
    db.Query<Relationship>()
            .Where(r => r.MentorId == mentorId)
            .Select(r => r.MenteeId)
            .ForEach(id => mentees.Add(db.Load<User>(id)));
    return mentees;
}

This seems to work just fine but the coding-angel on my shoulder is wrinkling her nose at the smells emanating from the nested use of the IDocumentSession (db) and the need for multiple Load calls to fill the Mentees List.

How can I optimise this method using best practice RavenDB syntax?

Edit Thanks to @Jonah Himango (see accepted answer below) who solved the problem of multiple calls to the database for me. In addition I have also created a new extension method called 'Memoize' to eliminate the need for the external 'mentees' result List (see code above).

Here is the optimised code. Please feel free to comment and refine further.

The Linq

public static List<User> GetMentees(IDocumentSession db, string mentorId)
{
    return  db.Query<Relationship>()
            .Customize(x => x.Include<Relationship>(o => o.MenteeId))
            .Where(r => r.MentorId == mentorId)
            .Memoize()
            .Select(r => db.Load<User>(r.MenteeId))
            .ToList();
}

The extension method

public static List<T> Memoize<T>(this IQueryable<T> target)
{
    return target.ToList();
}

Note : This extension method may seem completely superfluous (it is really) but it irritates my geek-gland that I have to call a function called ToList(), not to create a list, but to force the execution of the Linq statement. So my extension method just renames ToList() to the far more accurate Memoize().

Answers


You'll want to use the .Include query customization to tell Raven to include the related user object off of each Relationship:

db.Query<Relationship>()
            .Customize(x => x.Include<Relationship>(o => o.MenteeId))
            .Where(r => r.MentorId == mentorId)
            .Select(r => r.MenteeId)
            .ForEach(id => mentees.Add(db.Load<User>(id))); // .Load no longer makes a call to the DB; it's already loaded into the session!

Relevant documentation here:

The call to Load() is resolved completely client side (i.e. without additional requests to the RavenDB server) because the [related] object has already been retrieved via the .Include call.


Need Your Help

gitignore - how to ignore .idea/ directory for rubymine

git gitignore

I've placed .idea/ in both my ~/.gitignore_global and in my projects specific .gitignore file (and committed the later) but I still see the file as unstaged.

ConversionService in Spring

java spring data-binding spring-mvc converter

I'm following this scheme in a Spring application.