2012-05-02 15 views
6

mam 2 różne typy obiektów przechowywanych w RavenDb, które mają związek macierzysty/typ dziecka, tak w JSON:Mapa zmniejszyć w ciągu 2 RavenDb zbiorów z kolekcji dziecięcej

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

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

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

Zauważ, że ja nie wiem chcesz je przechowywać w tym samym dokumencie, ponieważ konto może mieć tysiące pozycji.

Próbuję napisać mapę/zmniejszenia indeks, który zwróci mi coś takiego:

{ 
    "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" 
     }] 
} 

tj listę wszystkich odrębnych nazw znaczników wraz z liczbą wszystkich i ich wartości.

Wydaje mi się, że potrzebuję wielu map, aby odwzorować razem kolekcje Konta i Przedmiotów, ale nie mogę wymyślić części zmniejszonej, aby utworzyć część wyniku "TagInfo".

Czy to możliwe, czy też modeluję to wszystko w Kruczym?

EDIT:

Klasa Chcę odzyskać od tego zapytania będzie wyglądać mniej więcej tak:

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;} 
} 

Odpowiedz

2

OK, więc wymyśliliśmy sposób, aby to zrobić w rozsądny sposób, który opiera się na odpowiedź Daniela, więc nagrywam tutaj dla przyszłych podróżników (prawdopodobnie ja!).

zmieniłem od próbując powrócić jeden wynik na koncie, aby jeden wynik na połączeniu konto/tag, więc indeks musiał zmienić w następujący sposób (zwróć uwagę na group by w reduce jest na 2 właściwościach):

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, 
                  }; 
    } 
} 

Jak poprzednio, zapytań jest to po prostu:

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

wynik ten może być następnie przekształcony w obiekt początkowo chciałem przez kwerendy LINQ w pamięci. W tym momencie liczba wyników, które mogłyby zostać zwrócone, byłaby względnie mała, więc wykonanie tego na końcu klienta jest łatwe do zaakceptowania. Oświadczenie LINQ to:

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 }) 
         }; 

co daje nam jeden obiekt za uwagę, z listy obiektów podrzędnych TagInfo obiektów - po jednym dla każdego unikalnego tagu.

6

Nie można używać/Reduce Indeks Multi Mapa na to, bo chcesz jedna mapa na tagach i druga na koncie. Nie mają wspólnej właściwości, więc nie można tu uzyskać wielu map/redukcji.

Zamiast tego można użyć funkcji TransformResult. Oto jak to zrobić:

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 
                }; 
    } 
} 

Później następnie można zapytać tak:

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

Dzięki Daniel, nie wiedziałem o 'TransformResults'! Nie jest to jednak dokładnie to, co chciałem, byłem po jednym wyniku na konto, z właściwością zawierającą szczegóły tagu, zobacz moją edycję.Ponadto powyższy parametr TotalSum nie działa, ponieważ sumuje WSZYSTKIE wartości elementów, a nie tylko te dla konta, którego szukam (myślę, że redukcja musi grupować się na koncie zamiast tagu?) – Simon

+0

Nie możesz mieć indeks zawierający klasę zagnieżdżoną w jej wyniku. Nie sądzę, że możesz dostać to, czego chcesz w jednym indeksie. Zamiast tego wolałbym mieć kilka niezależnych indeksów i zapytać je wszystkie, aby uzyskać wszystkie potrzebne informacje lub zmienić model danych, aby lepiej pasował do tego modelu dostępu do danych. –

+0

Ok, dzięki Daniel. Myślałem, że tak być może, ale nie byłam zbyt pewna. Wielkie dzięki za twój czas. – Simon

Powiązane problemy