Código da estrutura da entidade primeiro - duas chaves estrangeiras da mesma tabela

260

Eu comecei a usar o código EF primeiro, então sou um iniciante neste tópico.

Eu queria criar relações entre equipes e jogos:

1 partida = 2 equipes (casa, convidado) e resultado.

Eu pensei que era fácil criar um modelo assim, então comecei a codificar:

public class Team
{
    [Key]
    public int TeamId { get; set;} 
    public string Name { get; set; }

    public virtual ICollection<Match> Matches { get; set; }
}


public class Match
{
    [Key]
    public int MatchId { get; set; }

    [ForeignKey("HomeTeam"), Column(Order = 0)]
    public int HomeTeamId { get; set; }
    [ForeignKey("GuestTeam"), Column(Order = 1)]
    public int GuestTeamId { get; set; }

    public float HomePoints { get; set; }
    public float GuestPoints { get; set; }
    public DateTime Date { get; set; }

    public virtual Team HomeTeam { get; set; }
    public virtual Team GuestTeam { get; set; }
}

E eu recebo uma exceção:

O relacionamento referencial resultará em uma referência cíclica que não é permitida. [Nome da restrição = Match_GuestTeam]

Como posso criar esse modelo, com 2 chaves estrangeiras na mesma tabela?

Jarek
fonte

Respostas:

297

Tente o seguinte:

public class Team
{
    public int TeamId { get; set;} 
    public string Name { get; set; }

    public virtual ICollection<Match> HomeMatches { get; set; }
    public virtual ICollection<Match> AwayMatches { get; set; }
}

public class Match
{
    public int MatchId { get; set; }

    public int HomeTeamId { get; set; }
    public int GuestTeamId { get; set; }

    public float HomePoints { get; set; }
    public float GuestPoints { get; set; }
    public DateTime Date { get; set; }

    public virtual Team HomeTeam { get; set; }
    public virtual Team GuestTeam { get; set; }
}


public class Context : DbContext
{
    ...

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Match>()
                    .HasRequired(m => m.HomeTeam)
                    .WithMany(t => t.HomeMatches)
                    .HasForeignKey(m => m.HomeTeamId)
                    .WillCascadeOnDelete(false);

        modelBuilder.Entity<Match>()
                    .HasRequired(m => m.GuestTeam)
                    .WithMany(t => t.AwayMatches)
                    .HasForeignKey(m => m.GuestTeamId)
                    .WillCascadeOnDelete(false);
    }
}

As chaves primárias são mapeadas por convenção padrão. A equipe deve ter duas coleções de partidas. Você não pode ter uma única coleção referenciada por dois FKs. A correspondência é mapeada sem exclusão em cascata, porque não funciona nessas referências automáticas muitos para muitos.

Ladislav Mrnka
fonte
3
E se duas equipes puderem jogar apenas uma vez?
ca9163d9 8/06/2013
4
@ NickW: Isso é algo que você precisa lidar com seu aplicativo e não no mapeamento. Da perspectiva do mapeamento, os pares podem jogar duas vezes (cada um é convidado e em casa uma vez).
Ladislav Mrnka
2
Eu tenho um modelo semelhante. Qual é a maneira correta de lidar com a exclusão em cascata se uma equipe for removida? Eu olhei para criar um gatilho INSTEAD OF DELETE, mas não tenho certeza se existe uma solução melhor? Eu preferiria lidar com isso no banco de dados, não no aplicativo.
Woodchipper
1
@mrshickadance: É o mesmo. Uma abordagem usa API fluente e outras anotações de dados.
Ladislav Mrnka
1
Se eu usar o WillCascadeOnDelete false, se eu quiser excluir o Team, está lançando um erro. Um relacionamento do AssociationSet 'Team_HomeMatches' está no estado 'Excluído'. Dadas as restrições de multiplicidade, um 'Team_HomeMatches_Target' correspondente também deve estar no estado 'Excluído'.
Rupesh Kumar Tiwari
55

Também é possível especificar o ForeignKey()atributo na propriedade de navegação:

[ForeignKey("HomeTeamID")]
public virtual Team HomeTeam { get; set; }
[ForeignKey("GuestTeamID")]
public virtual Team GuestTeam { get; set; }

Dessa forma, você não precisa adicionar nenhum código ao OnModelCreatemétodo

ShaneA
fonte
4
Eu recebo a mesma exceção de qualquer maneira.
Jo Smo
11
Esta é a minha maneira padrão de especificar chaves estrangeiras que funciona para todos os casos, EXCETO quando uma entidade contém mais de uma propriedade nav do mesmo tipo (semelhante ao cenário HomeTeam e GuestTeam); nesse caso, a EF fica confusa ao gerar o SQL. A solução é adicionar código de OnModelCreateacordo com a resposta aceita, bem como as duas coleções dos dois lados do relacionamento.
Steven Manuel
eu uso onmodelcreating em todos os casos, exceto no caso mencionado, eu uso a chave estrangeira da anotação de dados e também não sei por que ela não é aceita!
hosam hemaily
48

