Indexing Polymorphic Data and generating the Collection for subclasses

121 views
Skip to first unread message

Alvin Saldanha

unread,
Aug 6, 2018, 4:15:11 AM8/6/18
to RavenDB - 2nd generation document database
Referring to the example indexing-polymorphic-data

There are 2 classes, Cat and Dog that inherit from the Animal class.

I have created a Cat Index and a Dog Index but the data for both Cats and Dogs is stored in a single collection called as "Animals".

I want to be able to fetch Only Cats or Dog based on Type. How can I do that?


var q = s.Query<Animal, DogIndex>(); 

The above query returns both cats and dogs. What should go in the where clause to return only Dogs?




Below is the complete code.


using Raven.Client.Documents;
using Raven.Client.Documents.Indexes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Raven.Client.Documents.Conventions;
using Raven.Client.Documents.Operations;
using Raven.Client.Documents.Queries;

public static class Program
{
public abstract class Animal
{
public string Name { get; set; }
}
public class Dog : Animal
{
public string Bark()
{
return "Woof Woof";
}
}
public class Cat : Animal
{
public string Meow()
{
return "Meooooooow";
}
}

public class CatIndex : AbstractIndexCreationTask<Cat, AnimalIndex>
{
public CatIndex()
{
Map = entities => from entity in entities
  select new AnimalIndex
  {
  Name = entity.Name
  };
}
}
public class DogIndex : AbstractIndexCreationTask<Dog, AnimalIndex>
{
public DogIndex()
{
Map = entities => from entity in entities
select new AnimalIndex
{
Name = entity.Name
};
}
}

public static void Main(string[] args)
{
DocumentStore documentStore = new DocumentStore()
{
Conventions =
{
FindCollectionName = type =>
{
if (typeof(Animal).IsAssignableFrom(type))
return "Animals";
return DocumentConventions.DefaultGetCollectionName(type);
}
}
};


documentStore.Urls = new[] { "http://live-test.ravendb.net" };
documentStore.Database = "AmaltheaTest";




using (var store = documentStore)
{
store.Initialize();
documentStore.Operations.Send(new DeleteByQueryOperation(
new IndexQuery { Query = "from @all_docs as d" },
new QueryOperationOptions
{
RetrieveDetails = true
}
));

new CatIndex().Execute(store);
new DogIndex().Execute(store);
using (var s = store.OpenSession())
{
s.Store(new Dog
{
Name = "Scooby Doo"
});

s.Store(new Dog
{
Name = "Scrappy Doo"
});
s.Store(new Cat
{
Name = "Garfield"
});
s.Advanced.WaitForIndexesAfterSaveChanges();
s.SaveChanges();
}

using (var s = store.OpenSession())
{
var q = s.Query<Animal, DogIndex>();

Console.WriteLine(q.ToString());

foreach (var person in q)
{
Console.WriteLine(s.Advanced.GetDocumentId(person));
}
}

}
}

public class AnimalIndex
{
public string Name { get; set; }
}
}

Arkadiusz Palinski

unread,
Aug 6, 2018, 10:14:43 AM8/6/18
to rav...@googlegroups.com
Hi,

They go the the same collection because you defined such convention:

FindCollectionName = type =>
{
if (typeof(Animal).IsAssignableFrom(type))
return "Animals";
return DocumentConventions.DefaultGetCollectionName(type);
}

Once you remove it your docs will be inserted into Cats and Dogs collections what I believe you want. See similar discussion: https://groups.google.com/forum/#!topic/ravendb/yonVBBp_v-Q


--
You received this message because you are subscribed to the Google Groups "RavenDB - 2nd generation document database" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ravendb+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Alexander Klaus

unread,
Aug 6, 2018, 8:27:28 PM8/6/18
to RavenDB - 2nd generation document database
Hi Arkadiusz,

Let me jump in, as Alvin (OP) and I are working on the same problem.

The objective is to query 
  1. both Cats and Dogs at the same time
  2. just Dogs or just Cats
We DO NOT require particular persistence classes or indexes. The Alvin's example may be a wrong path (though, IMHO it points to some ambiguity in the documentation).

IMHO we have one condition, which make our case distinctive from the examples in the docs and a similar discussion in this group:
  • All "unique" dogs and cats properties are nullable, so we cannot add a filtering condition (like IsBarking = true) to identify just dogs.
RavenDb already stores metadata for each entry with the class type ("Raven-Clr-Type": "Dog") and  it seems there are some methods to handle that (e.g. WhereEntityIs), but all my attempts to use them in the query or introduce a flag in the index based on Raven-Clr-Type failed.

The only workaround I see is to introduce a property in the persistent base class (Animal) to return the type (dog or cat), but it would be a smelly code (because a) the base class'd need to be aware of the derived classes; b) unnecessary property in the DB collection and index).
And here I just don't believe that RavenDb just doesn't have a way to achieve it. It's been many times in the past that it is possible to achieve whatever flexibility the devs need, just sometimes the solution is not on the surface. What's why here we rely on your help.

Best regards,
Alex Klaus

Oren Eini (Ayende Rahien)

unread,
Aug 7, 2018, 2:49:57 AM8/7/18
to ravendb
Alexander, 
A lot of that depends on the kind of queries you are routinely issuing. 
If most of your queries are on Animal, I would suggest bite the bullet and deal with it.
However, if most of your queries are on Cat or Dog, and queries on Animal are rare, you can define a multi map index that would map both of them and allow you to search across multiple collections

Hibernating Rhinos Ltd  

Oren Eini l CEO Mobile: + 972-52-548-6969

