2012-05-02 10 views
6

tengo 2 tipos diferentes de objetos almacenados en RavenDb, que son una relación padre/hijo, tipo como este en JSON:Mapa reducir en más de 2 RavenDb colecciones con la colección de niño

Account/1 
{   
    "Name": "Acc1", 
} 

Items/1 
{ 
    "Account": "Account/1", 
    "Value" : "100", 
    "Tags": [ 
     "tag1", 
     "tag2"] 
} 

Items/2 
{ 
    "Account": "Account/1", 
    "Value" : "50", 
    "Tags": [ 
     "tag2"] 
} 

Tenga en cuenta que no lo hago desea almacenar estos en el mismo documento, ya que una cuenta puede tener miles de elementos.

Estoy intentando escribir un mapa/reducir el índice de que me va a devolver algo como:

{ 
    "Account": "Acc1", 
    "TagInfo": [ 
     { "TagName" : "tag1", 
      "Count" : "1", //Count of all the "tag1" occurrences for acc1 
      "Value" : "100" //Sum of all the Values for acc1 which are tagged 'tag1' 
     }, 
     { "TagName" : "tag2", 
      "Count" : "2", //Two items are tagged "tag2" 
      "Value" : "150" 
     }] 
} 

es decir, una lista de todos los nombres de las etiquetas distintas a lo largo con el número de cada uno y su valor.

Creo que necesito usar un mapa múltiple para asignar juntas las colecciones de Cuenta y Artículos, pero no puedo entender la parte de reducción para crear la parte "TagInfo" del resultado.

¿Es esto posible, o estoy modelando todo esto mal en Raven?

EDIT:

La clase Quiero recuperar de esta consulta sería algo como esto:

public class QueryResult 
{ 
    public string AccountId {get;set;} 
    public TagInfo Tags {get;set;} 
} 

public class TagInfo 
{ 
    public string TagName {get;set;} 
    public int Count {get;set;} 
    public int TotalSum {get;set;} 
} 

Respuesta

2

bien, así que pensé encontrar una manera de hacer esto de una manera aceptable que se basa en la respuesta de Daniel, así que lo grabaré aquí para cualquier viajero futuro (¡probablemente yo mismo!).

I cambiado de tratar de devolver un resultado por cuenta, a un resultado por combinación de cuenta/etiqueta, por lo que el índice tuvo que cambiar de la siguiente manera (véase la categoría group by en el reduce es el 2 propiedades):

public class TagsWithCountAndValues : AbstractIndexCreationTask<Item, TagsWithCountAndValues.ReduceResult> 
{ 
    public class ReduceResult 
    { 
     public string AccountId { get; set; } 
     public string AccountName { get; set; } 
     public string TagName { get; set; } 
     public int TagCount { get; set; } 
     public int TagValue { get; set; } 
    } 

    public TagsWithCountAndValues() 
    { 
     Map = items => from item in items 
         from tag in item.Tags 
         select new ReduceResult 
         { 
          AccountId = item.AccountId, 
          TagName = tag, 
          TagCount = 1, 
          TagValue = item.Value 
         }; 

     Reduce = results => from result in results 
          where result.TagName != null 
          group result by new {result.AccountId, result.TagName} 
          into g 
          select new ReduceResult 
             { 
              AccountId = g.Key.AccountId, 
              TagName = g.Key.TagName, 
              TagCount = g.Sum(x => x.TagCount), 
              TagValue = g.Sum(x => x.TagValue), 
             }; 

     TransformResults = (database, results) => from result in results 
                let account = database.Load<Account>(result.AccountId) 
                select new ReduceResult 
                  { 
                   AccountId = result.AccountId, 
                   AccountName = account.Name, 
                   TagName = result.TagName, 
                   TagCount = result.TagCount, 
                   TagValue = result.TagValue, 
                  }; 
    } 
} 

al igual que antes, esto es sólo la consulta:

