EF Core 7 chega a ser 44x mais rápido!
Introdução
A cada versão do produto geralmente costumamos ver diversas features sendo implementadas no produto, sendo que na versão EF Core 6 e agora o EF Core 7 não contém tantas features como costumávamos a ver, isso tem um motivo, o produto está ficando cada vez mais maduro e estável, além disso foco tem sido em melhorias de performance e features já existentes.
A versão do EF Core 7 foi lançada oficialmente no 8 de novembro de 2022, ela não trouxe diversas novidades, mas trouxe algumas extremantes valiosas que são:
- Suporte JSON nativo
- Insert mais rápido
- Suporte atualização em massa
- Suporte exclusão em massa
Atualização em massa
A atualização e exclusão em massa foi um dos tópicos mais comentados e solicitados no github do projeto, inclusive o próprio team do EF Core fez uma apresentação onde disponibilizou um gráfico de performance e os ganhos que tivemos desde o EF 6, chegando a ser 44x mais rápido com as novas features implementadas.
Fonte imagens: PPT do Team EF Core
Entendendo o problema
var customers = db.Customers.Where(p => p.State == "SP");
foreach (var customer in customers)
{
customer.Discount = 10m;
}
db.SaveChanges();
Isso era o que a gente tinha para o momento, mas o custo de CPU e memória era muito alto, a instrução para atualização em massa era muito ruim dado que era gerado um comando para cada objeto afetado pela aplicação, veja um exemplo:
[Parameters=[@p1='967', @p0='10' (Precision = 18) (Scale = 2), @p3='968', @p2='10' (Precision = 18) (Scale = 2), @p5='969', @p4='10' (Precision = 18) (Scale = 2), @p7='970', @p6='10' (Precision = 18) (Scale = 2), @p9='971', @p8='10' (Precision = 18) (Scale = 2), @p11='972', @p10='10' (Precision = 18) (Scale = 2), @p13='973', @p12='10' (Precision = 18) (Scale = 2), @p15='974', @p14='10' (Precision = 18) (Scale = 2), @p17='975', @p16='10' (Precision = 18) (Scale = 2), @p19='976', @p18='10' (Precision = 18) (Scale = 2), @p21='977', @p20='10' (Precision = 18) (Scale = 2), @p23='978', @p22='10' (Precision = 18) (Scale = 2), @p25='979', @p24='10' (Precision = 18) (Scale = 2), @p27='980', @p26='10' (Precision = 18) (Scale = 2), @p29='981', @p28='10' (Precision = 18) (Scale = 2), @p31='982', @p30='10' (Precision = 18) (Scale = 2), @p33='983', @p32='10' (Precision = 18) (Scale = 2), @p35='984', @p34='10' (Precision = 18) (Scale = 2), @p37='985', @p36='10' (Precision = 18) (Scale = 2), @p39='986', @p38='10' (Precision = 18) (Scale = 2), @p41='987', @p40='10' (Precision = 18) (Scale = 2), @p43='988', @p42='10' (Precision = 18) (Scale = 2), @p45='989', @p44='10' (Precision = 18) (Scale = 2), @p47='990', @p46='10' (Precision = 18) (Scale = 2), @p49='991', @p48='10' (Precision = 18) (Scale = 2), @p51='992', @p50='10' (Precision = 18) (Scale = 2), @p53='993', @p52='10' (Precision = 18) (Scale = 2), @p55='994', @p54='10' (Precision = 18) (Scale = 2), @p57='995', @p56='10' (Precision = 18) (Scale = 2), @p59='996', @p58='10' (Precision = 18) (Scale = 2), @p61='997', @p60='10' (Precision = 18) (Scale = 2), @p63='998', @p62='10' (Precision = 18) (Scale = 2), @p65='999', @p64='10' (Precision = 18) (Scale = 2), @p67='1000', @p66='10' (Precision = 18) (Scale = 2)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
UPDATE [People] SET [Discount] = @p0
OUTPUT 1
WHERE [Id] = @p1;
UPDATE [People] SET [Discount] = @p2
OUTPUT 1
WHERE [Id] = @p3;
UPDATE [People] SET [Discount] = @p4
OUTPUT 1
WHERE [Id] = @p5;
UPDATE [People] SET [Discount] = @p6
OUTPUT 1
WHERE [Id] = @p7;
UPDATE [People] SET [Discount] = @p8
OUTPUT 1
WHERE [Id] = @p9;
UPDATE [People] SET [Discount] = @p10
OUTPUT 1
WHERE [Id] = @p11;
UPDATE [People] SET [Discount] = @p12
OUTPUT 1
WHERE [Id] = @p13;
UPDATE [People] SET [Discount] = @p14
OUTPUT 1
WHERE [Id] = @p15;
UPDATE [People] SET [Discount] = @p16
OUTPUT 1
WHERE [Id] = @p17;
UPDATE [People] SET [Discount] = @p18
OUTPUT 1
WHERE [Id] = @p19;
UPDATE [People] SET [Discount] = @p20
OUTPUT 1
WHERE [Id] = @p21;
UPDATE [People] SET [Discount] = @p22
OUTPUT 1
WHERE [Id] = @p23;
UPDATE [People] SET [Discount] = @p24
OUTPUT 1
WHERE [Id] = @p25;
UPDATE [People] SET [Discount] = @p26
OUTPUT 1
WHERE [Id] = @p27;
UPDATE [People] SET [Discount] = @p28
OUTPUT 1
WHERE [Id] = @p29;
UPDATE [People] SET [Discount] = @p30
OUTPUT 1
WHERE [Id] = @p31;
UPDATE [People] SET [Discount] = @p32
OUTPUT 1
WHERE [Id] = @p33;
UPDATE [People] SET [Discount] = @p34
OUTPUT 1
WHERE [Id] = @p35;
UPDATE [People] SET [Discount] = @p36
OUTPUT 1
WHERE [Id] = @p37;
UPDATE [People] SET [Discount] = @p38
OUTPUT 1
WHERE [Id] = @p39;
UPDATE [People] SET [Discount] = @p40
OUTPUT 1
WHERE [Id] = @p41;
UPDATE [People] SET [Discount] = @p42
OUTPUT 1
WHERE [Id] = @p43;
UPDATE [People] SET [Discount] = @p44
OUTPUT 1
WHERE [Id] = @p45;
UPDATE [People] SET [Discount] = @p46
OUTPUT 1
WHERE [Id] = @p47;
UPDATE [People] SET [Discount] = @p48
OUTPUT 1
WHERE [Id] = @p49;
UPDATE [People] SET [Discount] = @p50
OUTPUT 1
WHERE [Id] = @p51;
UPDATE [People] SET [Discount] = @p52
OUTPUT 1
WHERE [Id] = @p53;
UPDATE [People] SET [Discount] = @p54
OUTPUT 1
WHERE [Id] = @p55;
UPDATE [People] SET [Discount] = @p56
OUTPUT 1
WHERE [Id] = @p57;
UPDATE [People] SET [Discount] = @p58
OUTPUT 1
WHERE [Id] = @p59;
UPDATE [People] SET [Discount] = @p60
OUTPUT 1
WHERE [Id] = @p61;
UPDATE [People] SET [Discount] = @p62
OUTPUT 1
WHERE [Id] = @p63;
UPDATE [People] SET [Discount] = @p64
OUTPUT 1
WHERE [Id] = @p65;
UPDATE [People] SET [Discount] = @p66
OUTPUT 1
WHERE [Id] = @p67;
Resolvendo o problema
var rowsAffected = await db
.Customers
.Where(p => p.State == "SP")
.ExecuteUpdateAsync(p => p.SetProperty(x => x.Discount, 11m));
A instrução SQL ficou muito mais simplificada além de não rastrear os objetos mais na memória, em alguns testes não tão científicos que fiz em minha maquina enquanto testava outras coisas como Minimal API vs MVC, eu obitve uma performance em mais de 500%, isso é uma melhoria fantástica.
UPDATE [p]
SET [p].[Discount] = 11.0
FROM [Customers] AS [p]
WHERE [p].[State] = N'SP'
Métodos de extensão novos
- ExecuteUpdate
- ExecuteUpdateAsync
- ExecuteDelete
- ExecuteDeleteAsync
Para excluir registros em massa você pode executar o seguinte comando:
var rowsAffected = await db
.Customers
.Where(p => p.State == "SP")
.ExecuteDeleteAsync();
O comando executado na base de dados basicamente é mesmo que um humano escreveria.
DELETE FROM [p]
FROM [People] AS [p]
WHERE [p].[State] = N'SP'
Todos os métodos novos de extensão devolve a quantidade de registros que foram afetados na base de dados.
Contatos
twitter: @ralmsdeveloper
linkedin: @ralmsdeveloper
Deixe um comentário