2012-06-29 3 views
15

У меня есть требование, когда мне нужно извлечь весь сбор данных Users из RavenDB и сравнить полученный набор результатов с другим набором данных. В этой коллекции собрано около 4000 записей.Извлечение всей коллекции данных из RavenDB

Поскольку ворон безопасен По умолчанию, я продолжаю получать исключение для Number of requests per session exceeded или он возвращает максимум 128 записей.

Я не хочу устанавливать свойство Session.Advanced.MaxNumberOfRequestsPerSession на большее значение.

Какой запрос следует использовать для подсчета всех записей? Каков идеальный подход к решению этой ситуации?

ответ

18

Вы используете пейджинг и читаете эти 1024 элемента за раз.

int start = 0; 
while(true) 
{ 
    var current = session.Query<User>().Take(1024).Skip(start).ToList(); 
    if(current.Count == 0) 
      break; 

    start+= current.Count; 
    allUsers.AddRange(current); 

} 
+2

Я вижу, что выше решение работает потому, что Total No. записей около 4000, поэтому никаких запросов не будет <30. Просто любопытно, как бы справиться с аналогичным сценарием, в котором Total no records> 30 * 1024, то есть если они более чем говорят 31k? – annantDev

+1

@annantDev Вы можете отслеживать количество запросов, сделанных в сеансе. Как только он достигнет 30, удалите старый сеанс, создайте новый сеанс и продолжите чтение. –

1

Построение на ответ Ayende, вот полный метод, который действительно решить проблему 30 запросов на каждую сессию и действительно все документы поставляемого класса:

public static List<T> getAll<T>(DocumentStore docDB) { 
     return getAllFrom(0, new List<T>(), docDB); 
    } 

    public static List<T> getAllFrom<T>(int startFrom, List<T> list, DocumentStore docDB) { 
     var allUsers = list; 

     using (var session = docDB.OpenSession()) 
     { 
      int queryCount = 0; 
      int start = startFrom; 
      while (true) 
      { 
       var current = session.Query<T>().Take(1024).Skip(start).ToList(); 
       queryCount += 1; 
       if (current.Count == 0) 
        break; 

       start += current.Count; 
       allUsers.AddRange(current); 

       if (queryCount >= 30) 
       { 
        return getAllFrom(start, allUsers, docDB); 
       } 
      } 
     } 
     return allUsers; 
    } 

Я надеюсь, что это не слишком хаки, чтобы сделать это так.

+1

Ответ Ренато Ковариша имеет несколько преимуществ по сравнению с этим. Этот ответ использует рекурсию. – BrokeMyLegBiking

+0

@BrokeMyLegBiking Правда, рекурсия здесь не нужна. Но один из них, по крайней мере, обрабатывает 30 запросов за сеанс, что другой ответ, на который ссылается, не работает. –

1

я честно предпочитаю следующую функцию:

public IEnumerable<T> GetAll<T>() 
    { 
     List<T> list = new List<T>(); 

     RavenQueryStatistics statistics = new RavenQueryStatistics(); 

     list.AddRange(_session.Query<T>().Statistics(out statistics)); 
     if (statistics.TotalResults > 128) 
     { 
      int toTake = statistics.TotalResults - 128; 
      int taken = 128; 
      while (toTake > 0) 
      { 
       list.AddRange(_session.Query<T>().Skip(taken).Take(toTake > 1024 ? 1024 : toTake)); 
       toTake -= 1024; 
       taken += 1024; 
      } 
     } 

     return list; 
    } 

[] s

+0

Вы получите очень быстрый запрос 30 запросов за сеанс с этим кодом, как написано в любом случае, без расширения лимита запроса. –

1

С небольшим поворотом на @capaj's post. Ниже приведен общий способ получения всех идентификаторов документов в виде списка строк. Обратите внимание на использование Advanced.LuceneQuery<T>(idPropertyName), SelectFields<T>(idPropertyName) и GetProperty(idPropertyName), чтобы сделать вещи универсальными. По умолчанию предполагается, что "Id" является действительным свойством на заданном <T> (что должно быть в случае 99,999% времени). В случае, если у вас есть другое свойство, как ваш Id, вы можете его передать.

public static List<string> getAllIds<T>(DocumentStore docDB, string idPropertyName = "Id") { 
    return getAllIdsFrom<T>(0, new List<string>(), docDB, idPropertyName); 
} 

