Compartilhar via


Relações, propriedades de navegação e chaves estrangeiras

Este artigo fornece uma visão geral de como o Entity Framework gerencia relações entre entidades. Ele também fornece algumas diretrizes sobre como mapear e manipular relações.

Relações no EF

Em bancos de dados relacionais, as relações (também chamadas de associações) entre tabelas são definidas por meio de chaves estrangeiras. Uma FK (chave estrangeira) é uma coluna ou uma combinação de colunas que é usada para estabelecer e impor um vínculo entre os dados em duas tabelas. Geralmente, há três tipos de relacionamentos: um para um, um para muitos e muitos para muitos. Em uma relação um-para-muitos, a chave estrangeira é definida na tabela que representa o fim muitos da relação. A relação muitos para muitos envolve a definição de uma terceira tabela (chamada de junção ou tabela de junção), cuja chave primária é composta pelas chaves estrangeiras de ambas as tabelas relacionadas. Em uma relação um-para-um, a chave primária atua adicionalmente como uma chave estrangeira e não há nenhuma coluna de chave estrangeira separada para qualquer tabela.

A imagem a seguir mostra duas tabelas que participam de uma para muitas relações. A tabela Course é a tabela dependente porque contém a coluna DepartmentID que a vincula à tabela Department.

Department and Course tables

No Entity Framework, uma entidade pode estar relacionada a outras entidades por meio de uma associação ou relação. Cada relação contém duas extremidades que descrevem o tipo de entidade e a multiplicidade do tipo (um, zero ou um ou muitos) para as duas entidades nessa relação. A relação pode ser governada por uma restrição referencial, que descreve qual final na relação é uma função principal e que é uma função dependente.

As propriedades de navegação fornecem uma maneira de navegar por uma associação entre dois tipos de entidade. Cada objeto pode ter uma propriedade de navegação para cada relação na qual participa. As propriedades de navegação permitem que você navegue e gerencie relações em ambas as direções, retornando um objeto de referência (se a multiplicidade for um ou zero ou um) ou uma coleção (se a multiplicidade for muitas). Você também pode optar por ter navegação unidirecional, nesse caso, você define a propriedade de navegação em apenas um dos tipos que participa da relação e não em ambos.

É recomendável incluir propriedades no modelo que são mapeadas para chaves estrangeiras no banco de dados. Com as propriedades de chave estrangeira incluídas, você pode criar ou alterar uma relação modificando o valor da chave estrangeira em um objeto dependente. Esse tipo de associação é chamada de associação de chave estrangeira. O uso de chaves estrangeiras é ainda mais essencial ao trabalhar com entidades desconectadas. Observe que, ao trabalhar com 1 para 1 ou 1 para 0..1 relações, não há nenhuma coluna de chave estrangeira separada, a propriedade de chave primária atua como a chave estrangeira e é sempre incluída no modelo.

Quando as colunas de chave estrangeira não são incluídas no modelo, as informações de associação são gerenciadas como um objeto independente. As relações são controladas por meio de referências de objeto em vez de propriedades de chave estrangeira. Esse tipo de associação é chamado de associação independente. A maneira mais comum de modificar uma associação independente é modificar as propriedades de navegação geradas para cada entidade que participa da associação.

Você pode optar por usar um ou ambos os tipos de associações em seu modelo. No entanto, se você tiver uma relação pura de muitos para muitos conectada por uma tabela de junção que contenha apenas chaves estrangeiras, o EF usará uma associação independente para gerenciar essa relação muitos para muitos.   

A imagem a seguir mostra um modelo conceitual criado com o Entity Framework Designer. O modelo contém duas entidades que participam de uma para muitas relações. Ambas as entidades têm propriedades de navegação. Course é a entidade dependente e tem a propriedade de chave estrangeira DepartmentID definida.

Department and Course tables with navigation properties

O snippet de código a seguir mostra o mesmo modelo que foi criado com o Code First.

