Indexing one or more fields with IndexedValue
Matching property
The simplest way to define an index is to define a matching property on the IndexDefinition
This property has to
- have the same name as the property you want to index
- be of type
IndexedValue<T, TAttribute>
(where T equals the type of objects in the collection) - have the same type for TAttribute as the type of the property you want to index
- has to defined as
{ get; set; }
(or at least behave similar)
If any of these requirements is not met, the Collection method on the collection-file will throw as soon as you try to get a reference to it. So, you either get a collection with valid and working indexes, or you get an error.
Once you have this collection, all index functionality is typed correctly. The compiler will prevent you from errors.
An example:
///
/// This IndexDefinition can be used to index
// instances of Book
/// ||
class BookIndexDefinition : IndexDefinition<Book>
{
// Create an index on property Title
// ||
public IndexedValue<Book, string> Title { get; set; }
// || ||
// of Book which is a string
}
Custom Value
If indexing a property is not enough, custom indexes can be defined. In this case you also define a property like above, except you return an instance of IndexedValue, created with a function that returns the value to be indexed.
class BookIndexDefinition : IndexDefinition<Book>
{
public IndexedValue<Book, string> MainAuthorName
{
get{
//index the name of the first autor
return base.IndexedValue(
(book) => book.Authors.First().Name // function returning the value to be indexed.
);
}
/* setter not needed, nor usefull */
}
}
Compound Values
An IndexedValue can also be created with multiple fields.
In this case we should declare the property using IndexedValue<T, TAttr1, TAttr2, ...>
class BookIndexDefinition : IndexDefinition<Book>
{
public IndexedValue<Book, int, string> CategoryIdAndMainAuthorName
{
get{
//index the CategoryId and Name of the first autor
return IndexedValue(
(book) => CompoundValue(book.CategoryId, book.Authors.First().Name)
);
}
/* setter not needed, nor usefull */
}
}
Unique Index Values
Unique indexes make sure that there are no 2 keys with the same value in the index.
If we would try to Persist
an object that has a duplicate value for a given index, MarcelloDB will throw an exception.
We can define a unique index by using UniqueIndexedValue
instead of IndexedValue
.
//With a simple property
class BookIndexDefinition : IndexDefinition<Book>
{
public UniqueIndexedValue<Book, string> ISBN {get;set;}
}
//OR with a custom value
class BookIndexDefinition : IndexDefinition<Book>
{
public UniqueIndexedValue<Book, string> ISBN
{
get{
return UniqueIndexedValue(
book => book.ISBN
);
}
}
}
Besides the regular enumeration methods All
, GreaterThan
, SmallerThan
and Between
, a unique indexed value also has a Find
method which returns a single object or null;
var aSongOfIceAndFire = bookCollection.Indexes.ISBN.Find("9780553386790");
//is the equivalent of
var aSongsOfIceAndFire = bookCollection.Indexes.ISBN.Equals("9780553386790").FirstOrDefault();
Conditional indexes
Conditional indexes allow us to specify the circumstances under which an entry to the index is made.
For instance, it makes no sense to add books to the ISBN index if an ISBN number is not available.
MarcelloDB allows us to define an IndexedValue (or UniqueIndexedValue) with a Func<T, bool>
to decide whether this object should be added to the index.
Like this:
class BookIndexDefinition : IndexDefinition<Book>
{
public UniqueIndexedValue<Book, string> ISBN
{
get{
return UniqueIndexedValue(
book => book.ISBN,
book => book.ISBN != null //Only index this book if the ISBN isn't null
);
}
}
}
If it is comparable, it can be indexed
Although base types are preferred as indexed values for performance reasons, any type can be used as index entry.
The only requirement is that it implements IComparable.
class DeliveryZone : IComparable
{
public int PostalCode { get; set; }
public string CityName { get; set; }
// DeliveryZone compared first by cityName, then by postalCode
public int CompareTo(object obj)
{
var otherDeliveryZone = (DeliveryZone)obj;
var cityCompared = this.CityName.CompareTo(otherDeliveryZone.CityName);
if(cityCompared == 0)
{
return this.PostalCode.CompareTo(otherDeliveryZone.PostalCode);
}
return cityCompared ;
}
}
class OrderIndexDefinition : IndexDefinition<Order>
{
public IndexedValue<Order, DeliveryZone> DeliveryZone {
get {
return IndexedValue(
(order)=>order.DeliveryAddress.Zone;
);
}
}
}
//Get all orders, ordered by their delivery zone
orderCollection.Indexes.DeliveryZone.All
Please note that cases similar to the example above, are better implement using a compound index. Custom objects in indexes are serialized as Bson while Compound indexes are using a specialized serializer which performs a lot better.
class OrderIndexDefinition : IndexDefinition<Order>
{
public IndexedValue<Order, int, string> DeliveryZone {
get {
return IndexedValue(
(order)=>CompoundValue(order.DeliveryAddress.Zone.ZipCode, order.deliveryAddress.Zone.City);
);
}
}
}
``