Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simplest way to flatten document to a view in RavenDB

Tags:

c#

ravendb

Given the following classes:

public class Lookup
{
    public string Code { get; set; }
    public string Name { get; set; }
}

public class DocA
{
    public string Id { get; set; }
    public string Name { get; set; }
    public Lookup Currency { get; set; }
}

public class ViewA // Simply a flattened version of the doc
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string CurrencyName { get; set; } // View just gets the name of the currency
}

I can create an index that allows client to query the view as follows:

public class A_View : AbstractIndexCreationTask<DocA, ViewA>
{
    public A_View()
    {
        Map = docs => from doc in docs
                      select new ViewA
                      {
                          Id = doc.Id,
                          Name = doc.Name,
                          CurrencyName = doc.Currency.Name
                      };

        Reduce = results => from result in results
                      group on new ViewA
                      {
                          Id = result.Id,
                          Name = result.Name,
                          CurrencyName = result.CurrencyName
                      } into g
                      select new ViewA
                      {
                          Id = g.Key.Id,
                          Name = g.Key.Name,
                          CurrencyName = g.Key.CurrencyName
                      };
    }
}

This certainly works and produces the desired result of a view with the data transformed to the structure required at the client application. However, it is unworkably verbose, will be a maintenance nightmare and is probably fairly inefficient with all the redundant object construction.

Is there a simpler way of creating an index with the required structure (ViewA) given a collection of documents (DocA)?

FURTHER INFORMATION The issue appears to be that in order to have the index hold the data in the transformed structure (ViewA), we have to do a Reduce. It appears that a Reduce must have both a GROUP ON and a SELECT in order to work as expected so the following are not valid:

INVALID REDUCE CLAUSE 1:

        Reduce = results => from result in results
                      group on new ViewA
                      {
                          Id = result.Id,
                          Name = result.Name,
                          CurrencyName = result.CurrencyName
                      } into g
                      select g.Key;

This produces: System.InvalidOperationException: Variable initializer select must have a lambda expression with an object create expression

Clearly we need to have the 'select new'.

INVALID REDUCE CLAUSE 2:

        Reduce = results => from result in results
                      select new ViewA
                      {
                          Id = result.Id,
                          Name = result.Name,
                          CurrencyName = result.CurrencyName
                      };

This prduces: System.InvalidCastException: Unable to cast object of type 'ICSharpCode.NRefactory.Ast.IdentifierExpression' to type 'ICSharpCode.NRefactory.Ast.InvocationExpression'.

Clearly, we also need to have the 'group on new'.

Thanks for any assistance you can provide.

(Note: removing the type (ViewA) from the constructor calls has no effect on the above)

UPDATE WITH CORRECT APPROACH

As outlined in Daniel's blog mentioned in the answer below, here is the correct way to do this for this example:

public class A_View : AbstractIndexCreationTask<DocA, ViewA>
{
    public A_View()
    {
        Map = docs => from doc in docs
                      select new ViewA
                      {
                          Id = doc.Id,
                          Name = doc.Name,
                          CurrencyName = doc.Currency.Name
                      };

        // Top-level properties on ViewA that match those on DocA
        // do not need to be stored in the index.
        Store(x => x.CurrencyName, FieldStorage.Yes);
    }
}
like image 555
Phil Degenhardt Avatar asked Oct 08 '22 07:10

Phil Degenhardt


1 Answers

One solution, simply flatten in the Map and configure the index to store only properties that do not exist in DocA.

public class A_View : AbstractIndexCreationTask<DocA, ViewA>
{
    public A_View()
    {
        Map = docs => from doc in docs
                      select new ViewA
                      {
                          Id = doc.Id,
                          Name = doc.Name,
                          CurrencyName = doc.Currency.Name
                      };

        // Top-level properties on ViewA that match those on DocA
        // do not need to be stored in the index.
        Store(x => x.CurrencyName, FieldStorage.Yes);
    }
}
like image 197
Phil Degenhardt Avatar answered Oct 24 '22 00:10

Phil Degenhardt