Зачем быть.Содержит медленно? Самый эффективный способ получить несколько объектов по первичному ключу?
каков наиболее эффективный способ выбора нескольких объектов по первичному ключу?
public IEnumerable<Models.Image> GetImagesById(IEnumerable<int> ids)
{
//return ids.Select(id => Images.Find(id)); //is this cool?
return Images.Where( im => ids.Contains(im.Id)); //is this better, worse or the same?
//is there a (better) third way?
}
Я понимаю, что я мог бы сделать некоторые тесты производительности для сравнения, но мне интересно, есть ли на самом деле лучший способ, чем оба, и я ищу некоторое просветление о том, что разница между этими двумя запросами, если таковые имеются, как только они были "переведены".
5 ответов:
обновление: с добавлением выражения в EF6, производительность обработки Перечислима.Содержит значительно улучшилось. Анализ в этом ответе велик, но в значительной степени устарел с 2013 года.
используя
Containsв Entity Framework на самом деле очень медленно. Это правда, что он переводится вINпредложение в SQL и что сам SQL-запрос выполняется быстро. Но проблема и узкое место производительности заключается в переводе из вашего запроса LINQ в SQL. Дерево выражений, которое будет создано, расширяется в длинную цепочкуORконкатенации, потому что нет собственного выражения, которое представляет собойIN. Когда SQL создается это выражение многихORs распознается и сворачивается обратно в SQLINпредложения.это не значит, что с помощью
Containsхуже, чем выдача одного запроса на элемент в вашемidsколлекция (первый вариант). Это, наверное, еще лучше - по крайней мере, не слишком большие коллекции. Но для больших коллекций это действительно плохо. Я помню, что я испытал некоторое время назадContainsзапрос с около 12.000 элементов, которые работали, но заняли около минуты, хотя запрос в SQL выполняется менее чем за секунду.возможно, стоит проверить производительность комбинации нескольких раундов в базе данных с меньшим количеством элементов в
Containsвыражение для каждой поездки туда и обратно.этот подход, а также ограничения использования
ContainsС Entity Framework показано и объяснено здесь:почему оператор Contains () так резко снижает производительность Entity Framework?
возможно, что необработанная команда SQL будет работать лучше всего в этой ситуации, что будет означать, что вы вызываете
dbContext.Database.SqlQuery<Image>(sqlString)илиdbContext.Images.SqlQuery(sqlString)здесьsqlStringэто SQL, показанный в ответе @Rune.Edit
вот некоторые размеры:
я сделал это на таблице с 550000 записей и 11 столбцов (идентификаторы начинаются с 1 без пробелов) и выбрал случайным образом 20000 идентификаторов:
using (var context = new MyDbContext()) { Random rand = new Random(); var ids = new List<int>(); for (int i = 0; i < 20000; i++) ids.Add(rand.Next(550000)); Stopwatch watch = new Stopwatch(); watch.Start(); // here are the code snippets from below watch.Stop(); var msec = watch.ElapsedMilliseconds; }2
var result = context.Set<MyEntity>().AsNoTracking() .Where(e => ids.Contains(e.ID)) .ToList();результат -> МС = 84.5 сек
этот крошечный эффект
AsNoTrackingочень необычно. Это означает, что узким местом является не материализация объекта (и не SQL, как показано ниже).для обоих тестов можно увидеть в SQL Profiler, что SQL-запрос поступает в базу данных очень поздно. (Я не измерял точно, но это было позже, чем 70 секунд.) Очевидно, что перевод этого запроса LINQ в SQL очень дорог.
3
var values = new StringBuilder(); values.AppendFormat("{0}", ids[0]); for (int i = 1; i < ids.Count; i++) values.AppendFormat(", {0}", ids[i]); var sql = string.Format( "SELECT * FROM [MyDb].[dbo].[MyEntities] WHERE [ID] IN ({0})", values); var result = context.Set<MyEntity>().SqlQuery(sql).ToList();результат -> МС = 5.1 сек
5
// same as Test 3 but this time using Database.SqlQuery var result = context.Database.SqlQuery<MyEntity>(sql).ToList();результат -> МС = 3.7 сек
мое понимание заключается в том, что
context.Database.SqlQuery<MyEntity>(sql)это то же самое, чтоcontext.Set<MyEntity>().SqlQuery(sql).AsNoTracking(), поэтому нет никакой разницы между тестом 4 и тестом 5.(длина результирующих наборов не всегда была одинаковой из-за возможных дубликатов после случайного выбора идентификатора, но это всегда было между 19600 и 19640 элементами.)
Edit 2
второй вариант, безусловно, лучше, чем первый. Первый вариант приведет к
ids.Lengthзапросы к базе данных, в то время как второй вариант можно использовать'IN'оператор в SQL-запросе. Это в основном превратит ваш запрос LINQ в нечто вроде следующего SQL:SELECT * FROM ImagesTable WHERE id IN (value1,value2,...)где значение1, значение2 и т. д. являются значения идентификаторов переменных. Однако имейте в виду, что я думаю, что в этом случае может быть верхний предел количества значений, которые могут быть сериализованы в запрос путь. Я посмотрю, смогу ли найти какую-нибудь документацию...
Я использую Entity Framework 6.1 и узнал, используя ваш код, что лучше использовать:
return db.PERSON.Find(id);вместо:
return db.PERSONA.FirstOrDefault(x => x.ID == id);производительность Find () против FirstOrDefault есть некоторые мысли по этому поводу.
Weel, недавно у меня была аналогичная проблема, и лучшим способом, который я нашел, была вставка списка contains в временную таблицу и после объединения.
private List<Foo> GetFoos(IEnumerable<long> ids) { var sb = new StringBuilder(); sb.Append("DECLARE @Temp TABLE (Id bitint PRIMARY KEY)\n"); foreach (var id in ids) { sb.Append("INSERT INTO @Temp VALUES ('"); sb.Append(id); sb.Append("')\n"); } sb.Append("SELECT f.* FROM [dbo].[Foo] f inner join @Temp t on f.Id = t.Id"); return this.context.Database.SqlQuery<Foo>(sb.ToString()).ToList(); }это не очень красиво, но для больших списков это очень эффективно.
преобразование списка в массив с помощью toArray () повышает производительность. Вы можете сделать это таким образом:
ids.Select(id => Images.Find(id)); return Images.toArray().Where( im => ids.Contains(im.Id));
Comments