public static List<string> getAllIdsFrom<T>(int startFrom, List<string> list, DocumentStore docDB, string idPropertyName) { 
    var allUsers = list; 

    using (var session = docDB.OpenSession()) 
    { 
     int queryCount = 0; 
     int start = startFrom; 
     while (true) 
     { 
      var current = session.Advanced.LuceneQuery<T>().Take(1024).Skip(start).SelectFields<T>(idPropertyName).ToList(); 
      queryCount += 1; 
      if (current.Count == 0) 
       break; 

      start += current.Count; 
      allUsers.AddRange(current.Select(t => (t.GetType().GetProperty(idPropertyName).GetValue(t, null)).ToString())); 

      if (queryCount >= 28) 
      { 
       return getAllIdsFrom<T>(start, allUsers, docDB, idPropertyName); 
      } 
     } 
    } 
    return allUsers; 
} 

Пример где/как я использую это при создании в RavenDb с PatchRequest помощью сеанса BulkInsert. В некоторых случаях у меня могут быть сотни тысяч документов и я не могу позволить себе загружать все документы в памяти, чтобы повторно повторить их снова для операции патча ... таким образом, загрузка только их идентификаторов строк, чтобы перейти в Patch команда.

void PatchRavenDocs() 
{ 
    var store = new DocumentStore 
    { 
     Url = "http://localhost:8080", 
     DefaultDatabase = "SoMeDaTaBaSeNaMe" 
    }; 

    store.Initialize(); 

    // >>>here is where I get all the doc IDs for a given type<<< 
    var allIds = getAllIds<SoMeDoCuMeNtTyPe>(store);  

    // create a new patch to ADD a new int property to my documents 
    var patches = new[]{ new PatchRequest { Type = PatchCommandType.Set, Name = "SoMeNeWPrOpeRtY" ,Value = 0 }}; 

    using (var s = store.BulkInsert()){ 
     int cntr = 0; 
     Console.WriteLine("ID Count " + allIds.Count); 
     foreach(string id in allIds) 
     { 
      // apply the patch to my document 
      s.DatabaseCommands.Patch(id, patches); 

      // spit out a record every 2048 rows as a basic sanity check 
      if ((cntr++ % 2048) == 0) 
       Console.WriteLine(cntr + " " + id); 
     } 
    } 
} 

Надеюсь, это поможет. :)

11

Этот вопрос был размещен прежде, чем эта функция была доступна в RavenDB, но в случае, если кто-то в другом натыкается на это сейчас ...

Рекомендуемым способом сделать это через Streaming API. Клиент RavenDB выполняет пакетный поток, чтобы он мог автоматически «загружать» запросы/ответы на сервер/с сервера. Если вы решите использовать Streaming API, клиент предполагает, что вы «знаете, что делаете», и не проверяете пределы 128/1024/30, которые используются для регулярных запросов.

var query = session.Query<User>(); 
  
using (var enumerator = session.Advanced.Stream(query)) { 
    while (enumerator.MoveNext()) { 
        allUsers.Add(enumerator.Current.Document); 
    } 
} 

var count = allUsers.Count; 

Совет: Хотя это поощрял способ решить проблему ... Как правило, лучше, чтобы избежать ситуации, чтобы начать с. Что делать, если есть миллион записей? Это список allUsers станет огромным.Может быть, индекс или преобразование можно было бы сделать сначала, чтобы отфильтровать, какие данные вам действительно нужны для отображения пользователю/процессу? Это для целей отчетности? Может быть, RavenDB должен автоматически экспортироваться на сервер SQL с службами отчетов на нем? Etc ...

+0

Интерфейс Streaming API недоступен при использовании Shards в версии 2.5 и старше. (Не уверен относительно v3.0 +) –

1

Мне нравится решение Al Dass для получения ids для работы вместо полных крупных объектов. Также получение идентификаторов непосредственно из индекса. Однако рекурсия меня немного пугает (хотя я думаю, что это может быть хорошо), и я удалил отражение.

public List<string> GetAllIds<T>() 
{ 
var allIds = new List<string>(); 
IDocumentSession session = null; 

try 
{ 
    session = documentStore.OpenSession(); 
    int queryCount = 0; 
    int start = 0; 
    while (true) 
    { 
     var current = session.Advanced.DocumentQuery<T>() 
      .Take(1024) 
      .Skip(start) 
      .SelectFields<string>("__document_id") 
      .AddOrder("__document_id") 
      .ToList(); 

     if (current.Count == 0) 
      break; 
     allIds.AddRange(current); 

     queryCount += 1; 
     start += current.Count; 

     if (queryCount == 30) 
     { 
      queryCount = 0; 
      session.Dispose(); 
      session = documentStore.OpenSession(); 
     } 
    } 
} 
finally 
{ 
    if (session != null) 
    { 
     session.Dispose(); 
    } 
} 

return allIds; 
} 

также, это обновлено до ravendb 3

Смежные вопросы