How do you update a document based on an index in RavenDB? - ravendb

Taking the Stack Overflow tagging system as the canonical example, how can I get back a tag object with its count?
Given these entities:
// Represents the data from the TagCount index
public class TagCount
{
public string Tag { get; set; }
public int Count { get; set; }
}
// Represents the data used to populate a tag-wiki
public class Tag
{
public string Tag { get; set; }
public DateTime Created { get; set; }
public string Description { get; set; }
}
public class Question
{
// elided
public List<string> Tags { get; set; }
// elided
}
And the following definition of the TagCount index
// Map
from question in docs.Questions
from Tag in question.Tags
select new { Tag, Count = 1 }
// Reduce
from result in results
group result by result.Tag into g
select new
{
Tag = g.Key,
Count = g.Sum(x=>x.Count)
}
On the tags page (e.g. https://stackoverflow.com/tags) we need to display a combination of the Tags collection and the TagCounts index. That is, we need to display the Tag.Name and Tag.Description from the Tags collection and TagCount.Count from the TagCount index. In SQL Server this would be achieved by a join, but this is obviously the relational way of thinking. What is the idiomatic RavenDB way of achieving this?
Is there a way to add a Count property to the Tag entity and have the index automatically update that property?

You do that with patching, also called UpdateByIndex in the API.
You can see this here:
http://blog.hibernatingrhinos.com/12705/new-option-in-the-ravendb-studiondash-patching

Related

Sorting on nested Id property

Let's say we have a document like this
public class Event
{
public string Id { get; set; }
public EntityDescriptor Venue { get; set; }
// Other properties omitted for simplicity
}
public class EntityDescriptor
{
public string Id { get; set; }
public string Name { get; set; }
}
And an index like this
public class Events : AbstractIndexCreationTask<Event>
{
public Events()
{
Map = items => from e in items
select new
{
Venue_Id = e.Venue.Id,
Venue_Name = e.Venue.Name
};
}
}
When trying to sort on Event.Venue.Id
session.Query<Event, Events>().Take(10).OrderBy(e => e.Venue.Id).ToArray();
the sent request is
/indexes/Events?&pageSize=10&sort=__document_id&SortHint-__document_id=String
Is this by design or a bug?
PS: OrderBy(e => e.Venue.Name) works as expected (sort=Venue_Name).
It's not a bug. __document_id is the special known field containing the ID of the document. It's there regardless of whether you have an .Id property.
edit
I misread your question. This indeed appears to be a bug. I recommend you send a simple repro case to the Raven forum and let them know which RavenDB version you're using.

MongoDB query two collections with sort orders

Example: Let’s assume the following collections. ThrashMetalDocumentsCollection and SpeedMetalDocumentsCollection, both collections having the same HeavyMetalRecordDocument structure as shown below. How do I query and return ALL of the records in both collections and sort them by releaseDate (oldest first) and rating (high to low)? Thanks! \m/ \m/
static async Task getAllRecords()
{
var builder = Builders<HeavyMetalRecordDocument>.Filter;
//var filter;
using (var cursor = await ThrashMetalDocumentsCollection.Find()
.Sort(Builders<HeavyMetalRecordDocument>.Sort.Ascending(x => x.rating))
.ToCursorAsync())
{
while (await cursor.MoveNextAsync())
{
foreach (var doc in cursor.Current)
{
//Do Something…
}
}
}
}
public class HeavyMetalRecordDocument
{
public ObjectId Id { get; set; }
public String artist { get; set; }
public String title { get; set; }
public DateTime releaseDate { get; set; }
public int rating { get; set; } // 1-10
}
MongoDB is not a relational database, and as such, it cannot perform joins (which is what you are trying to accomplish). You probably want to think more about the structure of your database. Without knowing any more about what you are trying to do, I would suggest putting all types of albums into a single collection - putting in an additional field indicating the genre of the album.

Inheriting data from ancestor documents in RavenDB

I'm using RavenDB to store three types of curriculum: Units, Lessons, and Activities. These three types all inherit from CurriculumBase:
public abstract class CurriculumBase
{
public string Id { get; set; }
public string Title { get; set; }
public List<string> SubjectAreaIds { get; set; }
// non-relevant properties removed
}
These documents have a hierarchical relationship, so I've modeled the hierarchy as a separate single document as recommended here: Modelling Hierarchical Data with RavenDB
public class CurriculumHierarchy
{
public class Node
{
public string CurriculumId { get; set; }
public string Title { get; set; }
public List<Node> Children { get; set; }
public Node()
{
Children = new List<Node>();
}
}
public List<Node> RootCurriculum { get; set; }
public CurriculumHierarchy()
{
RootCurriculum = new List<Node>();
}
}
I need to be able to do searches across all curriculum documents. For simple properties, that seems easy enough to do with a multi-map index.
However one of the properties I need to be able to search by (in combination with the other search criteria) is SubjectAreaId. I need to be able to get curriculum for which it or any of its ancestors have the specified subject area id(s). In other words, for search purposes, documents should inherit the subjectAreaIds of their ancestors.
I've considered de-normalizing subjectAreaIds, and storing the full calculated set of subjectAreaIds in each document, but that will require updates whenever the hierarchy itself or the subjectAreaIds of any of a given document's ancestors change. I'm hoping this is something I can accomplish with an index, or perhaps an entirely different approach is needed.
You can use LoadDocument to load the parents during indexing.
http://ravendb.net/docs/article-page/3.0/csharp/indexes/indexing-related-documents
The main challenge I encountered was that I had written code in CurriculumHierarchy to get a document's ancestors, but this code isn't executable during indexing.
To solve this, I added a read-only property to CurriculumHierarchy which generates a dictionary of ancestors for each document:
public Dictionary<string, IEnumerable<string>> AncestorLookup
{
get
{
// Not shown: build a dictionary where the key is an
// ID and the value is a list of the IDs for
// that item's ancestors
}
}
This dictionary is serialized by Raven and therefore available for indexing.
Then my index ended up looking like this:
public class Curriculum_Search : AbstractMultiMapIndexCreationTask
{
public Curriculum_Search()
{
AddMap<Activity>(
activities =>
from activity in activities
let hierarchy =
LoadDocument<CurriculumHierarchy>("curriculum_hierarchy")
let ancestors =
LoadDocument<CurriculumBase>(hierarchy.AncestorLookup[activity.Id])
select new
{
subjectAreaIds = ancestors.SelectMany(x => x.SubjectAreaIds).Distinct().Union(activity.SubjectAreaIds),
});
// Not shown: Similar AddMap statements for Lessons and Units
}
}
I was a bit concerned about performance, but since there are less than 2000 total curriculum documents, this seems to perform acceptably.

Why do I need to name the properties in my index with underscore?

Given that I have the following structure (unnecessary details stripped out)
public class Product {
public Guid Id { get; set; }
public string Name { get; set; }
public Manufacturer Manufacturer { get; set; }
}
public class Manufacturer {
public Guid Id { get; set; }
public string Name { get; set; }
}
If I have a lot of these kind of products stored in raven and I want to index them by manufacturer id (or maybe some other things as well) I'd make an index such as this (of course in real life this index also contains some other information as well...)
public class ProductManufacturerIndex : AbstractIndexCreationTask<Product> {
public ProductManufacturerIndex() {
Map = products => from product in products
select new {
Manufacturer_Id = product.Manufacturer.Id,
};
}
}
My question here is, why do I need to name my field Manufacturer_Id? If I do not name it Manufacturer_Id I get exceptions when attempting to query my index since the manufacturer id column is not indexed.
Basically, why can't I do this? (Which would be my first guess)
public class ProductManufacturerIndex : AbstractIndexCreationTask<Product> {
public ProductManufacturerIndex() {
Map = products => from product in products
select new {
product.Manufacturer.Id,
};
}
}
There is a naming convention that RavenDB uses. If you aren't naming your fields properly, it doesn't know how to map things.
In this case, the second index you use has a property of Id, but RavenDB has no way of knowing that you mapped the Manufacturer's id, and not the root id.
That is why we have this convention. You can change it if you really want to, but it is generally not recommended.

How can I sort in (n)hibernate on a property of a child object?

I have an object from my domain model that has a child object. How can I use a criteria query to order based on a property of the child?
For example:
class FooType
{
public int Id { get; set; }
public string Name { get; set; }
public BarType Bar { get; set; }
}
class BarType
{
public int Id { get; set; }
public string Color { get; set; }
}
...
// WORKS GREAT
var orderedByName = _session.CreateCriteria<FooType>().AddOrder(Order.Asc("Name")).List();
// THROWS "could not resolve property: Bar.Color of: FooType"
var orderedByColor = _session.CreateCriteria<FooType>().AddOrder(Order.Asc("Bar.Color")).List();
What do I need to do to enable this scenario? I'm using NHibernate 2.1. Thanks!
You need to either add an alias or create a nested criteria for your child. Not sure how to do this in NHibernate, in Hibernate it's done via createCriteria() and createAlias() methods.
You would then use the alias as prefix in order by.
Update Hibernate code sample:
Criteria criteria = session.createCriteria(FooType.class);
criteria.createAlias("bar", "b");
criteria.addOrder(Order.asc("b.color"));
I imagine in NHibernate it would be quite similar, though with property/entity names uppercased. Here's an example from NHibernate documentation.