Indexing Polymorphic Data
-
By default, RavenDB indexes are defined on a specific entity type, referred to as a
Collection
,
and do not consider the inheritance hierarchy. -
In this page:
The challenge
Let's assume, for example, that we have the following inheritance hierarchy:
By default:
When saving a Cat
document, it will be assigned to the "Cats" collection,
while a Dog
document will be placed in the "Dogs" collection.
If we intend to create a simple Map-index for Cat documents based on their names, we would write:
class Cats_ByName extends AbstractJavaScriptIndexCreationTask {
constructor() {
super();
// Index the 'name' field from the CATS collection
this.map('Cats', cat => {
return {
name: cat.name
};
});
}
}
class Animal {
constructor(name) {
this.name = name;
}
}
class Cat extends Animal { }
And for Dogs:
class Dogs_ByName extends AbstractJavaScriptIndexCreationTask {
constructor() {
super();
// Index the 'name' field from the DOGS collection
this.map('Dogs', dog => {
return {
name: dog.name
};
});
}
}
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal { }
The challenge:
Querying each index results in documents only from the specific collection the index was defined for.
However, what if we need to query across ALL animal collections?
Possible solutions
Writing a Multi-map index enables getting results from all collections the index was defined for.
class CatsAndDogs_ByName extends AbstractJavaScriptMultiMapIndexCreationTask {
constructor() {
super();
// Index documents from the CATS collection
this.map('Cats', cat => {
return {
name: cat.name
};
});
// Index documents from the DOGS collection
this.map('Dogs', dog => {
return {
name: dog.name
};
});
}
}
Query the Multi-map index:
const catsAndDogs = await session
// Query the index
.query({ indexName: "CatsAndDogs/ByName" })
// Look for all Cats or Dogs that are named 'Mitzy' :))
.whereEquals("name", "Mitzy")
.all();
// Results will include matching documents from the CATS and DOGS collection
from index "CatsAndDogs/ByName"
where name == "Mitzy"
Another option is to create a polymorphic-index.
Use method WhereEntityIs
within your index definition to index documents from all collections
listed in the method.
class CatsAndDogs_ByName extends AbstractCsharpIndexCreationTask {
constructor() {
super();
// Index documents from both the CATS collection and the DOGS collection
this.map = `from animal in docs.WhereEntityIs("Cats", "Dogs")
select new {
animal.name
}`;
}
}
Query the polymorphic-index:
const catsAndDogs = await session
// Query the index
.query({ indexName: "CatsAndDogs/ByName" })
// Look for all Cats or Dogs that are named 'Mitzy' :))
.whereEquals("name", "Mitzy")
.all();
// Results will include matching documents from the CATS and DOGS collection
from index "CatsAndDogs/ByName"
where name == "Mitzy"
This option involves customizing the collection name that is assigned to documents created from
subclasses of the Animal class.
This is done by setting the findCollectionName convention on the document store.
const documentStore = new DocumentStore(["serverUrl_1", "serverUrl_2", "..."], "DefaultDB");
// Customize the findCollectionName convention
documentStore.conventions.findCollectionName = (type) => {
const typeName = type.name;
// Documents created from a 'Cat' or a 'Dog' entity will be assinged the "Animals" collection
if (typeName === "Cat" || typeName === "Dog") {
return "Animals";
}
// All other documents will be assgined the default collection name
return DocumentConventions.defaultGetCollectionName(type);
}
With the above convention in place, whenever a Cat or a Dog entity is saved, its document will be assigned the "Animals" collection instead of the default "Cats" or "Dogs" collection.
Now you can define a Map-index on the "Animals" collection:
class Animals_ByName extends AbstractJavaScriptIndexCreationTask {
constructor() {
super();
// Index documents from the ANIMALS collection
this.map('Animals', animal => {
return {
name: animal.name
};
});
}
}
Query the index:
const animals = await session
// Query the index
.query({ indexName: "Animals/ByName" })
// Look for all Animals that are named 'Mitzy' :))
.whereEquals("name", "Mitzy")
.all();
// Results will include matching documents from the ANIMALS collection
from index "Animals/ByName"
where name == "Mitzy"