ядро ef, почему оно генерирует этот запрос вместо простой вставки? (сохранить объектный график с байтом [] внутри) и связанная с этим проблема с производительностью

#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, сгенерированный миграциями.