Я работаю над небольшим проектом 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"
Ответ 1 · рейтинг 4 · Tim Taber
На данный момент вы не можете определить специальный результат. Хорошая новость заключается в том, что в настоящее время он находится в очереди: 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");
Ответ 2 · LittleDebugger
Ваша исходная строка кода должна была сделать именно то, что вы хотели. Это также рекомендуется вместо встроенного 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().