Почему беглый nhibernate не кэширует результаты, если использовать IEnumerable вместо List - PullRequest
0 голосов
/ 14 января 2012

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

Недавно я столкнулся с некоторыми проблемами производительности с Fluent Nhibernate, и я столкнулся с тем, что мне показалось очень странным. Когда я сделал IEnumerable, производительность List резко возросла. Я пытался понять, почему. Похоже, что это не так, и Google ничего не нашел.

Вот базовый тест, который я провел:

//Class has various built in type fields, but no references to anything
public class Something
{
  public int ID;
  public decimal Value;
}

var someRepository = new Repository(uow);

//RUN 1
var start = DateTime.Now;
// Returns a IEnumerable from a session.Linq<SomeAgg> based on the passed in parameters, nothing fancy. Has about 1300 rows that get returned.
var somethings = someRepository.GetABunchOfSomething(various, parameters);
var returnValue = SumAllFunction(somethings);
var timeSpent = DateTime.Now - start; //Takes {00:00:00.3580358} on my box

//RUN2
var start2 = DateTime.Now;
var returnValue = someFunction(somethings);
var timeSpent = DateTime.Now - start2; //Takes {00:00:00.0560000} on my box



public decimal SumAllFunction(IEnumerable<Something> somethings)
{
  return somethings.Sum(x => x.Value); //Value is a decimal that's part of the Something class
}

Теперь, если я возьму тот же код и просто изменю строку someRepository.GetABunchOfSomethingto и присоединен .ToList ():

//RUN 1
var start = DateTime.Now;
var somethings = someRepository.GetABunchOfSomething(various, parameters).ToList(); 
var returnValue = SumAllFunction(somethings);
var timeSpent = DateTime.Now - start; //Takes {00:00:00.3580358} on my box

//RUN 2
var start2 = DateTime.Now;
var returnValue = SumAllFunction(somethings);
var timeSpent = DateTime.Now - start2; //Takes {00:00:00.0010000} on my box

Больше ничего не изменилось. Эти результаты очень повторяемы. Так что это не просто вопрос времени.

Версия TLDR:

При запуске одного и того же IEnumerable через цикл дважды, второй запуск занимает в 10-20 раз больше времени, чем если бы я изменил IEnumerable на List с использованием .ToList (), прежде чем запускать его через 2 цикла.

Я проверил SQL, и когда это List, sql запускается только один раз и, кажется, кешируется и используется снова, вместо того, чтобы возвращаться в базу данных для получения результатов.

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

Я понимаю, что вы не можете добавлять / удалять из IEnumerable, но, насколько я понимаю, IEnumerable был бы изначально заполнен объектами-посредниками, а затем объекты-посредники впоследствии были бы гидратированы при необходимости. После того, как они были увлажнены, вам больше не нужно возвращаться в БД, но, похоже, это не так. У меня, очевидно, есть обходной путь для этого, но я подумал, что это странно, и мне было любопытно, почему он ведет себя так, как он делает.

1 Ответ

3 голосов
/ 14 января 2012

Когда вы вызываете ToList() для вашего GetABunchOfSomething результата, запрос выполняется в этот момент, и результаты помещаются в список. Если вы не вызываете ToList(), то только когда someFunction выполнит запрос, ваш таймер не примет это во внимание.

Я думаю, вы обнаружите, что разница во времени между ними обусловлена ​​этим.

Обновление

Результаты, хотя, возможно, и противоречивые для вас, имеют смысл. Причина, по которой запрос не выполняется до тех пор, пока вы не выполните итерацию, а также причина, по которой результаты не кэшируются, предоставляется в качестве функции. Скажем, вы хотите вызвать свой метод репозитория в двух местах кода; один раз отсортирован по Foo, другой раз отфильтрован по Bar. Если метод репозитория возвращает IQueryable<YourClass>, любые дополнительные изменения, внесенные в этот объект, будут фактически влиять на получаемый SQL, а не вызывать изменение коллекции в памяти. Например, если вы запустили это:

someRepository
    .GetABunchOfSomething(various, parameters)
    .Where(s => s.Bar == "SomeValue");

Сгенерированный SQL может выглядеть примерно так после итерации:

select *
from someTable
where Bar = 'SomeValue'

Однако, если вы сделали это вместо:

someRepository
    .GetABunchOfSomething(various, parameters)
    .ToList()
    .Where(s => s.Bar == "SomeValue");

Тогда вместо этого вы получите все строки из таблицы, и ваше приложение отфильтрует результаты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...