EF with TPT inheritance and audit fields

1

Hello.

I have already looked for answers in the TPT and Entity tags but I have not found anything similar.

My problem is when trying to use an inheritance model (TPT) where tables have fields with the same name (example: DataCadastro and Status ). At the time of inclusion of the record indicates the error indicating that it does not accept the null field. Below is the template:

public interface ICambioBase
{
    EStatus Status { get; set; }
    DateTime DataCadastro { get; set; }
}

public abstract class Usuário : ICambioBase
{
    public int Id { get; set; }
    public EPessoaFJ Tipo { get; set; }
    public string Nome { get; set; }
    public short NacionalidadeId { get; set; }
    public EStatus Status { get; set; }
    public DateTime DataCadastro { get; set; }

    public virtual País País { get; set; }

    public virtual ICollection<Documento> Documentos { get; set; }
    public virtual ICollection<Email> Emails { get; set; }
    public virtual ICollection<Telefone> Telefones { get; set; }
    public virtual ICollection<Endereço> Endereços { get; set; }
}

public class Pessoa : Usuário
{
    public Pessoa()
    {
        Tipo = EPessoaFJ.Física;
    }
    public ESexo Sexo { get; set; }
    public DateTime DataNascimento { get; set; }
    public EEstadoCivil EstadoCivilId { get; set; }
}

Code to enter Person:

        Pessoa pessoa = new Pessoa();

        pessoa.DataNascimento = Convert.ToDateTime("03/12/1976");
        pessoa.EstadoCivilId = EEstadoCivil.Casado;
        pessoa.País = ctx.Países.Single(b => b.Sigla == "BRA");
        pessoa.Nome = "Diogenes Morais de Santana";
        pessoa.DataCadastro = DateTime.Now;
        pessoa.Status = EStatus.Ativo;
        pessoa.Sexo = ESexo.Masculino;

        ctx.Pessoas.Add(pessoa);
        ctx.SaveChanges();


    ERRO: Cannot insert the value NULL into column 'DataCadastro', table 'CAMBIO2001.dbo._Pessoas'; column does not allow nulls. INSERT fails.The statement has been terminated.

Script Creation of Tables:

CREATE TABLE _Usuários
(
    Id              INT IDENTITY(1,1) PRIMARY KEY,
    Tipo            BIT NOT NULL, -- DOCUMENTO DE PESSOA FISICA OU JURIDICA
    Nome            VARCHAR(60) NOT NULL,
    NacionalidadeId SMALLINT NOT NULL FOREIGN KEY REFERENCES _Países(Id),
    Status          BIT NOT NULL,
    DataCadastro    DATETIME2(0) NOT NULL
);

CREATE TABLE _Pessoas
(
    Id              INT PRIMARY KEY FOREIGN KEY REFERENCES _Usuários(Id),
    Sexo            BIT NOT NULL,
    DataNascimento  DATE NOT NULL,
    EstadoCivilId   TINYINT NOT NULL FOREIGN KEY REFERENCES _EstadosCivis(Id),
    Status          BIT NOT NULL,
    DataCadastro    DATETIME2(0) NOT NULL
);

Context class with override of SaveChanges method:

        #region Tables
    public DbSet<País> Países { get; set; }
    public DbSet<Usuário> Usuários { get; set; }
    public DbSet<Pessoa> Pessoas { get; set; }
    public DbSet<Empresa> Empresas { get; set; }
    public DbSet<DocumentoTipo> DocumentosTipos { get; set; }
    public DbSet<Documento> Documentos { get; set; }
    public DbSet<Email> Emails { get; set; }
    public DbSet<Telefone> Telefones { get; set; }
    public DbSet<Endereço> Endereços { get; set; }
    public DbSet<ParceiroFunção> TiposParceiros { get; set; }
    public DbSet<Assessor> Assessores { get; set; }
    public DbSet<Apresentante> Apresentantes { get; set; }
    public DbSet<ClienteTipo> ClientesTipos { get; set; }
    public DbSet<Ocupação> Ocupações { get; set; }
    public DbSet<ClienteSituação> ClientesSituações { get; set; }
    public DbSet<Cliente> Clientes { get; set; }
    public DbSet<Banco> Bancos { get; set; }
    #endregion


    public Cotação_Contexto() : base("Cotacao_Connection")
    {
        Configuration.LazyLoadingEnabled = false;
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

        #region Models
        modelBuilder.Configurations.Add(new MapPaíses());
        modelBuilder.Configurations.Add(new MapUsuários());
        modelBuilder.Configurations.Add(new MapPessoas());
        modelBuilder.Configurations.Add(new MapEmpresas());
        modelBuilder.Configurations.Add(new MapDocumentosTipos());
        modelBuilder.Configurations.Add(new MapDocumentos());
        modelBuilder.Configurations.Add(new MapEmails());
        modelBuilder.Configurations.Add(new MapTelefones());
        modelBuilder.Configurations.Add(new MapEndereços());
        modelBuilder.Configurations.Add(new MapParceirosFunções());
        modelBuilder.Configurations.Add(new MapAssessores());
        modelBuilder.Configurations.Add(new MapApresentantes());
        modelBuilder.Configurations.Add(new MapOcupações());
        modelBuilder.Configurations.Add(new MapClientesSituações());
        modelBuilder.Configurations.Add(new MapClientesTipos());
        modelBuilder.Configurations.Add(new MapClientes());
        modelBuilder.Configurations.Add(new MapBancos());
        #endregion

        base.OnModelCreating(modelBuilder);
    }

    public override int SaveChanges()
    {
        var context = ((IObjectContextAdapter)this).ObjectContext;

        IEnumerable<ObjectStateEntry> objectStateEntries =
            from e in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified)
            where
                e.IsRelationship == false &&
                e.Entity != null &&
                typeof(ICambioBase).IsAssignableFrom(e.Entity.GetType())
            select e;

        var dataAtual = DateTime.Now;

        foreach (var entry in objectStateEntries)
        {
            dynamic entityBase = entry.Entity;

            if (entry.State == EntityState.Added || entityBase.DataCriacao == DateTime.MinValue)
            {
                entityBase.DataCadastro = dataAtual;
                entityBase.Status = EStatus.Ativo;
            }

        }

        //var entries = ChangeTracker.Entries();

        //foreach (var entry in entries)
        //  if (entry.State == EntityState.Added)
        //      foreach (var propname in entry.CurrentValues.PropertyNames)
        //          if (propname == "DataCadastro")
        //              entry.CurrentValues["DataCadastro"] = DateTime.Now;

        try
        {
            return base.SaveChanges();
        }
        catch (DbEntityValidationException e)
        {
            foreach (var eve in e.EntityValidationErrors)
            {
                Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
                    eve.Entry.Entity.GetType().Name, eve.Entry.State);
                foreach (var ve in eve.ValidationErrors)
                {
                    Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
                        ve.PropertyName, ve.ErrorMessage);
                }
            }
            throw;
        }
    }
}

ERROR: Can not insert the value NULL into column 'DataCadastro', table 'dbo._Pessoas'; column does not allow nulls. INSERT fails. The statement has been terminated

Any suggestions?

    
asked by anonymous 17.02.2016 / 14:29

2 answers

1

After a few days of breaking my head and leaving aside the issue of inheritance, I went back to the model with the inheritance modifying a small portion of the code. In the property of equal names I added a letter indicating the level and in the mapping of the property I indicated the name of the column of the table with HasCollumnName and then I was able to successfully insert the data. Following:

Model:

public abstract class Usuário
{
    public int Id { get; set; }
    public EPessoaFJ Tipo { get; set; }
    public string Nome { get; set; }
    public short NacionalidadeId { get; set; }
    public EStatus Status { get; set; }
    public DateTime DataCadastro { get; set; }

    public virtual País Nacionalidade { get; set; }
    public virtual List<Documento> Documentos { get; set; }
}
public class Pessoa : Usuário
{
    public ESexo Sexo { get; set; }
    public DateTime DataNascimento { get; set; }
    public EEstadoCivil EstadoCivilId { get; set; }
    public EStatus Statusb { get; set; } //inseri uma letra indicando o segundo nível
    public DateTime DataCadastrob { get; set; }
}

