18
respostas

Como faço um relacionamento 1 para 1 no Entity Framework Core 2.0 ?? A chave tem que ser FK e PK.

`` Não estou conseguindo realizar um relacionamento 1:1 no entity Framework Core 2.0.

Tabelas:

1- Pessoa 2-PessoaFisica 3-PessoaJuridica

A tabela de Pessoa a chave é Identity, porém às tabelas de PessoaFisica e PessoaJuridica não são identity. Elas recebem a chave estrangeira de Pessoa. Ou seja, PessoaFisica e PessoaJuridica tem uma FK e PK de Pessoa. Como fazer isso aqui no entity core 2.0 ??

O que está errado ??

public class Pessoa {

public int PessoaId { get; set; } public int Tipo { get; set; }

public virtual PessoaFisica PessoaFisica { get; set; } public virtual PessoaFisica PessoaJuridica { get; set; }

}

public class PessoaFisica {

public string Nome { get; set; } public string RG { get; set; } public string CPF { get; set; } public string Sexo { get; set; }

public virtual Pessoa Pessoa { get; set; }

}

public class PessoaJuridica {

public string RazaoSocial { get; set; } public string CNPJ { get; set; } public string IE { get; set; }

public virtual Pessoa Pessoa { get; set; }

}

public class PessoaConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { // Tabela builder.ToTable("Pessoa");

// Chave primária identity builder.HasKey(p => p.PessoaId);

// Atributos builder.Property(p => p.Tipo)

} }

public class PessoaFisicaConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { // Tabela builder.ToTable("PessoaFisica");

// Aqui nesse ponto tenho que fazer a chave FK e PK // Como fazer ???

// Atributos builder.Property(p => p.Nome).HasColumnName("Nome").HasColumnType("varchar(40)").IsRequired(); builder.Property(p => p.CPF).HasColumnName("CPF").HasColumnType("varchar(11)").IsRequired(); builder.Property(p => p.RG).HasColumnName("RG").HasColumnType("varchar(20)"); builder.Property(p => p.Sexo).HasColumnName("Sexo").HasColumnType("char(1)").IsRequired();

} }

public class PessoaJuridicaConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { // Tabela builder.ToTable("PessoaJuridica");

// Aqui nesse ponto tenho que fazer a chave FK e PK // Como fazer ???

// Atributos builder.Property(p => p.Nome).HasColumnName("RazaoSocial").HasColumnType("varchar(40)").IsRequired(); builder.Property(p => p.CNPJ).HasColumnName("CNPJ").HasColumnType("varchar(14)").IsRequired(); builder.Property(p => p.IE).HasColumnName("IE").HasColumnType("varchar(20)");

} }

```

18 respostas

Mauricio, bom dia. Tudo bem?

A primeira coisa que notei foi que a propriedade PessoaJuridica na classe Pessoa está declarada com o tipo PessoaFisica. Mas deve ser um erro de digitação, né?

Sobre o problema, o que você deve fazer é criar a shadow property PessoaId nas classes PessoaFisica e PessoaJuridica, e em seguida informar que essa shadow property é chave primária dessas entidades. O próprio EF Core cuida de descobrir que essa mesma coluna será também chave estrangeira (por conta das propriedades de navegação).

O código de configuração fica assim:

//na configuração de PessoaFisica...
builder .Property<int>("PessoaId");
builder.HasKey("PessoaId");

//na configuração de PessoaJuridica
builder.Property<int>("PessoaId");
builder.HasKey("PessoaId");

Me dá um feedback se é isso mesmo que estava pensando, e se o código acima funcionou.

Abraços!

Ok.

Estou no trabalho e chegando em casa vou testar e já respondo em seguida para lhe informar se funcionou.

Deve ser por volta das 17:00 hrs.

Aguarde e obrigado.
Professor, me tira uma dúvida, nas classes PessoaFisica e PessoaJuridica é necessário eu criar uma properiedade para elas ??

Exemplo:

public class PessoaFisica
{
    public int PessoaFisicaId { get; set; }
    .....
        ....
}

public class PessoaJuridica
{
    public int PessoaJuridicaId { get; set; }
    .....
        ....

}

Nesse parte eu fiquei com dúvidas se precisa criar esses propriedades ou não.

Se o sr. puder me esclarecer essa dúvida ficarei grato.

Não, Mauricio, se vc deseja usar a chave da pessoa como identificador, não precisa.

Professor deu esse erro aí:

The child/dependent side could not be determined for the one-to-one relationship that was detected between 'Pessoa.PessoaJuridica' and 'PessoaJuridica.Pessoa'. To identify the child/dependent side of the relationship, configure the foreign key property. If these navigations should not be part of the same relationship configure them without specifying the inverse. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details.

Favor desconsiderar minha última mensagem. ```

