#c# #sql-server #entity-framework #entity-framework-core
#c# #sql-сервер #entity-framework #entity-framework-core
Вопрос:
Я импортирую данные из устаревшей базы данных в новый проект, я немного смущен некоторыми SQL, сгенерированными EF Core (я посмотрел на это в представлении вывода, поскольку он выполнялся очень плохо по сравнению с тем, что я ожидал).
Я ориентируюсь на SQL Server и создаю объекты, которые имеют вложенные свойства типа Image (a poco) вот пример того, как это используется для объекта с таким свойством :
MyObject.Thumbnail = new Image()
{
Name = Thumbnail.GetValue("Name"),
OriginalData = ImageData,
Data = ImageData,
Width = bmp.Width,
Height = bmp.Height,
Format = ImageFormat.Jpg
};
Я остаюсь в замешательстве по поводу сгенерированного SQL (и, честно говоря, я этого не понимаю, это объявление временной таблицы с последующим объединением? Почему?) и по его производительности (для вставки 10 элементов с 2 свойствами изображения каждый требуется 17 секунд, я запускаю SQL Server локально на том же компьютере, что и процесс .net core, выполняющий запросы, поэтому нет задержки в сети и бесконечной полосы пропускания, а массивы байтов довольно малы для начала, в основном в диапазоне 300 кб).
Ожидаемой производительностью было бы удаление одного-двух нулей.
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (17,211ms) [Parameters=[@p0='?' (Size = -1) (DbType = Binary), @p1='?' (Size = 4000), @p2='?' (DbType = Int32), @p3='?' (DbType = Int32), @p4='?' (Size = 4000), @p5='?' (Size = -1) (DbType = Binary), @p6='?' (DbType = Int32), @p7='?' (Size = -1) (DbType = Binary), @p8='?' (Size = 4000), @p9='?' (DbType = Int32), @p10='?' (DbType = Int32), @p11='?' (Size = 4000), @p12='?' (Size = -1) (DbType = Binary), @p13='?' (DbType = Int32), @p14='?' (Size = -1) (DbType = Binary), @p15='?' (Size = 4000), @p16='?' (DbType = Int32), @p17='?' (DbType = Int32), @p18='?' (Size = 4000), @p19='?' (Size = -1) (DbType = Binary), @p20='?' (DbType = Int32), @p21='?' (Size = 8000) (DbType = Binary), @p22='?' (Size = 4000), @p23='?' (DbType = Int32), @p24='?' (DbType = Int32), @p25='?' (Size = 4000), @p26='?' (Size = 8000) (DbType = Binary), @p27='?' (DbType = Int32), @p28='?' (Size = -1) (DbType = Binary), @p29='?' (Size = 4000), @p30='?' (DbType = Int32), @p31='?' (DbType = Int32), @p32='?' (Size = 4000), @p33='?' (Size = -1) (DbType = Binary), @p34='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]);
MERGE [Images] USING (
VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, 0),
(@p7, @p8, @p9, @p10, @p11, @p12, @p13, 1),
(@p14, @p15, @p16, @p17, @p18, @p19, @p20, 2),
(@p21, @p22, @p23, @p24, @p25, @p26, @p27, 3),
(@p28, @p29, @p30, @p31, @p32, @p33, @p34, 4)) AS i ([Data], [Description], [Format], [Height], [Name], [OriginalData], [Width], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Data], [Description], [Format], [Height], [Name], [OriginalData], [Width])
VALUES (i.[Data], i.[Description], i.[Format], i.[Height], i.[Name], i.[OriginalData], i.[Width])
OUTPUT INSERTED.[Id], i._Position
INTO @inserted0;
Комментарии:
1. Выглядит как «upsert».
2. @RobertHarvey Есть идеи, почему при создании новых объектов будет выполняться upsert? (из поиска в Google upsert — объединенное обновление / вставка, я мог бы понять, если бы я делал что-то необычное, например, повторное извлечение объекта, но здесь я просто создаю новую иерархию объектов из пустой базы данных)
3. Можете ли вы добавить объявление объекта и любую конфигурацию, применяемую для него? (ModelBuilder / EntityTypeConfiguration)
4. Кажется, это преднамеренное поведение для пакетной вставки и сохранения идентификаторов … ( brentozar.com/archive/2017/05 /… ) Выглядит некрасиво, но, по-видимому, быстрее в целом. Сохранение двоичных изображений (дважды для каждого), вероятно, не будет быстрым.
5. Одна из миллиона причин, по которым я не использую EF. Это основано на предпосылке, что SQL сложный. Что это не так. Вы, очевидно, понимаете SQL, так зачем пытаться убедить другой уровень выполнять что-то эффективно. Хорошо, я признаю, что это 1-минутная напыщенная речь. Примите это или оставьте 🙂
Ответ №1:
Для SQL Server сохраняйте пакеты изменений по умолчанию. Для этого сценария (загрузка данных blob-объекта) пакетирование на самом деле плохое, поскольку вы не хотите, чтобы огромный набор параметров был привязан к клиенту и серверу, а затем передавался для загрузки. Вам нужны пакеты с одиночной вставкой (или, если ваши большие объекты действительно большие, потоковая передача SqlClient, для которой вам нужно перейти к ADO.NET ).
Вы настраиваете это в той же строке, в которой указываете DbContext использовать SQL Server, например:
optionsBuilder.UseSqlServer(constr, b => b.MaxBatchSize(1).UseRelationalNulls(true) );
Отключить пакетную обработку и отказаться от генерации запросов, которые эмулируют семантику сравнения C # null.
Если вам нужно условно отключить пакетную обработку, вы можете добавить параметр конструктора DbContext, который вы читаете при настройке OnConfiguring. НАПРИМЕР
public class Db : DbContext
{
bool disableBatching = false;
public Db() : base()
{
}
public Db(bool disableBatching)
{
this.disableBatching = true;
}
И если вам нужно убедить ваш DI-контейнер иногда выдавать DbContexts с отключенной пакетной обработкой, вы можете создать для этого новый подтип, поскольку большинство DI-контейнеров отрабатывают регистрацию типов. НАПРИМЕР:
public class NoBatchingDb : Db
{
public NoBatchingDb() : base(disableBatching: true) { }
}
Комментарии:
1. Поскольку я использую asp.net ядро контекст передается через DI, есть ли способ сконструировать его таким образом для этого одного очень специфического метода и при этом иметь пакетную обработку для всех других сценариев?
Ответ №2:
После прочтения некоторого кода EFCore я понял, что пакетирование может быть отключено для каждого объекта с помощью этого:
modelBuilder.Entity<MyEntity>().IsMemoryOptimized();
Не уверен, имеет ли это какие-либо другие последствия, хотя. Очевидно, оказывает влияние на SQL, сгенерированный миграциями.