Memory error when entering millions of records using Entity Framework

Asked

Viewed 548 times

7

I am using the Entity Framework to Insert and Update Thousands of Records.

At first it was slow, but after putting the code down it improved the speed.

db.Configuration.AutoDetectChangesEnabled = false;  
db.Configuration.ValidateOnSaveEnabled = false;

Speed Solved.

However, I can not reach the end because, as you insert, the model is getting large and increasing memory consumption until give me exception to reach 1,5GB of use.

Note: I have tried using the AsNoTracking().

I’m also trying to recharge the model from time to time, but it doesn’t lower consumption. It only increases.

Has anyone gone through this or has any idea?

Part of the code:

foreach (var prd in produtoGradeAux)
{
    if (dbPdv.Database.Connection.State != ConnectionState.Open)
       dbPdv.Database.Connection.Open();

using (var transaction = dbPdv.Database.Connection.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted))
{
    dbPdv.Database.UseTransaction(transaction);
    i++
    Produto prodAux = null;
    var pAux = dbPdv.produto_grade.AsNoTracking().FirstOrDefault(x => x.produto_GradeIdGw == prd.produto_gradeID);

    if (prd.cd_grade.Trim().Length > 6)
        pAux = dbPdv.produto_grade.AsNoTracking().FirstOrDefault(x => x.cd_grade.Trim() == prd.cd_grade.Trim());

    if (prd.cd_grade.Trim().Length > 6)
        prodAux = dbPdv.produto_grade.AsNoTracking().Where(x => x.Produto.cd_ref.Trim() == prd.cd_grade.Trim().Substring(0, 6)).Select(x => x.Produto).FirstOrDefault();

    int lnFamiliaId, lnGrupoId, lnUnidadeId, lnMarcaId, lnLinhaId;

    RetGrupos(prd, out lnFamiliaId, out lnGrupoId, out lnUnidadeId, out lnMarcaId, out lnLinhaId, dbPdv);

    if (pAux == null)
    {
        if (prodAux == null)
            prodAux = RetProduto(dbPdv, prd.Produto, lnFamiliaId, lnGrupoId, lnUnidadeId, lnMarcaId, lnLinhaId);
        pAux = RetProdutoGrade(dbPdv, prodAux, prd);
        SetProdutoEan(dbPdv, prd, pAux);
        SetProdutoCf(dbPdv, prd, pAux);
        SetProdutoEstoque(dbPdv, lojaAux, prd, pAux);
        SetProdutoPreco(dbPdv, prd, pAux);
    }
    else
    {
        AtuProdutoGrade(dbPdv, prd, pAux);
        AtuProduto(dbPdv, prd, pAux, lnFamiliaId, lnGrupoId, lnUnidadeId, lnMarcaId, lnLinhaId);
        AtuProdutoEan(dbPdv, prd, pAux);
        AtuProduto_Cf(dbPdv, prd, pAux);
        AtuProduto_Preco(dbPdv, prd, pAux);
        AtuProdutoEstoque(dbPdv, lojaAux, prd, pAux);
    }

    transaction.Commit();

    //Tentar Melhorar performance...
    if (i % 1000 == 0)
    {
        HabilitaDb(dbPdv);
        dbPdv.Dispose();
        dbPdv = GetDbPdv(pdvAux);
        DesabilitaDb(dbPdv);
    }
}  
}
  • You did what you should do and can’t help much more without knowing how your code is. Have you thought about doing it with SQL instead of EF? It does not need to be used for everything. It is possible that you have a memory leak. I answered this (specific case) earlier http://answall.com/a/84277/101

  • Opa.. Thank You For The Answer.. I will put Part of the Code in the Question

  • Each entity added to the context will continue there as long as the context exists or until you clear the context. Large-volume sequential entity processing you should do in batches, for example: every 100 entities you persist with and eliminate from context.

  • Opa Thanks @Caffé .. To Doing 1000 in 1000... I tried 500 too but I didn’t see much difference

  • 1

    Apart from your code (which I didn’t spend much time trying to understand) I’ll give you an algorithm suggestion: creates context, after X entities read, processed and persisted, sends the changes to the bank, destroys the context and creates another context for the next X entities. If you want to reuse the same context you will have to untie each entity so that they can be deleted from memory - I see no need; destroy context and create another new one is ok.

  • Good idea @Caffé.. I’m using the dbPdv.Dispose(); and then recreating it in through the GetDbPdv() in the Dbpdv Variable. Does It Not Work? It Really Wouldn’t Be Viable.

Show 1 more comment

2 answers

7


The Entity Framework was not made to enter 1 million records... It has an internal memory limit...(index dele) Asnotracking() will not solve... I have already performed the most complex tests possible.

For this type of insertion part for a ORM minor... with less management and details... in case a Microorm, as an example Dapper, Petapoco, etc...

If not feasible, use the Bulk Insert, which is an ADO.net implementation for the Entity framework. Entityframework.Bulkinsert

In my opinion, I recommend using in such cases the ADO.net for these processes that require performance in a large amount of data transition. Taking connection data from the context of the Entity framework or config. Because you are not limited only in INSERT(bulkinsert), you can perform other operations. A business option, in a large system, is to go to the Nhibernate. By personal experience and tests performed.


If so Entity Framework Core, see that link

  • Thank you @daniloloko.. I will evaluate these other alternatives..

  • 1

    After Switching to ADO.Net Solved.

5

In this answer, I explain several alternatives you can use to improve performance, but I do not believe that, even used together, they will solve your problem completely.

There are some things that caught my attention in your code. For example:

if (dbPdv.Database.Connection.State != ConnectionState.Open)
   dbPdv.Database.Connection.Open();

This is not necessary. The Entity Framework itself is responsible for controlling the connection lifecycle.

This:

using (var transaction = dbPdv.Database.Connection.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted))
{
    dbPdv.Database.UseTransaction(transaction);
    ...

It is also not aligned with the good practices of the Entity Framework (distributed transaction support). The correct thing would be to open one TransactionScope with the option of ReadUncommited in the IsolationLevel:

using (var scope = new TransactionScope(TransactionScopeOption.Required,
            new TransactionOptions()
            {
                IsolationLevel = IsolationLevel.ReadUncommitted
            }))
{
    ...

For your case, @Daniloloko’s answer is the way forward. The Nuget package from Entityframework.Bulkinsert is here.

  • Thanks for the Tips.. I will Change , and as you said I will take a look at Bulkinsert. Note: It Only Does Insert ? Update no?

  • 1

    Yes, just the Insert.

  • Will Insert Solve My Situation? Since I Also Do Updates.. Won’t Swell Memory?

  • 1

    As you do Insert and update, the way is to perform the Dispose of the context every 1000 or 2000 records.

  • Gee, I already do that as it may come up.. Even so Thank you for your answers..

Browser other questions tagged

You are not signed in. Login or sign up in order to post.