public class Course
{
  public int CourseID { get; set; }
  public string Title { get; set; }
  public int Credits { get; set; }
  public int DepartmentID { get; set; }
  public virtual Department Department { get; set; }
}

public class Department
{
   public Department()
   {
     this.Courses = new HashSet<Course>();
   }  
   public int DepartmentID { get; set; }
   public string Name { get; set; }
   public decimal Budget { get; set; }
   public DateTime StartDate { get; set; }
   public int? Administrator {get ; set; }
   public virtual ICollection<Course> Courses { get; set; }
}

Configurando ou mapeando relações

O restante desta página aborda como acessar e manipular dados usando relações. Para obter informações sobre como configurar relações em seu modelo, consulte as páginas a seguir.

Criando e modificando relações

Em uma associação de chave estrangeira, quando você altera a relação, o estado de um objeto dependente com um EntityState.Unchanged estado muda para EntityState.Modified. Em uma relação independente, alterar a relação não atualiza o estado do objeto dependente.

Os exemplos a seguir mostram como usar as propriedades de chave estrangeira e as propriedades de navegação para associar os objetos relacionados. Com associações de chave estrangeira, você pode usar qualquer método para alterar, criar ou modificar relações. Com associações independentes, você não pode usar a propriedade de chave estrangeira.

  • Atribuindo um novo valor a uma propriedade de chave estrangeira, como no exemplo a seguir.

    course.DepartmentID = newCourse.DepartmentID;
    
  • O código a seguir remove uma relação definindo a chave estrangeira como nula. Observe que a propriedade de chave estrangeira deve ser anulável.

    course.DepartmentID = null;
    

    Observação

    Se a referência estiver no estado adicionado (neste exemplo, o objeto de curso), a propriedade de navegação de referência não será sincronizada com os valores de chave de um novo objeto até que SaveChanges seja chamado. A sincronização não ocorre porque o contexto do objeto não contém chaves permanentes para objetos adicionados até que sejam salvos. Se você precisar ter novos objetos totalmente sincronizados assim que definir a relação, use um dos métodos a seguir.*

  • Atribuindo um novo objeto a uma propriedade de navegação. O código a seguir cria uma relação entre um curso e um department. Se os objetos estiverem anexados ao contexto, o course também será adicionado à coleção department.Courses, e a propriedade de chave estrangeira correspondente no objeto course será definida como o valor da propriedade de chave do departamento.

    course.Department = department;
    
  • Para excluir a relação, defina a propriedade de navegação como null. Se você estiver trabalhando com o Entity Framework baseado no .NET 4.0, o final relacionado precisará ser carregado antes de defini-lo como nulo. Por exemplo:

    context.Entry(course).Reference(c => c.Department).Load();
    course.Department = null;
    

    A partir do Entity Framework 5.0, que se baseia no .NET 4.5, você pode definir a relação como nula sem carregar o final relacionado. Você também pode definir o valor atual como nulo usando o método a seguir.

    context.Entry(course).Reference(c => c.Department).CurrentValue = null;
    
  • Excluindo ou adicionando um objeto em uma coleção de entidades. Por exemplo, você pode adicionar um objeto de tipo Course à coleção department.Courses. Esta operação cria uma relação entre um curso específico e um determinado department. Se os objetos estiverem anexados ao contexto, a referência do departamento e a propriedade de chave estrangeira no objeto de curso serão definidas como o apropriado department.

    department.Courses.Add(newCourse);
    
  • Usando o método ChangeRelationshipState para alterar o estado da relação especificada entre dois objetos de entidade. Esse método é mais comumente usado ao trabalhar com aplicativos N-Tier e uma associação independente (ele não pode ser usado com uma associação de chave estrangeira). Além disso, para usar esse método, você deve descer até ObjectContext, conforme mostrado no exemplo abaixo.
    No exemplo a seguir, há uma relação muitos para muitos entre instrutores e cursos. Chamar o método ChangeRelationshipState e passar o parâmetro EntityState.Added, permite que o SchoolContext saiba que uma relação foi adicionada entre os dois objetos:

    
    ((IObjectContextAdapter)context).ObjectContext.
      ObjectStateManager.
      ChangeRelationshipState(course, instructor, c => c.Instructor, EntityState.Added);
    

    Observe que, se você estiver atualizando (não apenas adicionando) uma relação, deverá excluir a relação antiga depois de adicionar a nova:

    ((IObjectContextAdapter)context).ObjectContext.
      ObjectStateManager.
      ChangeRelationshipState(course, oldInstructor, c => c.Instructor, EntityState.Deleted);
    