var results = session 
    .Query<TagsWithCountAndValues.ReduceResult, TagsWithCountAndValues>() 
    .ToList(); 

el resultado de esto, entonces puede ser transformado en el objeto principio quería por una consulta LINQ en memoria. En este punto, el número de resultados que podrían devolverse sería relativamente pequeño, por lo que realizar esto en el extremo del cliente es fácilmente aceptable. La declaración de LINQ es:

var hierachicalResult = from result in results 
         group new {result.TagName, result.TagValue} by result.AccountName 
         into g 
         select new 
         { 
          Account = g.Key, 
          TagInfo = g.Select(x => new { x.TagName, x.TagValue, x.TagCount }) 
         }; 

Lo que nos da un objeto por cuenta, con una lista de elementos secundarios del TagInfo objetos - una para cada etiqueta única.

6

No se puede utilizar un multi Mapa/Reducir índice para que debido a que desea un mapa en las etiquetas y el otro en la cuenta. No tienen una propiedad común, por lo que no puede tener una multi mapas/reducir aquí.

Sin embargo, puede usar TransformResult en su lugar. Así es como se hace:

public class Account 
{ 
    public string Id { get; set; } 
    public string Name { get; set; } 
} 

public class Item 
{ 
    public string Id { get; set; } 
    public string AccountId { get; set; } 
    public int Value { get; set; } 
    public List<string> Tags { get; set; } 
} 

public class TagsWithCountAndValues : AbstractIndexCreationTask<Item, TagsWithCountAndValues.ReduceResult> 
{ 
    public class ReduceResult 
    { 
     public string AccountId { get; set; } 
     public string AccountName { get; set; } 
     public string Tag { get; set; } 
     public int Count { get; set; } 
     public int TotalSum { get; set; } 
    } 

    public TagsWithCountAndValues() 
    { 
     Map = items => from item in items 
         from tag in item.Tags 
         select new 
         { 
          AccountId = item.AccountId, 
          Tag = tag, 
          Count = 1, 
          TotalSum = item.Value 
         }; 
     Reduce = results => from result in results 
          group result by result.Tag 
          into g 
          select new 
          { 
           AccountId = g.Select(x => x.AccountId).FirstOrDefault(), 
           Tag = g.Key, 
           Count = g.Sum(x => x.Count), 
           TotalSum = g.Sum(x => x.TotalSum) 
          }; 
     TransformResults = (database, results) => from result in results 
                let account = database.Load<Account>(result.AccountId) 
                select new 
                { 
                 AccountId = result.AccountId, 
                 AccountName = account.Name, 
                 Tag = result.Tag, 
                 Count = result.Count, 
                 TotalSum = result.TotalSum 
                }; 
    } 
} 

Posteriormente continuación, se puede consultar la siguiente manera:

var results = session.Query<TagsWithCountAndValues.ReduceResult, TagsWithCountAndValues>() 
    .Where(x => x.AccountId == "accounts/1")       
    .ToList(); 
+0

Gracias Daniel, no sabía sobre 'TransformResults'! Aunque eso no es exactamente lo que quería, buscaba un resultado por Cuenta, con una propiedad que contiene los detalles de la etiqueta, ver mi edición.Además, el TotalSum anterior no funciona, ya que suma TODOS los valores de los elementos, no solo los de la cuenta que estoy consultando (¿creo que la reducción necesita agruparse en la cuenta en lugar de la etiqueta?) – Simon

+0

No puede tener una índice que tiene una clase anidada dentro de su resultado. No creo que puedas obtener lo que quieres en un solo índice. En cambio, prefiero tener algunos índices independientes y consultarlos todos para obtener toda la información que necesita o cambiar el modelo de datos para que se adapte mejor a ese patrón de acceso a los datos. –

+0

Ok, gracias Daniel. Pensé que ese podría ser el caso, pero no estaba muy seguro. Muchas gracias por tu tiempo. – Simon

Cuestiones relacionadas