Boa noite Professor.

Do jeito que o sr. me passou está funcionando de forma errada.
Fica assim o Script-Migration


CREATE TABLE [PessoaFisica] (
    [PessoaId] int NOT NULL IDENTITY,
    [CPF] varchar(11) NOT NULL,
    [DataNascimento] date NOT NULL,
    [Nome] varchar(50) NOT NULL,
    [RG] varchar(20) NULL,
    [Sexo] char(1) NULL,
    CONSTRAINT [PK_PessoaFisica] PRIMARY KEY ([PessoaId])
);

GO

CREATE TABLE [PessoaJuridica] (
    [PessoaId] int NOT NULL IDENTITY,
    [CNPJ] varchar(14) NOT NULL,
    [DataFundacao] date NOT NULL,
    [IE] varchar(20) NULL,
    [NomeFantasia] varchar(40) NULL,
    [RazaoSocial] varchar(40) NOT NULL,
    CONSTRAINT [PK_PessoaJuridica] PRIMARY KEY ([PessoaId])
);

GO

CREATE TABLE [Pessoa] (
    [PessoaId] int NOT NULL,
    [PessoaJuridicaPessoaId] int NULL,
    [Tipo] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_Pessoa] PRIMARY KEY ([PessoaId]),
    CONSTRAINT [FK_Pessoa_PessoaFisica_PessoaId] FOREIGN KEY ([PessoaId]) REFERENCES [PessoaFisica] ([PessoaId]) ON DELETE CASCADE,
    CONSTRAINT [FK_Pessoa_PessoaJuridica_PessoaJuridicaPessoaId] FOREIGN KEY ([PessoaJuridicaPessoaId]) REFERENCES [PessoaJuridica] ([PessoaId]) ON DELETE NO ACTION
);

 public class Pessoa
 {

     public int PessoaId { get; protected set; }
     public string Tipo { get; protected set; }

     public virtual PessoaFisica PessoaFisica { get; protected set; }
     public virtual PessoaJuridica PessoaJuridica { get; protected set; }

}


 public class PessoaFisica
 {

    public int PessoaId { get; protected set; }

    public string CPF { get; protected set; }
    public string RG { get; protected set; }
    public string Nome { get; protected set; }
    public string Sexo { get; protected set; }
    public DateTime DataNascimento { get; protected set; }

    public virtual Pessoa Pessoa { get; protected set; }

 }

  public class PessoaJuridica
  {

    public int PessoaId { get; protected set; }

    public string CNPJ { get; protected set; }
    public string IE { get; protected set; }
    public string RazaoSocial { get; protected set; }
    public string NomeFantasia { get; protected set; }
    public DateTime DataFundacao { get; protected set; }

    public virtual Pessoa Pessoa { get; protected set; }

 }


Só funciona assim.

