简体   繁体   中英

Errors in SaveChanges using entity framework and PostgreSQL

With the codes below, I'm trying to insert a new object into my PostgreSQL database, using the entity framework:

Class campo

public class campo
{
    public int id { get; set; }
    public int id_extrator { get; set; }
    public string nome { get; set; }
    public bool campo_selecionado { get; set; }

}

Calling the Event:

private void btnSalvarDetalhesExtrator_Click(object sender, EventArgs e)
{
    try
    {
        _campoDal = new CampoDal();
        List<campo> listaCampos = new List<campo>();
        campo dadosCampo;
        for (int a = 0; a < gvCamposExtrator.RowCount; a++)
        {
            dadosCampo = new campo();
            dadosCampo.id_extrator = _idExtrator;
            dadosCampo.nome = (string)gvCamposExtrator.Rows[a].Cells["Nome"].Value;
            dadosCampo.campo_selecionado = (bool)gvCamposExtrator.Rows[a].Cells["Campo_Selecionado"].Value;
            _campoDal.AdicionarCampo(dadosCampo);
        }
    }
}

Adding to the database

public campo AdicionarCampo(campo dadosCampo)
{
    try
    {
        _dbContext.Campos.Add(new campo
            {
                nome = dadosCampo.nome,
                id_extrator = dadosCampo.id_extrator,
                campo_selecionado = dadosCampo.campo_selecionado
            }
        );
        _dbContext.SaveChanges();
        return dadosCampo;
    }
    catch (Exception ex)
    {
        throw;
    }
}

However, the error below is shown when I try to perform this action, which is similar to the ZombieCheck of SQLTransaction.

Error ZombieCheck Npgsql

Stack Tracer:

   em Npgsql.NpgsqlTransaction.CheckReady()
   em Npgsql.NpgsqlTransaction.get_DbConnection()
   em System.Data.Common.DbTransaction.get_Connection()
   em System.Data.Entity.Infrastructure.Interception.DbTransactionDispatcher.Dispose(DbTransaction transaction, DbInterceptionContext interceptionContext)
   em System.Data.Entity.Core.EntityClient.EntityTransaction.Dispose(Boolean disposing)
   em System.Data.Common.DbTransaction.Dispose()
   em System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
   em System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions options, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction)
   em System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass148_0.<SaveChangesInternal>b__0()
   em System.Data.Entity.Infrastructure.DefaultExecutionStrategy.Execute[TResult](Func`1 operation)
   em System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions options, Boolean executeInExistingTransaction)
   em System.Data.Entity.Core.Objects.ObjectContext.SaveChanges(SaveOptions options)
   em System.Data.Entity.Internal.InternalContext.SaveChanges()
   em System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
   em System.Data.Entity.DbContext.SaveChanges()
   em ConectorAditi.DAL.Concrete.CampoDal.AdicionarCampo(campo dadosCampo) na C:\Users\Joao Pedro\source\repos\ConectorAditi\ConectorAditi\DAL\Concrete\CampoDal.cs:linha 29

Trying to solve or give a temporary solution to this, instead of returning a throw in the catch block I initially did not write anything in the block or put it to return the object I was trying to add, as if it had worked.

public campo AdicionarCampo(campo dadosCampo)
{
    try
    {
        _dbContext.Campos.Add(new campo
            {
                nome = dadosCampo.nome,
                id_extrator = dadosCampo.id_extrator,
                campo_selecionado = dadosCampo.campo_selecionado
            }
        );
        return dadosCampo;
    }
    catch (Exception ex)
    {
        return dadosCampo;
    }
}

In this implementation the data is saved in the database, however in each loop interaction the SaveChanges saves the new object and those that have already been inserted, instead of inserting only the data of the new instantiated object.

I don't know if the problem may be there, but the code below refers to the context class of the database

public class ApplicationDataBase : DbContext
{
    private readonly string schema;
    public DbSet<assunto> Assuntos { get; set; }
    public DbSet<atualizacao_campos_realizada> AtualizacoesCamposRealizadas { get; set; }
    public DbSet<campo> Campos { get; set; }
    public DbSet<carga_realizada> CargaRealizadas { get; set; }
    public DbSet<data_base> DataBases { get; set; }
    public DbSet<extrator> Extratores { get; set; }
    public DbSet<tipo_carga> TiposCargas { get; set; }

    public ApplicationDataBase(string schema) : base("dbConectionString")
    {
        this.schema = schema;
    }
    protected override void OnModelCreating(DbModelBuilder builder)
    {
        builder.Conventions.Remove<PluralizingTableNameConvention>();
        Database.SetInitializer<ApplicationDataBase>(null);
        builder.HasDefaultSchema(this.schema);
        base.OnModelCreating(builder);
    }
}

Would anyone know where I can be wrong in this implementation and how can I solve the "ZombieCheck" problems of NpgsqlTransaction or even that of data duplication when using SaveChanges? I've tried some solutions and implementations but none has solved these cases.

EDIT:

As a temporary solution, I added the line below in the catch block that removes the entity that was added from context tracking, finding this solution through the comment made by @Sowmyadhar Gourishetty:

        catch (Exception ex)
        {
            _dbContext.Entry(dadosCampo).State = EntityState.Detached;

            return dadosCampo;
         }

However, I still haven't found a way to avoid the "ZombieCheck" error posted in the first image, thus preventing it from entering the catch block. If anyone can help, I appreciate it.

I think your issue may be that EF does not know what your primary key is.

Try adding the following attribute to property id in class campo :

[Key]
public int id { get; set; }

This attribute can be found in namespace System.ComponentModel.DataAnnotations .

Also, you don't need to create a new entity. You can just add the one you are passing on to the method. There is no need to return anything now. Also, there is no point on catching the exception if you are just going to rethrow it. The only time this is useful is if you need to add additional information about what is happening inside the method.

Your method is then reduced to this:

public void AdicionarCampo(campo dadosCampo)
{
    _dbContext.Campos.Add(dadosCampo);
    _dbContext.SaveChanges();
}

All this being said, your code will currently always add new records. If what you wish to do is update existing ones and only add new ones, then you will need to retrieve the objects from the context first (when they exist), update them and then call the SaveChanges method in the context.

One thing I will caution against is the fact that you are keeping an internal context instance ( _dbContext ). This means that you need to make sure the context is in the right state when you use it. The context is meant to be used as a unit of work, so you should only keep it alive in this manner if you are going to be allowing a series of operations that will ultimately be "committed" together. In fact, if this doesn't fix your issue, chances are you are doing something else with the DAL (and hence, the context) prior to these calls that may cause the issue.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM