I am currently working on a difficult problem, and am at the level where I think RavenDB cannot help me any further ( It's already doing enough in our case ) but i just want to check if i am missing something before i do some crazy stuff to make my case work :-)
I have "Offer" documents and an index that fans out and creates a few index entries per "Offer"
When i query this i need to get the actual documents only ( once ) even if multiple index entries would match for a document.
This already works fine when i project the query back to the actual document type.
However the count of the query is off and returning the number of matching index entries instead of the document count.
( I need the count for paging through the result set )
This behaviour makes total sense to me and i am prepared to work around it, however i am want to know if i a missing something and somehow ravendb could already solve this for me and return the count in documents instead of index entries quantities.
( In case you're wondering, I have a hierarchy of leaflet flight, leaflets, leafletpages and offers and on each level certain properties need to overridable and be inherited down to the acutal offers. but since there is some data sharing going on this leads to a classic diamond inheritance problem, where i have to create one index entry per inheritance path )
Here is my simple code: ( Case 4 returns the wrong count )
public class LeafletFlight
{
public LeafletFlight()
{
LeafletReferences = new List<LeafletReference>();
AvailabilityZipCodes = new List<string>();
}
public string Id { get; set; }
public IList<LeafletReference> LeafletReferences { get; set; }
public IList<string> AvailabilityZipCodes { get; set; }
}
public class LeafletReference
{
public string LeafletId { get; set; }
}
public class Leaflet
{
public Leaflet()
{
AvailabilityZipCodes = new List<string>();
LeafletPageReferences = new List<LeafletPageReference>();
}
public string Id { get; set; }
public string LeafletFlightId { get; set; }
public IList<LeafletPageReference> LeafletPageReferences { get; set; }
public IList<string> AvailabilityZipCodes { get; set; }
}
public class LeafletPageReference
{
public string LeafletPageId { get; set; }
}
public class LeafletPage
{
public LeafletPage()
{
OfferReferences = new List<OfferReference>();
LeafletIds = new List<string>();
}
public string Id { get; set; }
public IList<OfferReference> OfferReferences { get; set; }
public IList<string> LeafletIds { get; set; }
}
public class OfferReference
{
public OfferReference()
{
AvailabilityZipCodes = new List<string>();
}
public string OfferId { get; set; }
public IList<string> AvailabilityZipCodes { get; set; }
}
public class Offer
{
public Offer()
{
AvailabilityZipCodes = new List<string>();
LeafletPageIds = new List<string>();
}
public string Id { get; set; }
public string Name { get; set; }
public IList<string> LeafletPageIds { get; set; }
public IList<string> AvailabilityZipCodes { get; set; }
}
public class OfferIndex : AbstractIndexCreationTask<Offer>
{
public class Entry
{
public IEnumerable<string> AvailabilityZipCodes { get; set; }
public int PathCount { get; set; }
}
public OfferIndex()
{
Map = offers => from offer in offers
let paths =
offer.LeafletPageIds.Select(p => LoadDocument<LeafletPage>(p))
.SelectMany(p => p.LeafletIds.Select(l => new
{
LeafletPage = p,
Leaflet = LoadDocument<Leaflet>(l),
LeafletFlight = LoadDocument<LeafletFlight>(LoadDocument<Leaflet>(l).LeafletFlightId),
Offer = offer
}))
from path in paths
let offerAvailabilityZips = path.Offer.AvailabilityZipCodes
let offerReferenceAvailabilityZips = path.LeafletPage.OfferReferences.First(o => o.OfferId ==
path.Offer.Id).AvailabilityZipCodes
let leafletAvailabilityZips = path.Leaflet.AvailabilityZipCodes
let leafletFlightAvailabilityZips = path.LeafletFlight.AvailabilityZipCodes
select new
{
AvailabilityZipCodes = offerAvailabilityZips.Any() ? offerAvailabilityZips : (offerReferenceAvailabilityZips.Any() ? offerReferenceAvailabilityZips : (leafletAvailabilityZips.Any() ? leafletAvailabilityZips : leafletFlightAvailabilityZips))
};
MaxIndexOutputsPerDocument = 2048;
}
}
public class Test : RavenTestBase
{
public void Execute()
{
using (var store = NewDocumentStore(port:8084,indexes:new AbstractIndexCreationTask[] {new OfferIndex()}))
{
using (var session = store.OpenSession())
{
var leafletFlight = new LeafletFlight();
leafletFlight.AvailabilityZipCodes.Add("1010");
leafletFlight.AvailabilityZipCodes.Add("1020");
session.Store(leafletFlight);
var leaflet1 = new Leaflet();
leaflet1.AvailabilityZipCodes.Add("1020");
leaflet1.AvailabilityZipCodes.Add("1030");
leaflet1.LeafletFlightId = leafletFlight.Id;
var leaflet2 = new Leaflet();
leaflet2.LeafletFlightId = leafletFlight.Id;
session.Store(leaflet1);
session.Store(leaflet2);
leafletFlight.LeafletReferences.Add(new LeafletReference() { LeafletId = leaflet1.Id});
leafletFlight.LeafletReferences.Add(new LeafletReference() { LeafletId = leaflet2.Id});
var leafletPage = new LeafletPage();
leafletPage.LeafletIds.Add(leaflet1.Id);
leafletPage.LeafletIds.Add(leaflet2.Id);
session.Store(leafletPage);
leaflet1.LeafletPageReferences.Add(new LeafletPageReference() {LeafletPageId = leafletPage.Id});
leaflet2.LeafletPageReferences.Add(new LeafletPageReference() {LeafletPageId = leafletPage.Id});
var offer = new Offer();
offer.Name = "Test01";
offer.LeafletPageIds.Add(leafletPage.Id);
session.Store(offer);
leafletPage.OfferReferences.Add(new OfferReference() { OfferId = offer.Id});
session.SaveChanges();
WaitForIndexing(store);
ValidateCount(session,"1050",0);
ValidateCount(session,"1010",1);
ValidateCount(session,"1030",1);
ValidateCount(session,"1020",1);
}
}
}
private void ValidateCount(IDocumentSession session, string zipCode,int count)
{
RavenQueryStatistics statistics = null;
var queryResult =
session.Query<OfferIndex.Entry, OfferIndex>()
.Statistics(out statistics)
.Where(o => o.AvailabilityZipCodes.Any(z => z == zipCode)).As<Offer>().ToList();
if (count != statistics.TotalResults)
{
Console.WriteLine($"INVALID for zip {zipCode}, expected {count}, got {statistics.TotalResults}");
}
else
{
Console.WriteLine($"VALID for zip {zipCode}");
}
}
}