Classes de Contexto

  public class PessoaConfiguracao : IEntityTypeConfiguration<Pessoa>
    {
        public void Configure(EntityTypeBuilder<Pessoa> builder)
        {

            // Tabela
            builder.ToTable("Pessoa");

            // Chave primária
            builder.HasKey(p=>p.PessoaId);

            // Atributos

            builder.Property("PessoaId");

            builder
                .Property(p => p.Tipo)
                .HasColumnName("Tipo")
                .IsRequired();


        }

    }

  public class PessoaFisicaConfiguracao : IEntityTypeConfiguration<PessoaFisica>
    {
        public void Configure(EntityTypeBuilder<PessoaFisica> builder)
        {

            // Tabela
            builder.ToTable("PessoaFisica");


            // Chave FK e PK
            builder.HasKey(pf=>pf.PessoaId);

            // Atributos

            builder
                .Property(pf => pf.PessoaId)
                .HasColumnName("PessoaFisicaId");

            builder
                .Property(pf => pf.CPF)
                .HasColumnName("CPF")
                .HasColumnType("varchar(11)")
                .IsRequired();

            builder
                .Property(pf => pf.RG)
                .HasColumnName("RG")
                .HasColumnType("varchar(20)");

            builder
                .Property(pf => pf.Nome)
                .HasColumnName("Nome")
                .HasColumnType("varchar(50)")
                .IsRequired();

            builder
                .Property(pf => pf.DataNascimento)
                .HasColumnName("DataNascimento")
                .HasColumnType("date");


            builder
                .Property(pf => pf.Sexo)
                .HasColumnName("Sexo")
                .HasColumnType("char(1)");


        }
    }


 public class PessoaJuridicaConfiguracao : IEntityTypeConfiguration<PessoaJuridica>
    {
        public void Configure(EntityTypeBuilder<PessoaJuridica> builder)
        {

            // Tabela
            builder.ToTable("PessoaJuridica");


            // Chave FK e PK
            builder.HasKey(pj=>pj.PessoaId);

            // Atributos

            builder
                .Property(pj => pj.PessoaId)
                .HasColumnName("PessoaJuridicaId");

            builder
                .Property(pj => pj.CNPJ)
                .HasColumnName("CNPJ")
                .HasColumnType("varchar(14)")
                .IsRequired();

            builder
                .Property(pj => pj.IE)
                .HasColumnName("IE")
                .HasColumnType("varchar(20)");

            builder
                .Property(pj => pj.RazaoSocial)
                .HasColumnName("RazaoSocial")
                .HasColumnType("varchar(40)")
                .IsRequired();

            builder
                .Property(pj => pj.NomeFantasia)
                .HasColumnName("NomeFantasia")
                .HasColumnType("varchar(40)");

            builder
                .Property(pj => pj.DataFundacao)
                .HasColumnName("DataFundacao")
                .HasColumnType("date");


        }
    }

Não sei o porquê, mas só funciona assim professor.

Não estou entendendo. No vídeo do curso do sr. funciona mas aqui só funciona dessa forma. Por quê ?? Existe uma explicação técnica ou fiz algo de errado ?? Mas assim desse jeito que mandei para o sr. FUNCIONA MESMO.

Pode me ajudar a entender isso ??

Fala, Mauricio. Como está o método OnModelConfiguring()?

Coloca o código aqui, por favor.

A Classe onde herda o DbContext está assim


public class EfCoreAppDapperContexto : DbContext
{


    public DbSet<Pessoa> Pessoa { get; set; }
    public DbSet<PessoaFisica> PessoaFisica { get; set; }
    public DbSet<PessoaJuridica> PessoaJuridica { get; set; }


    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.UseSqlServer("Conexão com Sql Server")
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new PessoaConfiguracao());
        modelBuilder.ApplyConfiguration(new PessoaFisicaConfiguracao());
        modelBuilder.ApplyConfiguration(new PessoaJuridicaConfiguracao());

    }


}




  As classes de configuração estão assim:


  public class PessoaConfiguracao : IEntityTypeConfiguration<Pessoa>
    {
        public void Configure(EntityTypeBuilder<Pessoa> builder)
        {

            // Tabela
            builder.ToTable("Pessoa");

            // Chave primária
            builder.HasKey(p=>p.PessoaId);

            // Atributos

            builder.Property("PessoaId");

            builder
                .Property(p => p.Tipo)
                .HasColumnName("Tipo")
                .IsRequired();


        }

    }

  public class PessoaFisicaConfiguracao : IEntityTypeConfiguration<PessoaFisica>
    {
        public void Configure(EntityTypeBuilder<PessoaFisica> builder)
        {

            // Tabela
            builder.ToTable("PessoaFisica");


            // Chave FK e PK
            builder.HasKey(pf=>pf.PessoaId);

            // Atributos

            builder
                .Property(pf => pf.PessoaId)
                .HasColumnName("PessoaFisicaId");

            builder
                .Property(pf => pf.CPF)
                .HasColumnName("CPF")
                .HasColumnType("varchar(11)")
                .IsRequired();

            builder
                .Property(pf => pf.RG)
                .HasColumnName("RG")
                .HasColumnType("varchar(20)");

            builder
                .Property(pf => pf.Nome)
                .HasColumnName("Nome")
                .HasColumnType("varchar(50)")
                .IsRequired();

            builder
                .Property(pf => pf.DataNascimento)
                .HasColumnName("DataNascimento")
                .HasColumnType("date");


            builder
                .Property(pf => pf.Sexo)
                .HasColumnName("Sexo")
                .HasColumnType("char(1)");


        }
    }


 public class PessoaJuridicaConfiguracao : IEntityTypeConfiguration<PessoaJuridica>
    {
        public void Configure(EntityTypeBuilder<PessoaJuridica> builder)
        {

            // Tabela
            builder.ToTable("PessoaJuridica");


            // Chave FK e PK
            builder.HasKey(pj=>pj.PessoaId);

            // Atributos

            builder
                .Property(pj => pj.PessoaId)
                .HasColumnName("PessoaJuridicaId");

            builder
                .Property(pj => pj.CNPJ)
                .HasColumnName("CNPJ")
                .HasColumnType("varchar(14)")
                .IsRequired();

            builder
                .Property(pj => pj.IE)
                .HasColumnName("IE")
                .HasColumnType("varchar(20)");

            builder
                .Property(pj => pj.RazaoSocial)
                .HasColumnName("RazaoSocial")
                .HasColumnType("varchar(40)")
                .IsRequired();

            builder
                .Property(pj => pj.NomeFantasia)
                .HasColumnName("NomeFantasia")
                .HasColumnType("varchar(40)");

            builder
                .Property(pj => pj.DataFundacao)
                .HasColumnName("DataFundacao")
                .HasColumnType("date");


        }
    }