Sincronizando as alterações entre as chaves estrangeiras e as propriedades de navegação

Quando você altera a relação dos objetos anexados ao contexto usando um dos métodos descritos acima, o Entity Framework precisa manter chaves, referências e coleções estrangeiras em sincronia. O Entity Framework gerencia automaticamente essa sincronização (também conhecida como correção de relação) para as entidades POCO com proxies. Para obter mais informações, consulte Como trabalhar com proxies.

Se você estiver usando entidades POCO sem proxies, verifique se o método DetectChanges é chamado para sincronizar os objetos relacionados no contexto. Observe que as APIs a seguir disparam automaticamente uma chamada DetectChanges.

  • DbSet.Add
  • DbSet.AddRange
  • DbSet.Remove
  • DbSet.RemoveRange
  • DbSet.Find
  • DbSet.Local
  • DbContext.SaveChanges
  • DbSet.Attach
  • DbContext.GetValidationErrors
  • DbContext.Entry
  • DbChangeTracker.Entries
  • Executando uma consulta LINQ em um DbSet

No Entity Framework, normalmente você usa propriedades de navegação para carregar entidades relacionadas à entidade retornada pela associação definida. Para obter mais informações, consulte Carregar objetos relacionados.

Observação

Em uma associação de chave estrangeira, quando você carrega uma extremidade relacionada de um objeto dependente, o objeto relacionado será carregado com base no valor de chave estrangeira do dependente que está atualmente na memória:

    // Get the course where currently DepartmentID = 2.
    Course course = context.Courses.First(c => c.DepartmentID == 2);

    // Use DepartmentID foreign key property
    // to change the association.
    course.DepartmentID = 3;

    // Load the related Department where DepartmentID = 3
    context.Entry(course).Reference(c => c.Department).Load();

Em uma associação independente, o final relacionado de um objeto dependente é consultado com base no valor de chave estrangeira que está atualmente no banco de dados. No entanto, se a relação foi modificada e a propriedade de referência no objeto dependente aponta para um objeto principal diferente que é carregado no contexto do objeto, o Entity Framework tentará criar uma relação conforme ela é definida no cliente.

Gerenciando simultaneidade

Em associações de chave estrangeira e independentes, as verificações de simultaneidade são baseadas nas chaves de entidade e em outras propriedades de entidade definidas no modelo. Ao usar o Designer EF para criar um modelo, defina o atributo ConcurrencyMode como fixo para especificar que a propriedade deve ser verificada quanto à simultaneidade. Ao usar o Code First para definir um modelo, use a anotação ConcurrencyCheck nas propriedades que você deseja verificar quanto à simultaneidade. Ao trabalhar com o Code First, você também pode usar a anotação TimeStamp para especificar que a propriedade deve ser verificada quanto à simultaneidade. Você pode ter apenas uma propriedade de carimbo de data/hora em uma determinada classe. Code First mapeia essa propriedade para um campo não anulável no banco de dados.

Recomendamos que você sempre use a associação de chave estrangeira ao trabalhar com entidades que participam da verificação e resolução de simultaneidade.

Para obter mais informações, consulte Tratamento de conflitos de simultaneidade.

Trabalhando com chaves sobrepostas

Chaves sobrepostas são chaves compostas em que algumas propriedades na chave também fazem parte de outra chave na entidade. Você não pode ter uma chave sobreposta em uma associação independente. Para alterar uma associação de chave estrangeira que inclua chaves sobrepostas, recomendamos que você modifique os valores de chave estrangeira em vez de usar as referências de objeto.