Почему первый запрос занимает больше времени? - PullRequest
1 голос
/ 30 марта 2019

Сегодня в каком-то эксперименте я заметил интересную вещь:

var dbContextOptionsBuilder = new DbContextOptionsBuilder<MyContext>();
dbContextOptionsBuilder.UseSqlServer(@"Data Source=LAPTOP-HBBAKRHO\SQLEXPRESS;Initial Catalog=myDb;Integrated Security=True");
var context = new MyContext(dbContextOptionsBuilder.Options);

Stopwatch stopWatch;

stopWatch = Stopwatch.StartNew();
context.Projects.AsNoTracking().SingleOrDefault(p => p.Id.Equals(12345));
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");

stopWatch = Stopwatch.StartNew();
context.Projects.AsNoTracking().SingleOrDefault(p => p.Id.Equals(12345));
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");

stopWatch = Stopwatch.StartNew();
context.Projects.AsNoTracking().SingleOrDefault(p => p.Id.Equals(12345));
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");

// CLOSE.
context.Dispose();

Результаты:

  • AsNoTracking (). SingleOrDefaultAsync, по идентификатору: 2457
  • AsNoTracking (). SingleOrDefaultAsync, по идентификатору: 51
  • AsNoTracking (). SingleOrDefaultAsync, по идентификатору: 29

Как видите, первый запрос всегда занимает больше времени. Почему это происходит?

Я думал, что ORM открывает / закрывает соединение с базой данных для каждого запроса, может быть, это не так, и EF Core открывает соединение только в первый раз и использует его для всех последующих запросов до тех пор, пока DbContext не удалится?

Ответы [ 2 ]

1 голос
/ 01 апреля 2019

Вы получаете данные по одному и тому же параметру из одного и того же экземпляра контекста каждый раз, поэтому второй и третий запросы get вообще не будут отправляться на SQL-сервер.EF имеет свой собственный кэш первого уровня всех сущностей, которые он загружает из базы данных.Вот почему второй и третий раз намного быстрее.Если вы измените параметр 12345 для второго запроса на любой другой, он будет быстрее, чем первый, но не так быстро, потому что он будет запрашивать данные с сервера.

Я бы посоветовал вам прочитать эти темы наВыполнения и производительность EF:

правильное управление ef

история кэша плана запросов

0 голосов
/ 01 апреля 2019

Первый запрос медленнее, потому что Entity Framework должен создать представления сопоставления из вашей модели.Это называется "компилирование вашей модели".Это происходит, когда вы выполняете свой первый запрос.

Итак, в вашем эксперименте первый запрос должен быть медленным, даже если он не возвращает никаких записей.

Так что, если вы попробуете что-то вроде этого:

stopWatch = Stopwatch.StartNew();
context.Projects.AsNoTracking().SingleOrDefault(p => p == -1); // record does not exist
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");

stopWatch = Stopwatch.StartNew();
context.Projects.AsNoTracking().SingleOrDefault(p => p == -2); // record does not exist
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");

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

Когда вы теперь берете другую сущность из своего контекста (давайте представим, что у вас есть dbset с именем employee в вашем контексте) и выполняетепервый запрос к этому dbset и выполнение запросов к проектам после этого, вы увидите, что запросы проектов будут выполняться намного быстрее.

stopWatch = Stopwatch.StartNew();
context.Employees.AsNoTracking().SingleOrDefault(e => e == -1); // record does not exist
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");


stopWatch = Stopwatch.StartNew();
context.Projects.AsNoTracking().SingleOrDefault(p => p == -1); // record does not exist
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");

stopWatch = Stopwatch.StartNew();
context.Projects.AsNoTracking().SingleOrDefault(p => p == -2); // record does not exist
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");

Один из способов обойти эту проблему - выполнить «фальшивку»."запрос во время запуска, чтобы первый пользовательский запрос не работал медленно

...