As classes de domínio estão assim:

 public class Pessoa
 {

     public int PessoaId { get; protected set; }
     public string Tipo { get; protected set; }

     public virtual PessoaFisica PessoaFisica { get; protected set; }
     public virtual PessoaJuridica PessoaJuridica { get; protected set; }

}


 public class PessoaFisica
 {

    public int PessoaId { get; protected set; }

    public string CPF { get; protected set; }
    public string RG { get; protected set; }
    public string Nome { get; protected set; }
    public string Sexo { get; protected set; }
    public DateTime DataNascimento { get; protected set; }

    public virtual Pessoa Pessoa { get; protected set; }

 }

  public class PessoaJuridica
  {

    public int PessoaId { get; protected set; }

    public string CNPJ { get; protected set; }
    public string IE { get; protected set; }
    public string RazaoSocial { get; protected set; }
    public string NomeFantasia { get; protected set; }
    public DateTime DataFundacao { get; protected set; }

    public virtual Pessoa Pessoa { get; protected set; }

 }
Bem professor, eu tive que mandar e posts separadaos pois o código ficou grande para o sr. olhar.

Mas é desse forma que está funcionando.
Realmente estou confuso, mas está funcionando mesmo.
As tabelas de PessoaFisica e PessoaJuridica recebem uma FK de pessoa e vira uma PK também. simples assim.
A dúvida em relação ao que o sr. me pediu para fazer e de como eu fiz fazer funcionar, continua.
Aguardo uma orientação, uma explicação de como fazer corretamente ou se essa maneira que eu fiz está certa ???!!!!
Não sei !!! Fico no aguardo.
Mas desse jeito ai está funcionando.
Aguardo seu contato.
Maurício Ribeiro.

Mauricio, acabei de usar exatamente seu código. O resultado de um comando Script-Migration foi o seguinte:

CREATE TABLE [Pessoa] (
    [PessoaId] int NOT NULL IDENTITY,
    [Tipo] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_Pessoa] PRIMARY KEY ([PessoaId])
);

GO

CREATE TABLE [PessoaFisica] (
    [PessoaFisicaId] int NOT NULL,
    [CPF] varchar(11) NOT NULL,
    [DataNascimento] date NOT NULL,
    [Nome] varchar(50) NOT NULL,
    [RG] varchar(20) NULL,
    [Sexo] char(1) NULL,
    CONSTRAINT [PK_PessoaFisica] PRIMARY KEY ([PessoaFisicaId]),
    CONSTRAINT [FK_PessoaFisica_Pessoa_PessoaFisicaId] FOREIGN KEY ([PessoaFisicaId]) REFERENCES [Pessoa] ([PessoaId]) ON DELETE CASCADE
);

GO

CREATE TABLE [PessoaJuridica] (
    [PessoaJuridicaId] int NOT NULL,
    [CNPJ] varchar(14) NOT NULL,
    [DataFundacao] date NOT NULL,
    [IE] varchar(20) NULL,
    [NomeFantasia] varchar(40) NULL,
    [RazaoSocial] varchar(40) NOT NULL,
    CONSTRAINT [PK_PessoaJuridica] PRIMARY KEY ([PessoaJuridicaId]),
    CONSTRAINT [FK_PessoaJuridica_Pessoa_PessoaJuridicaId] FOREIGN KEY ([PessoaJuridicaId]) REFERENCES [Pessoa] ([PessoaId]) ON DELETE CASCADE
);

