учитывая список объектов, использующих C #, отправьте их в ravendb, не зная, какие из них уже существуют

#batch-processing #ravendb

#пакетная обработка #ravendb

Вопрос:

Дано 1000 документов со сложной структурой данных. например, для класса Car, который имеет три свойства, Make и Model и одно свойство Id.

Какой наиболее эффективный способ в C # отправить эти документы в raven db (предпочтительно в пакетном режиме) без необходимости запрашивать коллекцию raven по отдельности, чтобы найти, что обновлять, а что вставлять. На данный момент я должен действовать так. Что совершенно неэффективно. примечание: _session — это оболочка для IDocumentSession, где фиксируются вызовы SaveChanges и добавляются вызовы Store .

     private void PublishSalesToRaven(IEnumerable<Sale> sales)
    {
        var page = 0;
        const int total = 30;
        do
        {
            var paged = sales.Skip(page*total).Take(total);
            if (!paged.Any()) return;
            foreach (var sale in paged)
            {
                var current = sale;
                var existing = _session.Query<Sale>().FirstOrDefault(s => s.Id == current.Id);
                if (existing != null)
                    existing = current;
                else
                    _session.Add(current);
            }
            _session.Commit();
            page  ;
        } while (true);
    }
  

Ответ №1:

Похоже, что ваш код сеанса не отслеживается с помощью API RavenDB (у нас нет Add or Commit ). Вот как вы это делаете в RavenDB

 private void PublishSalesToRaven(IEnumerable<Sale> sales)
{
    sales.ForEach(session.Store);
    session.SaveChanges();
}
  

Комментарии:

1. сеанс, который вы видите, является оболочкой для IDocuentSession от Raven. Add вызывает метод Store . Фиксация вызывает SaveChanges. Что происходит с вашим кодом, когда я выполняю сеанс. Хранить для документа, который уже существует в raven? будет ли Raven достаточно умен, чтобы понять, что его нужно обновлять, а не добавлять?

2. FWIW помните, что ForEach — это нестандартный метод linq, вам либо нужен код, который реализует себя, какая-то библиотека, в которую он встроен, либо используйте стандартный блок foreach .

3. @afif — это правильно => если у вас есть продажи новых автомобилей, идентификатор == null? В противном случае идентификатор — это какое-то число?

4. @Pure.Krome на самом деле нет. Поскольку автомобили поступают из существующего хранилища, у них всегда есть идентификатор. И я использую тот же идентификатор, чтобы сохранить их в Raven.

Ответ №2:

Ваш пример кода вообще не работает. Основная проблема заключается в том, что вы не можете просто отключить ссылки и ожидать, что RavenDB распознает это:

 if (existing != null)
    existing = current;
  

Вместо этого вам нужно обновлять каждое свойство по одному:

 existing.Model = current.Model;
existing.Make = current.Model;
  

Таким образом вы можете облегчить отслеживание изменений в RavenDB и многих других фреймворках (например, NHibernate). Если вы хотите избежать написания этого интересного фрагмента кода, я рекомендую использовать AutoMapper:

 existing = Mapper.Map<Sale>(current, existing);
  

Еще одна проблема с вашим кодом заключается в том, что вы используете сеанс.Запрос, где вы должны использовать Session.Load. Помните: Если вы запрашиваете документ по его идентификатору, вы всегда захотите использовать Load!
Основное отличие состоит в том, что один использует локальный кеш, а другой нет (то же самое относится к эквивалентным методам NHibernate).

Хорошо, теперь я могу ответить на ваш вопрос: если я вас правильно понял, вы хотите сохранить кучу экземпляров Sale в своей базе данных, в то время как они должны быть либо добавлены, если они не существуют, либо обновлены, если они существуют. Верно? Один из способов — исправить ваш пример кода с помощью приведенных выше подсказок и позволить ему работать. Однако это приведет к выдаче одного ненужного запроса (сеанса.Загрузка (existingId)) для каждой итерации. Вы можете легко избежать этого, если настроите индекс, который выбирает все идентификаторы всех документов внутри вашей коллекции продаж. Прежде чем перебирать свои элементы, вы можете загрузить все существующие идентификаторы.

Тем не менее, я хотел бы знать, что вы на самом деле хотите сделать. Каков ваш домен / вариант использования?

Комментарии:

1. Спасибо, Дэниел, за эти указатели. Очень полезно. Мой вариант использования — периодически запускать процесс, который опрашивает таблицу SQL Server и отправляет обновления / вставки в Raven. У меня уже есть механизм, который идентифицирует измененные / новые записи в таблице sql server с момента последнего обновления Raven (через свойство LastModified datetime в классе Car).

2. Просто имейте в виду, что полностью полагаться на поля даты несколько опасно, когда дело доходит до синхронизации между двумя базами данных на разных серверах, поскольку системное время может измениться на одном из серверов… Кстати, если вы нашли мой ответ полезным, вы также можете пометить его как ответ.

Ответ №3:

Это то, что работает для меня прямо сейчас. Примечание: метод InjectFrom поступает из Omu.ValueInjecter (пакет nuget)

     private void PublishSalesToRaven(IEnumerable<Sale> sales)
    {
        var ids = sales.Select(i => i.Id);
        var existingSales = _ravenSession.Load<Sale>(ids);
        existingSales.ForEach(s => s.InjectFrom(sales.Single(i => i.Id == s.Id)));

        var existingIds = existingSales.Select(i => i.Id);
        var nonExistingSales = sales.Where(i => !existingIds.Any(x => x == i.Id));
        nonExistingSales.ForEach(i => _ravenSession.Store(i));

        _ravenSession.SaveChanges();
    }