zzzprojects / EntityFramework-Extensions

Entity Framework Bulk Operations | Improve Entity Framework performance with Bulk SaveChanges, Insert, update, delete and merge for SQL Server, SQL Azure, SQL Compact, MySQL and SQLite.
https://entityframework-extensions.net
346 stars 57 forks source link

EF Core 7 BulkInsert #528

Closed viniciusverasdossantos closed 1 year ago

viniciusverasdossantos commented 1 year ago

Description

Describe the issue or proposed feature. I'm migrating my project from EF6 to EF Core 7 and I'm having a problem with a bulk insert in a specific entity

Code:

 Action<BulkOperation<ProdutoBase>> operation = b =>
                    {
                       b.AutoMapOutputDirection = false;
                    };
                    session.BulkInsert(registros, operation);

EntityTypeConfiguration:

public class ProdutoBaseDbMapping : IEntityTypeConfiguration<ProdutoBase>
    {
        public void Configure(EntityTypeBuilder<ProdutoBase> builder)
        {
            builder.HasKey(p => p.Id);
            builder.ToTable("ProdutoBase")
                .HasDiscriminator<string>("Discriminator")
                .HasValue<Produto>("Produto")
                .HasValue<Servico>("Servico");

            builder.Ignore(p => p.DesconsiderarObrigatoriedadesConfiguradasNaInclusao);
            builder.Ignore(p => p.ClassificacaoFiscalEmCache);
            builder.Property(p => p.CodigoPrincipal).HasMaxLength(ColumnLength.Codigo);
            builder.Property(p => p.CodigoDeBarras).HasMaxLength(ColumnLength.Codigo);
            builder.Property(p => p.Nome).HasMaxLength(ColumnLength.DescricaoProduto).IsRequired();
            builder.Property(p => p.Referencia).HasMaxLength(ColumnLength.Codigo);
            builder.Property(p => p.Observacao).HasMaxLength(ColumnLength.Observacao);
            builder.Property(p => p.Aplicacao).HasMaxLength(5000);            

            builder.Property(p => p.AliquotaDeDescontoMaximo).HasPrecision(18, 3).IsRequired(false);
            builder.Property(p => p.AliquotaDeComissaoDeVendedores).HasPrecision(18, 3).IsRequired(false);

            builder.Property(p => p.ValorDeComissaoDeVendedores).HasPrecision(18, 3);

            builder.HasIndex(e => e.CodigoPrincipal);
            builder.HasIndex(e => e.CodigoDeBarras);
            builder.HasIndex(e => e.Referencia);
            builder.HasIndex(e => e.Inativo);
            builder.HasIndex(e => e.Nome); 

            builder.OwnsOne(p => p.ProdutoCurvaABC);
            builder.Navigation(x => x.ProdutoCurvaABC).IsRequired();
            builder.OwnsOne(p => p.DadosComercioEletronico);
            builder.Navigation(p => p.DadosComercioEletronico).IsRequired();

            builder.HasOne(p => p.CadeiaDePreco).WithMany().HasForeignKey(p => p.IdCadeiaDePreco).OnDelete(DeleteBehavior.NoAction);
            builder.HasOne(p => p.FamiliaProduto).WithMany().HasForeignKey(p => p.IdFamiliaProduto).OnDelete(DeleteBehavior.NoAction);
            builder.HasOne(p => p.UnidadeDeEstocagem).WithMany().HasForeignKey(p => p.IdUnidadeDeEstocagem).OnDelete(DeleteBehavior.NoAction);
            builder.HasOne(p => p.UnidadeTributavel).WithMany().HasForeignKey(p => p.IdUnidadeTributavel).OnDelete(DeleteBehavior.NoAction);
            builder.HasOne(p => p.Secao).WithMany().HasForeignKey(p => p.IdSecao).OnDelete(DeleteBehavior.NoAction);
            builder.HasOne(p => p.CategoriaComercioEletronicoPrincipal).WithMany().HasForeignKey(p => p.IdCategoriaComercioEletronicoPrincipal).OnDelete(DeleteBehavior.NoAction);
            builder.HasOne(p => p.Grupo).WithMany().HasForeignKey(p => p.IdGrupo).OnDelete(DeleteBehavior.NoAction); 
            builder.HasOne(p => p.ClassificacaoFiscal).WithMany(a => a.Produtos).HasForeignKey(p => p.IdClassificacaoFiscal).OnDelete(DeleteBehavior.NoAction);
            builder.HasOne(p => p.Marca).WithMany().HasForeignKey(p => p.IdMarca).OnDelete(DeleteBehavior.NoAction);            
            builder.HasOne(p => p.FornecedorPrincipal).WithMany().HasForeignKey(p => p.IdFornecedorPrincipal).OnDelete(DeleteBehavior.NoAction);
            builder.HasOne(p => p.Mensagem).WithMany().HasForeignKey(p => p.IdMensagem).OnDelete(DeleteBehavior.NoAction);

            builder.HasMany(i => i.CategoriasDeComercioEletronico)
               .WithMany()
               .UsingEntity<CategoriasDeComercioEletronicoDoProduto>(
                   m => m.HasOne(x => x.CategoriaComercioEletronico).WithMany().HasForeignKey(x => x.IdCategoriasDeComercioEletronicoDoProduto),
                   m => m.HasOne(x => x.ProdutoBase).WithMany().HasForeignKey(x => x.IdProdutoBase)
               )
               .HasKey(x => new { x.IdCategoriasDeComercioEletronicoDoProduto, x.IdProdutoBase });

            builder.HasMany(i => i.Caracteristicas)
                .WithMany()
                .UsingEntity<CaracteristicaDoProduto>(
                    m => m.HasOne(x => x.CaracteristicaDeProdutos).WithMany().HasForeignKey(x => x.IdCaracteristicaDeProdutos),
                    m => m.HasOne(x => x.ProdutoBase).WithMany().HasForeignKey(x => x.IdProdutoBase)                    
                )
                .HasKey(x => new { x.IdCaracteristicaDeProdutos, x.IdProdutoBase });

            builder.HasMany(i => i.ProdutosRelacionados)
              .WithMany()
                .UsingEntity<ProdutosRelacionados>(
                    m => m.HasOne(x => x.ProdutoRelacionado).WithMany().HasForeignKey(x => x.IdProdutoRelacionado).OnDelete(DeleteBehavior.NoAction),
                    m => m.HasOne(x => x.ProdutoBase).WithMany().HasForeignKey(x => x.IdProdutoBase).OnDelete(DeleteBehavior.NoAction)
                )
                .HasKey(x => new { x.IdProdutoRelacionado, x.IdProdutoBase });

        }
    }