GO

Está correto, certo?

Agora vou fazer as modificações que eu sugeri pra gente comparar. Daqui a pouco coloco as observações.

Mauricio, sua configuração está certinha. Só vou explicar cada modificação que sugeri na minha resposta original pra você entender meu raciocínio, ok?

Chaves primárias

Primeiro, sugeri que você criasse shadow properties (propriedades que não existem no modelo) em cada classe PF e PJ...

builder.Property<int>("PessoaId");

E depois configurá-las como chave primária de cada tabela:

builder.HasKey("PessoaId");

Chaves estrangeiras

A criação de cada chave estrangeira seria descoberta por convenção, já que existem propriedades de navegação em cada lado da relação.

Por exemplo, Pessoa (um lado) tem:

public virtual PessoaFisica PessoaFisica { get; set; }

E em PessoaFisica (o outro lado) tem:

public virtual Pessoa Pessoa { get; set; }

Isso bastaria para gerar o modelo físico que você desejava.

Contudo, você definiu propriedades chamadas PessoaId em cada classe para materializar o valor de cada chave. Essas propriedades não estavam no modelo do seu post inicial, então a solução que enviei não se aplica mais. Talvez por isso não tenha entendido.

Novo cenário

Para esse novo cenário é necessário mapear essas propriedades com os nomes certos na tabela, e dizer que elas são chaves primárias. Assim:

builder.HasKey(pf => pf.PessoaId);
builder.Property(pf => pf.PessoaId).HasColumnName("PessoaFisicaId");

E as chaves estrangeiras se resolverão também por convenção.

Tudo o que você já havia feito!

Espero que tenha ajudado a entender o código, Maurício.

Obrigado professor.

Entendi perfeitamente a explicação do sr.

Só gostaria saber para finalizar, como faço para criar ON DELETE RESTRICT e o ON UPDATE RESTRICT nas chaves estrangeiras usando essa estrutura de código do Entity ??? Não sei fazer.

No meu caso, eu preciso dominar esse tipo de situação aqui no sistema.

Pode me ajudar com mais essa dúvida ??

Atenciosamente, Maurício Ribeiro.

Oi, Mauricio, qual banco de dados vc está usando?

Sei que o SQL Server 2017 não suporta ON DELETE RESTRICT.

Veja aqui:

https://docs.microsoft.com/en-us/sql/relational-databases/tables/modify-foreign-key-relationships

De qualquer forma, essa configuração vai depender do seu provider. Se for para o SQL Server, você tem as opções:

  • NO ACTION
  • CASCADE
  • SET NULL
  • SET DEFAULT

O ON DELETE é configurado com o método OnDelete() asssim:

//exemplo para a relação entre pessoa e PJ:
builder
  .HasOne(pj => pj.Pessoa)
  .WithOne(p => p.PessoaJuridica)
  .OnDelete(DeleteBehavior.SetNull);

Curiosamente não achei na documentação como configurar o ON UPDATE. Mas consegui "hackear" a migração fazendo o seguinte:

  • Configure o mapeamento
  • Gere a migração com Add-Migration
  • Na classe que representa a migração vá na criação da chave estrangeira e inclua o código onUpdate: ReferentialAction.Cascade, desse modo:
table.ForeignKey(
   name: "FK_PessoaFisica_Pessoa_PessoaFisicaId",
   column: x => x.PessoaFisicaId,
   principalTable: "Pessoa",
   principalColumn: "PessoaId",
   onUpdate: ReferentialAction.Cascade,
   onDelete: ReferentialAction.Cascade);
  • Gere o script (ou faça o update)

Tranquilo e favorável?

Se tiver outras dúvidas (espero que sim! Isso significa que está aprendendo) peço que por gentileza feche esse tópico e abra um novo. O motivo é que assim outros participantes vão ver aquela dúvida específica no título do tópico e se interessar.

Abraços!

Estou usando o SQL SERVER 2008 R2 e o 2014

Hahahahaha.....É isso mesmo. Uso dois bancos. Não pergunte. Kkkkkkk....

Mas obrigado mais uma vez professor.

Vou fazer os ajustes e verificar.

Estou fazendo sempre os cursos de vocês aqui.

Pra mim são os melhores. Principalmente no quesito suporte ao aluno.

Parabéns.

Vou testar aqui e dou um retorno OK ??