Sei que é um post de vários anos e você pode resolver seu problema com a solução acima. No entanto, eu só quero sugerir o uso de InverseProperty para alguém que ainda precisa. Pelo menos você não precisa alterar nada no OnModelCreating.

O código abaixo não foi testado.

public class Team
{
    [Key]
    public int TeamId { get; set;} 
    public string Name { get; set; }

    [InverseProperty("HomeTeam")]
    public virtual ICollection<Match> HomeMatches { get; set; }

    [InverseProperty("GuestTeam")]
    public virtual ICollection<Match> GuestMatches { get; set; }
}


public class Match
{
    [Key]
    public int MatchId { get; set; }

    public float HomePoints { get; set; }
    public float GuestPoints { get; set; }
    public DateTime Date { get; set; }

    public virtual Team HomeTeam { get; set; }
    public virtual Team GuestTeam { get; set; }
}

Você pode ler mais sobre a InverseProperty no MSDN: https://msdn.microsoft.com/en-us/data/jj591583?f=255&MSPPError=-2147217396#Relationships

khoa_chung_89
fonte
1
Obrigado por esta resposta, no entanto, torna as colunas Chave estrangeira anuláveis ​​na tabela Correspondência.
precisa saber é o seguinte
Isso funcionou muito bem para mim no EF 6, onde eram necessárias coleções anuláveis.
Pynt 12/09/16
Se você deseja evitar uma API fluente (por qualquer motivo, #discussão), isso funciona de maneira fantástica. No meu caso, eu precisava incluir uma anotação foriegnKey adicional na entidade "Match", porque meus campos / tabelas têm sequências para PKs.
DiscipleMichael
1
Isso funcionou muito para mim. Btw. se você não deseja que as colunas sejam nulas, basta especificar a chave estrangeira com o atributo [ForeignKey]. Se a chave não for anulável, você estará pronto.
Jakub Holovsky
16

Você pode tentar isso também:

public class Match
{
    [Key]
    public int MatchId { get; set; }

    [ForeignKey("HomeTeam"), Column(Order = 0)]
    public int? HomeTeamId { get; set; }
    [ForeignKey("GuestTeam"), Column(Order = 1)]
    public int? GuestTeamId { get; set; }

    public float HomePoints { get; set; }
    public float GuestPoints { get; set; }
    public DateTime Date { get; set; }

    public virtual Team HomeTeam { get; set; }
    public virtual Team GuestTeam { get; set; }
}

Quando você faz uma coluna FK permitir NULLS, você está interrompendo o ciclo. Ou estamos apenas trapaceando o gerador de esquema EF.

No meu caso, essa modificação simples resolve o problema.

Maico
fonte
3
Cuidado leitores. Embora isso possa solucionar o problema de definição de esquema, ele altera a semântica. Provavelmente não é o caso de uma partida sem duas equipes.
N8allan
14

Isso ocorre porque as exclusões em cascata são ativadas por padrão. O problema é que, quando você chama uma exclusão na entidade, ela também exclui cada uma das entidades referenciadas pela tecla f. Você não deve tornar os valores 'necessários' anuláveis ​​para corrigir esse problema. Uma opção melhor seria remover a convenção de exclusão em cascata do EF Code First:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 

Provavelmente é mais seguro indicar explicitamente quando fazer uma exclusão em cascata para cada um dos filhos ao mapear / configuração. a entidade.

solavancos
fonte
Então, o que é depois que isso é executado? Restrictem vez de Cascade?
Jo Smo
4

InverseProperty no EF Core, a solução é fácil e limpa.

InverseProperty

Portanto, a solução desejada seria:

public class Team
{
    [Key]
    public int TeamId { get; set;} 
    public string Name { get; set; }

    [InverseProperty(nameof(Match.HomeTeam))]
    public ICollection<Match> HomeMatches{ get; set; }

    [InverseProperty(nameof(Match.GuestTeam))]
    public ICollection<Match> AwayMatches{ get; set; }
}


public class Match
{
    [Key]
    public int MatchId { get; set; }

    [ForeignKey(nameof(HomeTeam)), Column(Order = 0)]
    public int HomeTeamId { get; set; }
    [ForeignKey(nameof(GuestTeam)), Column(Order = 1)]
    public int GuestTeamId { get; set; }

    public float HomePoints { get; set; }
    public float GuestPoints { get; set; }
    public DateTime Date { get; set; }

    public Team HomeTeam { get; set; }
    public Team GuestTeam { get; set; }
}
pritesh agrawal
fonte