Mappings:

    public MapUsuários()
    {
        this.ToTable("_Usuários");

        HasKey(u => u.Id);

        Property(u => u.Tipo)
            .IsRequired();

        Property(u => u.Nome)
            .HasMaxLength(60)
            .IsRequired();

        Property(u => u.NacionalidadeId)
            .IsRequired();

        Property(u => u.Status)
            .IsRequired();

        Property(u => u.DataCadastro)
            .IsRequired();

        HasRequired(u => u.Nacionalidade).WithMany().HasForeignKey(u => u.NacionalidadeId);

        HasMany(u => u.Documentos).WithRequired(d => d.Usuário).HasForeignKey(d => d.UsuárioId);
    }
    public MapPessoas()
    {
        this.ToTable("_Pessoas");

        HasKey(p => p.Id);

        Property(p => p.Sexo)
            .IsRequired();

        Property(p => p.DataNascimento)
            .IsRequired();

        Property(p => p.EstadoCivilId)
            .IsRequired();

        Property(p => p.Statusb)
            .HasColumnName("Status") //Indicando o nome da coluna no banco
            .IsRequired();

        Property(p => p.DataCadastrob)
            .HasColumnName("DataCadastro")
            .IsRequired();

    }

Insertion test

private void btnIniciar_Click(object sender, EventArgs e)
    {
        País país = new País();
        país.Nome = "Brasil";
        país.Sigla = "BRA";

        ctx.Países.Add(país);

        DocumentoTipo dt = new DocumentoTipo();
        dt.Descrição = "Outros";
        dt.Tipo = EPessoaFJ.Física;

        ctx.DocumentosTipos.Add(dt);

        dt = new DocumentoTipo();
        dt.Descrição = "RG";
        dt.Tipo = EPessoaFJ.Física;

        ctx.DocumentosTipos.Add(dt);

        dt = new DocumentoTipo();
        dt.Descrição = "CNPJ";
        dt.Tipo = EPessoaFJ.Jurídica;

        ctx.DocumentosTipos.Add(dt);

        Pessoa pessoa = new Pessoa();
        pessoa.Tipo = EPessoaFJ.Física;
        pessoa.Nome = "Diogenes Morais de Santana";
        pessoa.Nacionalidade = ctx.Países.Local.Single(p => p.Sigla == "BRA");

        pessoa.Sexo = ESexo.Masculino;
        pessoa.DataNascimento = Convert.ToDateTime("XX/XX/XX76");
        pessoa.EstadoCivilId = EEstadoCivil.Casado;
        pessoa.Statusb = EStatus.Ativo;
        pessoa.DataCadastrob = DateTime.Now;
        pessoa.Documentos = new List<Documento>();
        Documento doc = new Documento();
        doc.DocumentoTipo = ctx.DocumentosTipos.Local.Single(dtip => dtip.Descrição == "RG");
        doc.Numero = "xxxxxxxx";
        doc.DataEmissão = Convert.ToDateTime("01/01/2001");
        doc.Emissor = "SSP/SP";
        doc.Nacionalidade = ctx.Países.Local.Single(p => p.Sigla == "BRA");

        pessoa.Documentos.Add(doc);

        ctx.Usuários.Add(pessoa);

        ctx.SaveChanges();
    }

I know it's not a technical solution, but it solved my problem.

Many thanks Gypsy.

    
22.02.2016 / 17:56
3

Your project catches my attention by not following several recommended standards for the Entity Framework, starting with the non-use of Migrations , which saves you from generating scripts to update your base.

The correct way to do what you want, as to the date of registration, is to reimplement the SaveChanges method in context . This does not depend on a builder (which, incidentally, is very strange in your example) and does not have to make unnecessary inheritance. For this case, an interface works best, just because a class can only inherit one and only one class, and if you actually need to inherit another class, you will have problems.

Another criticism that deserves special attention is the fact that you are using strings where you could use Enums . For example:

pessoa.EstadoCivil = ctx.EstadosCivis.Where(ec => ec.Descrição.ToLower().Contains("casado")).Single();

EstadoCivil nor should it be a Model . You will insert civil status very seldom in the life cycle of your system. In addition, the following sentence causes an unnecessary performance burden for business logic.

Finally, this:

pessoa.Pais = ctx.Paises.Where(b => b.Sigla == "BRA").Single();

Can be rewritten as follows:

pessoa.Pais = ctx.Paises.Single(b => b.Sigla == "BRA");
    
17.02.2016 / 15:41