Exception

Value cannot be null. (Parameter 'key')

Log:

info: Microsoft.EntityFrameworkCore.Database.Command[0]
      -- Executing Command:

              SELECT column_name, data_type FROM Information_Schema.Columns
              WHERE table_schema = 'dbo'
              AND  table_name = 'produtobase'
              ORDER BY data_type

      -- CommandTimeout:7200
      -- Executing at 18/04/2023 04:52:25

info: Microsoft.EntityFrameworkCore.Database.Command[0]
      -- Completed at 18/04/2023 04:52:25
      -- Result: SqlDataReader

info: Microsoft.EntityFrameworkCore.Database.Command[0]
      -- Failed with error: Value cannot be null. (Parameter 'key')

Exception message:

Stack trace:
   at System.ThrowHelper.ThrowArgumentNullException(String name)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at .`1.( , String )
   at .( , DbConnection , DbTransaction )
   at .Execute(List`1 actions)
   at Z.BulkOperations.BulkOperation.Execute()
   at .BulkInsert[T](DbContext this, IEntityType entityType, IEnumerable`1 list, Action`1 options, SavingSelector savingSelector, Boolean forceSpecificTypeMapping)
   at .BulkInsert[T](DbContext this, IEnumerable`1 entities, Action`1 options, Boolean isBulkSaveChanges)
   at DbContextExtensions.BulkInsert[T](DbContext this, IEnumerable`1 entities, Action`1 options)
   at Vvs.CPlusAnywhere.ConversorDeDados.Importacao.Core.Importador.<>c__DisplayClass271_0`1.<ExecutaImportacao>b__0() in C:\repos\CPlus5\Aplicacao\ConversorDeDados\Importador.cs:line 7323

Further technical details

JonathanMagnan commented 1 year ago

Hello @viniciusverasdossantos

Thank you for all the information. We pretty much know exactly where the code has an issue, but unfortunately, your code looks a little bit too much complex to try to reproduce on our side (we might surely miss something if we do).

Do you think you could create a runnable project with the issue? It doesn’t need to be your project, just a new solution with the minimum code to reproduce the issue. You can send it in private here: info@zzzprojects.com

Best Regards,

Jon

viniciusverasdossantos commented 1 year ago

bugzzz.zip

I discovered that the problem is in the Owned DadosComercioEletronico property

If I just ignore it in the mapping...everything works. I tried to reproduce in the seaprado project but it has many dependencies and the problem did not occur.

It works by adding: builder.Ignore(p => p.DadosComercioEletronico); rather than: builder.OwnsOne(p => p.DadosComercioEletronico); builder.Navigation(p => p.DadosComercioEletronico).IsRequired();

Could you help?

JonathanMagnan commented 1 year ago

Hello @viniciusverasdossantos ,

Unfortunately, we do not have the error in your project.

We had to delete the existing row prior to insert

using (var context = new ProductContext())
{
    context.Products.DeleteFromQuery();
}

But besides that, the code seems to work perfectly without an error.

Could you double-check the code you provided to make sure it contains the error?

Best Regards,

Jon

LuisNebreda commented 1 year ago

Hi,

I have similar issue (I think for same reason), in my case it happens only when an entity and it childs list has complex types props and we try to enter a value on a non nullable field which the library detects at null, on this example, it fails if we try to save a 0 enum value but not on any other value on same enumerator (it fails also if the complex type has a GUID and we try to sabe a GUID empty). Here you have the example https://dotnetfiddle.net/QfHYDT, as you can see removing the line when we try to save first enum value it works (also if we mark the enum as nullable). Do you also think it could be same?

JakobFerdinand commented 1 year ago

Hello,

we have the exact same error. I think it happens when the owned/complex type is used more than once. In the sample of @LuisNebreda it´s the type Props.

The reason is that EF Core returns null when you call DbContext.Model.FindEntityType(typeof(Props)) what you do here. image image image

LuisNebreda commented 1 year ago

I think you're right, and it has an error retrieving it type. Because in my case it happens only if we try to save a record with a value than "looks" to be null (like 0 on enum or guid empty) on that object appearance twice image

image if not it works image So, I thought, some way, is not evaluating correctly if it's null or not, surely, as you said, because is not retrieving it type properly

JonathanMagnan commented 1 year ago

Hello @LuisNebreda ,

Thank you for the Fiddle,

My developer confirms that is indeed a bug, he is currently looking at it.

Best Regards,

Jon

viniciusverasdossantos commented 1 year ago

Do you have an update planned? Stopped my tests

JonathanMagnan commented 1 year ago

Hello @viniciusverasdossantos ,

Sorry for the delay, I forgot to notify you that we released the fix for this issue on Tuesday.

Could you try the latest version and let us know if everything work now?

Best Regards,

Jon

viniciusverasdossantos commented 1 year ago

Worked, thank you! I'm having a problem with UpdateFromQuery now. MAs in the previous version was already. I will open issue.

LuisNebreda commented 1 year ago

Working for me too, thanks a lot