Including Related Documents
Join a Single Document
Marten supports the ability to run include queries that execute a join
SQL query behind the curtains, in order to fetch a referenced document as well as the queried document. Suppose you are querying for a github Issue
that contains a property AssigneeId
, which references the Id of the User
assigned to the Issue. If you wish to fetch the User
as well in one trip to the database, you can use the .Include()
method like so:
[Fact]
public void simple_include_for_a_single_document()
{
var user = new User();
var issue = new Issue { AssigneeId = user.Id, Title = "Garage Door is busted" };
using var session = theStore.IdentitySession();
session.Store<object>(user, issue);
session.SaveChanges();
using var query = theStore.QuerySession();
User included = null;
var issue2 = query
.Query<Issue>()
.Include<User>(x => x.AssigneeId, x => included = x)
.Single(x => x.Title == issue.Title);
SpecificationExtensions.ShouldNotBeNull(included);
included.Id.ShouldBe(user.Id);
SpecificationExtensions.ShouldNotBeNull(issue2);
}
The first parameter of the Include()
method takes an expression that specifies the document properties on which the join will be done (AssigneeId
in this case). The second parameter is the expression that will assign the fetched related document to a previously declared variable (included
in our case). By default, Marten will use an inner join. This means that any Issue
with no corresponding User
(or no AssigneeId
), will not be fetched. If you wish to override this behavior, you can add as a third parameter the enum JoinType.LeftOuter
.
Join Many Documents
If you wish to fetch a list of related documents, you can declare a List<User>
variable and pass it as the second parameter. The Include()
method should be appended with ToList()
or ToArray()
.
Instead of a List, you could also use a Dictionary with a key type corresponding to the Id type and a value type corresponding to the Document type:
[Fact]
public void include_to_dictionary()
{
var user1 = new User();
var user2 = new User();
var issue1 = new Issue { AssigneeId = user1.Id, Title = "Garage Door is busted" };
var issue2 = new Issue { AssigneeId = user2.Id, Title = "Garage Door is busted" };
var issue3 = new Issue { AssigneeId = user2.Id, Title = "Garage Door is busted" };
using var session = theStore.IdentitySession();
session.Store(user1, user2);
session.Store(issue1, issue2, issue3);
session.SaveChanges();
using var query = theStore.QuerySession();
var dict = new Dictionary<Guid, User>();
query.Query<Issue>().Include(x => x.AssigneeId, dict).ToArray();
dict.Count.ShouldBe(2);
dict.ContainsKey(user1.Id).ShouldBeTrue();
dict.ContainsKey(user2.Id).ShouldBeTrue();
}
Include Multiple Document Types
Marten also allows you to chain multiple Include()
calls:
[Fact]
public void multiple_includes()
{
var assignee = new User();
var reporter = new User();
var issue1 = new Issue { AssigneeId = assignee.Id, ReporterId = reporter.Id, Title = "Garage Door is busted" };
using var session = theStore.IdentitySession();
session.Store(assignee, reporter);
session.Store(issue1);
session.SaveChanges();
using var query = theStore.QuerySession();
User assignee2 = null;
User reporter2 = null;
query
.Query<Issue>()
.Include<User>(x => x.AssigneeId, x => assignee2 = x)
.Include<User>(x => x.ReporterId, x => reporter2 = x)
.Single()
.ShouldNotBeNull();
assignee2.Id.ShouldBe(assignee.Id);
reporter2.Id.ShouldBe(reporter.Id);
}
Asynchronous Support
Marten supports Include within an asynchronous context. The query will be run asynchronously when you append your query with the corresponding Async method, like:
ToListAsync()
SingleAsync()
And so on...
Marten also supports running an Include query within batched queries:
var batch = query.CreateBatchQuery();
var found = batch.Query<Issue>()
.Include<User>(x => x.AssigneeId, x => included = x)
.Where(x => x.Title == issue1.Title)
.Single();