Зачем быть.Содержит медленно? Самый эффективный способ получить несколько объектов по первичному ключу?



каков наиболее эффективный способ выбора нескольких объектов по первичному ключу?



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?

}


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

577   5  

5 ответов:

обновление: с добавлением выражения в EF6, производительность обработки Перечислима.Содержит значительно улучшилось. Анализ в этом ответе велик, но в значительной степени устарел с 2013 года.

используя Contains в Entity Framework на самом деле очень медленно. Это правда, что он переводится в IN предложение в SQL и что сам SQL-запрос выполняется быстро. Но проблема и узкое место производительности заключается в переводе из вашего запроса LINQ в SQL. Дерево выражений, которое будет создано, расширяется в длинную цепочку OR конкатенации, потому что нет собственного выражения, которое представляет собой IN. Когда SQL создается это выражение многих OR s распознается и сворачивается обратно в SQL IN предложения.

это не значит, что с помощью 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

    Ничего не найдено.