Office: +972-4-622-7811 l Fax: +972-153-4-622-7811

 


To unsubscribe from this group and stop receiving emails from it, send an email to ravendb+unsubscribe@googlegroups.com.

Alexander Klaus

unread,
Aug 7, 2018, 3:17:11 AM8/7/18
to RavenDB - 2nd generation document database
Hi Oren,

We're more the 2nd case, where mostly need to query on dogs and cats independently. 

So in this case, you suggest to store 2 independent collections (one for cats and one for dogs, even they're derived from the same class) and create one multi map index to query against dogs and cats?

I think I have tried that and queries like s.Query<Dog, AnimalMultiMapIndex>() returned all cats and dogs with a type of object Dog. As I mentioned above, all "unique" dogs and cats properties are nullable, so we cannot add a filtering condition (like IsBarking = true) to identify just dogs.

Best regards,
Alex Klaus

Grisha Kotler

unread,
Aug 7, 2018, 3:56:30 AM8/7/18
to rav...@googlegroups.com
Your query should be  s.Query<Dog>()
or  s.Query<Dog>().Where(x => x.IsBarking)

Hibernating Rhinos Ltd  cid:image001.png@01CF95E2.8ED1B7D0

Grisha Kotler l RavenDB Core Team Developer Mobile: +972-54-586-8647

RavenDB paving the way to "Data Made Simplehttp://ravendb.net/


To unsubscribe from this group and stop receiving emails from it, send an email to ravendb+unsubscribe@googlegroups.com.

Alexander Klaus

unread,
Aug 7, 2018, 4:21:21 AM8/7/18
to RavenDB - 2nd generation document database
Hi Grisha,

No, it's not a solution.
As I said before all "unique" dogs and cats properties are nullable, so we cannot add a filtering condition (like IsBarking = true) to identify just dogs.
We were posting the request in a hope that RavenDb provides an out-of-the-box way to resolve the type of the entry (either in the index or query) based on the meta data. Oren gave me a hope (see above) that it's possible.

Best Regards,
Alex Klaus

Oren Eini (Ayende Rahien)

unread,
Aug 7, 2018, 4:26:13 AM8/7/18
to ravendb
Alexander,
Given your scenario, what you need to do is to have separate collections.
When you want to query just on Dogs, you use:

session.Query<Dog>().Where(x=>x.Name).ToList(); 

In this case, Name is shared, but only Dogs will be returned.

On the other hand, when you want to run a query on both dogs & cats, you'll use:

session.Query<Animal, CatsAndDogsMultiMapIndex>()
   .Where(x=>x.IsRaining)
   .ToList();
To unsubscribe from this group and stop receiving emails from it, send an email to ravendb+unsubscribe@googlegroups.com.

Grisha Kotler

unread,
Aug 7, 2018, 4:29:35 AM8/7/18
to rav...@googlegroups.com
For that you'll need to remove the FindCollectionName conventions.

Hibernating Rhinos Ltd  cid:image001.png@01CF95E2.8ED1B7D0

Grisha Kotler l RavenDB Core Team Developer Mobile: +972-54-586-8647

Office: +972-4-622-7811 l Fax: +972-153-4-622-7811

RavenDB paving the way to "Data Made Simplehttp://ravendb.net/


To unsubscribe from this group and stop receiving emails from it, send an email to ravendb+unsubscribe@googlegroups.com.

Alexander Klaus

unread,
Aug 7, 2018, 7:06:12 AM8/7/18
to RavenDB - 2nd generation document database
Hi Oren and Grisha,

Is it expected that a query like session.Query<Dog>().Where(x=>x.Name).ToList() would create a new auto-index? And a result of that would be having 3 indexes - one per collection (Dogs and Cats) and one multi-mapping?

Best regards,
Alex Klaus

Oren Eini (Ayende Rahien)

unread,
Aug 7, 2018, 7:24:03 AM8/7/18
to ravendb
Yes
To unsubscribe from this group and stop receiving emails from it, send an email to ravendb+unsubscribe@googlegroups.com.

Alexander Klaus

unread,
Aug 7, 2018, 7:52:55 AM8/7/18
to RavenDB - 2nd generation document database
Sigh...
If there is a way to raise a feature request or submit an upvote for an existing one I'd like to do so.

The acceptance criteria of the feature would be
Provide an ability to query records of a certain entity type/class against a single index built for 2 or more entity types (either derived from one base class or implementing the same interface). Also query records of all entity types/classes supported by the index.
 
Possible implementation could be to have a method to resolve the entity type and persist it in the index. Like there is already a method to get the metadata dictionary, which contains the class name, so if there is a way to persist the class name to the index, it would do.

Best regards,
Alex Klaus

Oren Eini (Ayende Rahien)

unread,
Aug 7, 2018, 8:47:37 AM8/7/18
to ravendb
It is already here.
Multi map is how you do that. 

The maps:

from d in docs.Dogs
select new { Animal = "Dog", d.Name } 

from c in docs.Cats
select new { Animal = "Cat", d.Name }


Then in you query you can use:


session.Query<MyIndex.Result, MyIndex>()
   .Where(x=>x.Animal == "Dog")
   .Where(x=>x.Name == "Robert")
   .OfType<Dog>()

session.Query<MyIndex.Result, MyIndex>()
   .Where(x=>x.Name == "Robert")
   .OfType<Animal>()


The key problem here is that the C# polymorphic type doesn't translate at the document level, so you need to be explicit about it


To unsubscribe from this group and stop receiving emails from it, send an email to ravendb+unsubscribe@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages