Пользовательский запрос счетчика EF Core

Я работаю над небольшим проектом ASP.NET Core для пометки изображений с использованием Entity Framework Core в базе данных Sqlite, в основном только для обучения. Есть две таблицы (и POCO), Теги и Изображения, где несколько тегов связаны с каждым изображением. Я пытаюсь подсчитать все изображения, с которыми связаны теги.

В простом SQL я бы написал SELECT COUNT(DISTINCT ImageId) FROM Tags, чтобы получить количество, а в LINQ я придумал _context.Tags.Select(t => t.Image).Distinct().Count(). Но этот запрос LINQ заставляет EF-Core объединять две таблицы, возвращать все строки, а затем выполнять Distinct и Count в коде.

Я пытался сделать _context.Tags.FromSql("SELECT COUNT(DISTINCT ImageId) FROM Tags"), но поскольку этот запрос возвращает только счетчик, вызов завершается ошибкой, потому что EF не может сопоставить результат с тегом. Я также пытался использовать _context.Database.FromSql<int>, но не смог найти никакой реальной документации по нему и, похоже, для него не существует IntelliSense.

На данный момент я сделал то, что подробно описано в разделе «ADO.NET» этого запись в блоге от Эрика Андерсона:

int count;
using (var connection = _context.Database.GetDbConnection())
{
    connection.Open();

    using (var command = connection.CreateCommand())
    {
        command.CommandText = "SELECT COUNT(DISTINCT ImageId) FROM Tags";
        string result = command.ExecuteScalar().ToString();

        int.TryParse(result, out count);
    }
}

Но лучший ли это способ добиться эффективного подсчета?


Изменить: вот запрос, который EF помещает в вывод отладки:

SELECT "t"."TagId", "t"."Content", "t"."ImageId", "t.Image"."ImageId", "t.Image"."FileName", "t.Image"."Path", "t.Image"."Url"
FROM "Tags" AS "t"
LEFT JOIN "Images" AS "t.Image" ON "t"."ImageId" = "t.Image"."ImageId"
ORDER BY "t"."ImageId"

person Daniel Dreier    schedule 17.09.2016    source источник


Ответы (2)


На данный момент вы не можете определить специальный результат. Хорошая новость заключается в том, что в настоящее время он находится в очереди: https://github.com/aspnet/EntityFramework/issues/1862

А пока вот метод расширения, который будет работать:

public static int IntFromSQL(this ApplicationDbContext context, string sql )
{
    int count;
    using (var connection = context.Database.GetDbConnection())
    {
        connection.Open();

        using (var command = connection.CreateCommand())
        {
            command.CommandText = sql;
            string result = command.ExecuteScalar().ToString();

            int.TryParse(result, out count);
        }
    }
    return count;
}

Применение:

int result = _context.IntFromSQL("SELECT COUNT(DISTINCT ImageId) FROM Tags");
person Tim Taber    schedule 17.09.2016
comment
Это было полезно и работает. Однако после подсчета количества результатов моего запроса я фактически выполняю запрос, используя .fromSql для получения результатов. и после запуска кода fromsql теперь генерирует исключение нулевой ссылки. Любой намек на это? - person Ricker Silva; 04.05.2018

Ваша исходная строка кода должна была сделать именно то, что вы хотели. Это также рекомендуется вместо встроенного SQL.

_context.Tags.Select(t => t.Image).Distinct().Count()

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

Способ проверки фактического запроса без нарушения работающего кода — использование MyLoggerProvider из документации Entity Framework Core.

https://docs.efproject.net/en/latest/miscellaneous/logging.html?highlight=logging

Как только регистратор будет зарегистрирован в коде, любой SQL-запрос, запущенный к серверу, будет отображаться в окне консоли и/или в файле c:\temp\log.txt.

Следующее сообщение журнала было создано при использовании Distinct() и Count() в таблицах базы данных примера веб-сайта.

SELECT COUNT(*)
FROM (
SELECT DISTINCT [a.Blog].[BlogId], [a.Blog].[Url]
FROM [Posts] AS [a]
INNER JOIN [Blogs] AS [a.Blog] ON [a].[BlogId] = [a.Blog].[BlogId]
) AS [t]Closing connection to database '***' on server 'tcp:**************'.

Наконец, поскольку вам не нужны никакие свойства t.Image, кажется, что вы должны использовать Where(), а не Select().

person LittleDebugger    schedule 17.09.2016
comment
Я добавил запрос, который отображается в выводе отладки, к моему вопросу. Я только когда-либо переступал вызов LINQ, но никогда не входил в него. Но я настрою ведение журнала и посмотрю, что будет записано во время работы в Release. - person Daniel Dreier; 18.09.2016
comment
Запуск в отладке или выпуске не должен иметь никакого значения, но попробовать стоит. - person LittleDebugger; 18.09.2016
comment
Странно, что в запросе указан порядок. Что получится, если убрать выделение? _context.Tags.Distinct().Count() - person LittleDebugger; 18.09.2016
comment
Запрос без выбора SELECT COUNT(*) FROM (SELECT DISTINCT "t"."TagId", "t"."Content", "t"."ImageId" FROM "Tags" AS "t") AS "t0". Это соответствует тому, что я ожидал, если бы хотел считать теги. Но я ищу количество изображений с тегами. (Я ничего не передавал в Distinct(), поэтому, думаю, именно поэтому он включил туда все столбцы) - person Daniel Dreier; 20.09.2016
comment
Я настроил собственный регистратор, и запрос был таким же, как я отредактировал в своем вопросе. Не могли бы вы привести пример замены Select() на Where()? - person Daniel Dreier; 20.09.2016