Получение всей коллекции данных из RavenDB
У меня есть требование, когда мне нужно собрать всю коллекцию данных Users
из RavenDB и сравнить полученный набор результатов с другим набором данных. В этой коллекции собрано около 4000 записей.
Поскольку Raven безопасен по умолчанию, я продолжаю получать исключение для Number of requests per session exceeded
или он возвращает максимум 128 записей.
Я не хочу устанавливать свойство Session.Advanced.MaxNumberOfRequestsPerSession
на более высокое значение.
Какой запрос я должен использовать для подсчета всех записей? Каков идеальный подход к решению этой ситуации?
Ответы
Ответ 1
Вы используете пейджинг и читаете эти 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
Этот вопрос был опубликован до того, как эта функция была доступна в 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...
Ответ 3
Основываясь на ответе Айенде, вот полный метод, который преодолевает проблему 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;
}
Надеюсь, это не слишком хреново, чтобы сделать это вот так.
Ответ 4
Я честно предпочитаю следующую функцию:
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
Ответ 5
С небольшим завихрением на @capaj 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;
}
Пример того, где/как я использую это, - это сделать PatchRequest
в RavenDb с помощью сеанса 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);
}
}
}
Надеюсь, это поможет.:)
Ответ 6
Мне нравится решение Al Dass для получения идентификаторов для работы вместо полных больших объектов. Также получение идентификаторов непосредственно из индекса. Однако рекурсия меня немного пугает (хотя я думаю, что это может быть нормально), и я удалил отражение.
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