Как избежать геометрического замедления с большими транзакциями Linq?

Я написал несколько действительно хороших библиотек для использования в LinqToSql. (Когда-нибудь, когда я успею подумать об этом, я могу сделать его открытым исходным кодом...:))

В любом случае, я не уверен, связано ли это с моими библиотеками или нет, но я обнаружил, что когда у меня есть большое количество измененных объектов в одной транзакции, а затем вызывается DataContext.GetChangeSet(), все начинает получать reaalllly slooowwwww, Когда я ворвался в код, я обнаружил, что моя программа вращает свои колеса, делая очень много сравнений Equals() между объектами в наборе изменений. Я не могу гарантировать, что это правда, но я подозреваю, что если в наборе изменений есть n объектов, то вызов GetChangeSet() вызывает каждый объект для сравнения с любым другим объектом для эквивалентности, т.е. В лучшем случае (n ^ 2-n)/2 вызывает Equals()...

Да, конечно, я мог бы совершать каждый объект по отдельности, но этот вид побеждает цель транзакций. И в программе, которую я пишу, у меня могло бы быть пакетное задание, содержащее 100 000 отдельных элементов, и все они должны выполняться вместе. Там собрано около 5 миллиардов сравнений.

Итак, вопрос в следующем: (1) правильно ли я оцениваю ситуацию? Получаете ли вы это поведение в чистом учебнике LinqToSql, или это то, что делают мои библиотеки? И (2) существует стандартное/разумное решение, чтобы я мог создать свою партию, не делая программу геометрически медленнее с каждым дополнительным объектом в наборе изменений?

Ответ 1

В конце концов я решил переписать партии так, чтобы каждый отдельный элемент сохранялся независимо, все в рамках одной большой транзакции. Другими словами, вместо:

var b = new Batch { ... };
while (addNewItems) {
  ...
  var i = new BatchItem { ... };
  b.BatchItems.Add(i);
}
b.Insert(); // that a function in my library that calls SubmitChanges()

.. вам нужно сделать что-то вроде этого:

context.BeginTransaction(); // another one of my library functions
try {
  var b = new Batch { ... };
  b.Insert(); // save the batch record immediately
  while (addNewItems) {
    ...
    var i = new BatchItem { ... };
    b.BatchItems.Add(i);
    i.Insert(); // send the SQL on each iteration
  }
  context.CommitTransaction(); // and only commit the transaction when everything is done.
} catch {
  context.RollbackTransaction();
  throw;
}

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