Каков самый быстрый способ определить, существует ли строка с использованием Linq to SQL?
Мне не интересно содержимое строки, я просто хочу знать, существует ли строка. Столбец Name
является первичным ключом, поэтому будет либо 0, либо 1 подходящие строки. В настоящее время я использую:
if ((from u in dc.Users where u.Name == name select u).Count() > 0)
// row exists
else
// row doesn't exist
В то время как вышеописанное работает, он делает много ненужной работы, выбирая все содержимое строки (если оно существует). Создает ли следующий запрос более быстрый запрос:
if (dc.Users.Where(u => u.Name == name).Any())
... или есть еще более быстрый запрос?
Ответы
Ответ 1
Подход Count()
может выполнять дополнительную работу, поскольку (в TSQL) EXISTS
или TOP 1
часто намного быстрее; db может оптимизировать "есть ли хотя бы одна строка". Лично я использовал бы любую/предикатную перегрузку:
if (dc.Users.Any(u => u.Name == name)) {...}
Конечно, вы можете сравнить то, что каждый делает, наблюдая за TSQL:
dc.Log = Console.Out;
Ответ 2
Конечно
if (dc.Users.Where(u => u.Name == name).Any())
это лучше всего, и если несколько условий для проверки, то его очень просто написать как
Скажите, что вы хотите проверить пользователя для компании, затем
if (dc.Users.Where(u => u.ID== Id && u.Company==company).Any())
Ответ 3
Я думаю:
if (dc.Users.Any(u => u.Name == name)) {...}
- лучший подход.
Ответ 4
Для тех, кто заявляет, что Any() - это путь вперед, я провел простой тест в LinqPad с базой данных SQL CommonPasswords, 14 миллионов "давай или бери". Код:
var password = "qwertyuiop123";
var startTime = DateTime.Now;
"From DB:".Dump();
startTime = DateTime.Now;
if (CommonPasswords.Any(c => System.Data.Linq.SqlClient.SqlMethods.Like(c.Word, password)))
{
$"FOUND: processing time: {(DateTime.Now - startTime).TotalMilliseconds}\r\n".Dump();
}
else
{
$"NOT FOUND: processing time: {(DateTime.Now - startTime).TotalMilliseconds}\r\n".Dump();
}
"From DB:".Dump();
startTime = DateTime.Now;
if (CommonPasswords.Where(c => System.Data.Linq.SqlClient.SqlMethods.Like(c.Word, password)).Count() > 0)
{
$"FOUND: processing time: {(DateTime.Now - startTime).TotalMilliseconds}\r\n".Dump();
}
else
{
$"NOT FOUND: processing time: {(DateTime.Now - startTime).TotalMilliseconds}\r\n".Dump();
}
"From DB:".Dump();
startTime = DateTime.Now;
if (CommonPasswords.Where(c => c.Word.ToLower() == password).Take(1).Any())
{
$"FOUND: processing time: {(DateTime.Now - startTime).TotalMilliseconds}\r\n".Dump();
}
else
{
$"NOT FOUND: processing time: {(DateTime.Now - startTime).TotalMilliseconds}\r\n".Dump();
}
Вот переведенный SQL:
-- Region Parameters
DECLARE @p0 NVarChar(1000) = 'qwertyuiop123'
-- EndRegion
SELECT
(CASE
WHEN EXISTS(
SELECT NULL AS [EMPTY]
FROM [Security].[CommonPasswords] AS [t0]
WHERE [t0].[Word] LIKE @p0
) THEN 1
ELSE 0
END) AS [value]
GO
-- Region Parameters
DECLARE @p0 NVarChar(1000) = 'qwertyuiop123'
-- EndRegion
SELECT COUNT(*) AS [value]
FROM [Security].[CommonPasswords] AS [t0]
WHERE [t0].[Word] LIKE @p0
GO
-- Region Parameters
DECLARE @p0 NVarChar(1000) = 'qwertyuiop123'
-- EndRegion
SELECT
(CASE
WHEN EXISTS(
SELECT NULL AS [EMPTY]
FROM (
SELECT TOP (1) NULL AS [EMPTY]
FROM [Security].[CommonPasswords] AS [t0]
WHERE LOWER([t0].[Word]) = @p0
) AS [t1]
) THEN 1
ELSE 0
END) AS [value]
Вы можете видеть, что ЛЮБОЙ оборачивает запрос в другой слой кода, чтобы выполнить CASE "Где существует, то 1", где Count() просто добавляет команду Count. Проблема с обоими из них заключается в том, что вы не можете сделать Top (1), но я не вижу лучшего способа использования Top (1)
Результаты:
Из БД: НАЙДЕНО: время обработки: 13.3962
Из БД: НАЙДЕН: время обработки: 12.0933
Из БД: НАЙДЕНО: время обработки: 787.8801
Снова:
Из БД: НАЙДЕНО: время обработки: 13.3878
Из БД: НАЙДЕНО: время обработки: 12.6881
Из БД: НАЙДЕНО: время обработки: 780.2686
Снова:
Из БД: НАЙДЕНО: время обработки: 24.7081
Из БД: НАЙДЕНО: время обработки: 23.6654
Из БД: НАЙДЕНО: время обработки: 699.622
Без индекса:
Из БД: НАЙДЕНО: время обработки: 2395.1988
Из БД: НАЙДЕНО: время обработки: 390.6334
Из БД: НАЙДЕНО: время обработки: 664.8581
Теперь некоторые из вас могут думать, что это всего лишь миллисекунда или две. Однако различие было намного больше, прежде чем я поместил в него индекс; на несколько секунд.
Последнее вычисление началось с того, что я начал с представления, что ToLower() будет быстрее, чем LIKE, и я был прав, пока не попытался подсчитать и не поместил в него индекс. Я думаю, Lower() делает индекс нерелевантным.
Ответ 5
Я не согласен с тем, что выбор top 1 всегда будет превосходить подсчет выбора для всех реализаций SQL. Знаешь, все зависит от реализации. Любопытно, что даже характер данных, хранящихся в конкретной базе данных, также влияет на общий результат.
Давайте рассмотрим оба из них, как бы я их реализовал, если бы я это сделал: для обоих случаев оценка проекции (WHERE) - общий шаг.
Далее для выбора top 1 вам нужно будет прочитать все поля (если только вы не выбрали верхнюю часть 1 'x', например: select top 1 1). Это будет функционально эквивалентно IQueryable.Any(...)., За исключением того, что вы потратите некоторое время на мигание значения для каждого столбца первой встреченной записи, если EXISTS. Если в заявлении найдено SELECT TOP, проекция является усечением, если нет постпроекции proc (например, предложение ORDER BY). Этот препроцесс берет небольшую стоимость, но это дополнительная стоимость, если никакой записи не существует, и в этом случае полный проект все еще выполняется.
Для выбора счетчика препроцесс не выполняется. Проецирование выполняется, и если EXISTS является ложным, результат мгновен. Если EXISTS истинно, счетчик по-прежнему быстро, потому что это будет просто dW_Highest_Inclusive - dW_Lowest_Exclusive. С точностью до 500-26. Если существует ложь, результат будет еще более быстрым.
Таким образом, оставшийся случай: насколько быстро вызывается проекция и что вы теряете, выполняя полную проекцию? И ответ приводит к самой важной проблеме, которая заключается в следующем: поле [NAME] индексировано или нет! Если у вас есть индекс в [NAME], производительность любого запроса будет настолько близка, что она сводится к предпочтениям разработчиков.
В общем, я просто напишу от двух до четырех запросов linq и разницу во времени до и после.
- выберите счетчик
- выберите верхнюю часть 1
- выберите верхнюю часть 1 1
- выберите любой
Повторите все 4 с некластеризованным индексом в [NAME];