3

I know the include-feature of RavenDB. It allows me to fetch a referenced document right away in one roundtrip to the database. But my problem is: The document i fetch in the first place is not including a reference to the "other" documents. But the "other" documents have references to the current document.

Imagine a setup where we have sites across the world. Each site may trigger various alarms. Each alarm has a reference to the site via siteId.

Now i would like to get a list of all the sites including all alarms. But it looks like, this is not possible with RavenDB? Since include only accepts a "path" in the site-Document which holds an id (or an array of ids) to the referenced document.

This could be solved by providing an array of alarmIds within the site'-document and referencing this array in include. But in contrast to a lot of examples featuring stuff like an orderwithlineItemswhere the order is a self contained thing, mysite` will be running for years, collecting alarms anywhere between 0 and a million. Which seems to be a bad idea to me.

Of course i could go the other way round: Query all alarms and include the sites via sitesId. But this would not return a site that has zero alarms.

So is this just a design error on my side? To i misunderstand something? Or is it just not possible to do this in one query and prevent a "n+1 query"?

Sven Eppler
  • 1,646
  • 1
  • 15
  • 26

2 Answers2

3
public class A
{
    public string Id { get; set; }
}

public class B
{
    public string Id { get; set; }
    public string A { get; set; }
}

public class MultiMapIndex : AbstractMultiMapIndexCreationTask<MultiMapIndex.Result>
{
    public class Result
    {
        public string Id { get; set; }
        public IEnumerable<string> Bs { get; set; }
    }

    public MultiMapIndex()
    {
        AddMap<A>(items => from a in items
            select new Result {Id = a.Id, Bs = new string[0]});

        AddMap<B>(items => from b in items
            select new Result {Id = b.A, Bs = new[] {b.Id}});

        Reduce = results => from result in results
            group result by result.Id
            into g
            select new Result {Id = g.Key, Bs = g.SelectMany(r => r.Bs)};
    }
}

[Fact]
public async Task TestCase()
{
    using var store = GetDocumentStore();

    await new MultiMapIndex().ExecuteAsync(store);
    using (var session = store.OpenAsyncSession())
    {
        await session.StoreAsync(new B {A = "a/1"}, "b/0");
        await session.StoreAsync(new A(), "a/1");
        await session.StoreAsync(new A(), "a/2");
        await session.SaveChangesAsync();
    }

    WaitForIndexing(store);

    using (var session = store.OpenAsyncSession())
    {
        var results = await session.Query<MultiMapIndex.Result, MultiMapIndex>()
            .Include(r => r.Bs)
            .ToArrayAsync();

        var before = session.Advanced.NumberOfRequests;

        var bs = session.LoadAsync<B>(results[0].Bs);

        Assert.Equal(before, session.Advanced.NumberOfRequests);
    }
}
  • 1
    Hi! This looks interesting! I will have a deeper look at MultiMapIndexes. And hope to use it like this via node.js. ;) – Sven Eppler Oct 26 '21 at 08:41
2

If you do choose to query all Alarms, as you mention,
then you can create a Map-Reduce index on the Alarms collection which will group-by the Sites.
Then you can query this Map-Reduce index and know per Site the count of Alarms it has or doesn't have...

https://demo.ravendb.net/demos/csharp/static-indexes/map-reduce-index

Danielle
  • 3,324
  • 2
  • 18
  • 31
  • 1
    Ok, so i could easily get the alarmCount per Site, fine. But i still could not inlcude this result in one go? Instead i would need to fetch all the sites and then fetch the count for each site again (and now i am back to the n+1 query problem) – Sven Eppler Oct 22 '21 at 07:44