Entity Framework - anotações de dados

DataAnnotations é usado para configurar as classes que irão destacar as configurações mais comumente necessárias. DataAnnotations também são compreendidos por vários aplicativos .NET, como ASP.NET MVC, que permite que esses aplicativos aproveitem as mesmas anotações para validações do lado do cliente. Os atributos DataAnnotation substituem as convenções CodeFirst padrão.

System.ComponentModel.DataAnnotations inclui os seguintes atributos que afetam a nulidade ou o tamanho da coluna.

  • Key
  • Timestamp
  • ConcurrencyCheck
  • Required
  • MinLength
  • MaxLength
  • StringLength

System.ComponentModel.DataAnnotations.Schema o namespace inclui os seguintes atributos que afetam o esquema do banco de dados.

  • Table
  • Column
  • Index
  • ForeignKey
  • NotMapped
  • InverseProperty

Chave

O Entity Framework depende de cada entidade com um valor-chave que usa para rastrear entidades. Uma das convenções das quais o Code First depende é como ela indica qual propriedade é a chave em cada uma das classes do Code First.

  • Convenção é procurar uma propriedade chamada “Id” ou uma que combine o nome da classe e “Id”, como “StudentId”.

  • A propriedade será mapeada para uma coluna de chave primária no banco de dados.

  • As classes Aluno, Curso e Matrícula seguem esta convenção.

Agora vamos supor que a classe Student use o nome StdntID em vez de ID. Quando o Code First não encontra uma propriedade que corresponda a esta convenção, ele lançará uma exceção devido ao requisito do Entity Framework de que você deve ter uma propriedade de chave. Você pode usar a anotação de chave para especificar qual propriedade deve ser usada como EntityKey.

Vamos dar uma olhada no código a seguir de uma classe Student que contém StdntID, mas não segue a convenção padrão do Code First. Portanto, para lidar com isso, um atributo Key é adicionado, o que o tornará uma chave primária.

public class Student {

   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Ao executar seu aplicativo e examinar seu banco de dados no SQL Server Explorer, você verá que a chave primária agora é StdntID na tabela Alunos.

O Entity Framework também oferece suporte a chaves compostas. Composite keystambém são chaves primárias que consistem em mais de uma propriedade. Por exemplo, você tem uma classe DrivingLicense cuja chave primária é uma combinação de LicenseNumber e IssuingCountry.

public class DrivingLicense {

   [Key, Column(Order = 1)]
   public int LicenseNumber { get; set; }
   [Key, Column(Order = 2)]
   public string IssuingCountry { get; set; }
   public DateTime Issued { get; set; }
   public DateTime Expires { get; set; }
}

Quando você tem chaves compostas, o Entity Framework exige que você defina uma ordem das propriedades da chave. Você pode fazer isso usando a anotação da coluna para especificar um pedido.

Timestamp

O Code First tratará as propriedades de carimbo de data / hora da mesma forma que as propriedades de ConcurrencyCheck, mas também garantirá que o campo do banco de dados que o código primeiro gera seja não anulável.

  • É mais comum usar campos rowversion ou timestamp para verificação de simultaneidade.

  • Em vez de usar a anotação ConcurrencyCheck, você pode usar a anotação TimeStamp mais específica, desde que o tipo da propriedade seja uma matriz de bytes.

  • Você só pode ter uma propriedade de carimbo de data / hora em uma determinada classe.

Vamos dar uma olhada em um exemplo simples adicionando a propriedade TimeStamp à classe Course -

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   public int Credits { get; set; }
   [Timestamp]
   public byte[] TStamp { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Como você pode ver no exemplo acima, o atributo Timestamp é aplicado à propriedade Byte [] da classe Course. Portanto, Code First criará uma coluna de carimbo de data / hora TStampna tabela Courses.

ConcurrencyCheck

A anotação ConcurrencyCheck permite sinalizar uma ou mais propriedades a serem usadas para verificação de simultaneidade no banco de dados quando um usuário edita ou exclui uma entidade. Se você estiver trabalhando com o EF Designer, isso se alinha com a configuração de ConcurrencyMode de uma propriedade como Fixo.

Vamos dar uma olhada em um exemplo simples de como o ConcurrencyCheck funciona adicionando-o à propriedade Title na classe Course.

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   public string Title { get; set; }
   public int Credits { get; set; }
   [Timestamp, DataType("timestamp")]
   public byte[] TimeStamp { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Na classe Course acima, o atributo ConcurrencyCheck é aplicado à propriedade Title existente. Agora, o Code First incluirá a coluna Title no comando update para verificar a simultaneidade otimista, conforme mostrado no código a seguir.

exec sp_executesql N'UPDATE [dbo].[Courses]
   SET [Title] = @0
   WHERE (([CourseID] = @1) AND ([Title] = @2))
   ',N'@0 nvarchar(max) ,@1 int,@2 nvarchar(max) ',@0=N'Maths',@1=1,@2=N'Calculus'
go

Anotação necessária

A anotação Requerido informa ao EF que uma propriedade particular é necessária. Vamos dar uma olhada na seguinte classe Student na qual o id obrigatório é adicionado à propriedade FirstMidName. O atributo obrigatório forçará o EF a garantir que a propriedade contenha dados.

public class Student {

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

   [Required]
   public string LastName { get; set; }

   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Conforme visto no exemplo acima, o atributo Required é aplicado a FirstMidName e LastName. Portanto, Code First criará colunas NOT NULL FirstMidName e LastName na tabela Alunos, conforme mostrado na imagem a seguir.

Comprimento máximo

O atributo MaxLength permite especificar validações de propriedade adicionais. Ele pode ser aplicado a uma propriedade de tipo string ou array de uma classe de domínio. EF Code First definirá o tamanho de uma coluna conforme especificado no atributo MaxLength.

Vamos dar uma olhada na seguinte classe Course, na qual o atributo MaxLength (24) é aplicado à propriedade Title.

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   [MaxLength(24)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Quando você executa o aplicativo acima, Code First criará um título de coluna nvarchar (24) na tabela CourseId, conforme mostrado na imagem a seguir.

Quando o usuário define o Título que contém mais de 24 caracteres, o EF lançará EntityValidationError.

MinLength

O atributo MinLength também permite especificar validações de propriedade adicionais, assim como você fez com MaxLength. O atributo MinLength também pode ser usado com o atributo MaxLength, conforme mostrado no código a seguir.

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   [MaxLength(24) , MinLength(5)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

EF lançará EntityValidationError, se você definir um valor da propriedade Title menor que o comprimento especificado no atributo MinLength ou maior que o comprimento especificado no atributo MaxLength.

StringLength

StringLength também permite que você especifique validações de propriedade adicionais, como MaxLength. A única diferença é que o atributo StringLength só pode ser aplicado a uma propriedade do tipo string das classes de Domínio.

public class Course {

   public int CourseID { get; set; }
   [StringLength (24)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Entity Framework também valida o valor de uma propriedade para o atributo StringLength. Se o usuário definir o Título que contém mais de 24 caracteres, o EF lançará EntityValidationError.

Mesa

A convenção Default Code First cria um nome de tabela semelhante ao nome da classe. Se você está permitindo que o Code First crie o banco de dados e também deseja alterar o nome das tabelas que ele está criando. Então -

  • Você pode usar o Code First com um banco de dados existente. Mas nem sempre é o caso de os nomes das classes corresponderem aos nomes das tabelas em seu banco de dados.

  • O atributo de tabela substitui essa convenção padrão.

  • EF Code First criará uma tabela com um nome especificado no atributo Table para uma determinada classe de domínio.

Vamos dar uma olhada no exemplo a seguir, no qual a classe é chamada de Aluno e, por convenção, o Code First presume que isso será mapeado para uma tabela chamada Alunos. Se não for esse o caso, você pode especificar o nome da tabela com o atributo Tabela, conforme mostrado no código a seguir.

[Table("StudentsInfo")]
public class Student {

   [Key]
   public int StdntID { get; set; }
   [Required]
   public string LastName { get; set; }
   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Agora você pode ver que o atributo Table especifica a tabela como StudentsInfo. Quando a tabela for gerada, você verá o nome da tabela StudentsInfo, conforme mostrado na imagem a seguir.

Você não pode apenas especificar o nome da tabela, mas também pode especificar um esquema para a tabela usando o atributo Tabela, conforme mostrado no código a seguir.

[Table("StudentsInfo", Schema = "Admin")] 
public class Student {

   [Key]
   public int StdntID { get; set; }
   [Required]
   public string LastName { get; set; }
   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Você pode ver no exemplo acima, a tabela é especificada com o esquema de administração. Agora, o Code First criará a tabela StudentsInfo no esquema Admin, conforme mostrado na imagem a seguir.

Coluna

Também é o mesmo que o atributo Tabela, mas o atributo Tabela substitui o comportamento da tabela, enquanto o atributo Coluna substitui o comportamento da coluna. A convenção Default Code First cria um nome de coluna semelhante ao nome da propriedade. Se você está permitindo que o Code First crie o banco de dados e também deseja alterar o nome das colunas em suas tabelas. Então -

  • O atributo da coluna substitui a convenção padrão.

  • EF Code First criará uma coluna com um nome especificado no atributo Column para uma determinada propriedade.

Vamos dar uma olhada no exemplo a seguir, no qual a propriedade é chamada FirstMidName e, por convenção, Code First presume que isso será mapeado para uma coluna chamada FirstMidName.

Se não for esse o caso, você pode especificar o nome da coluna com o atributo Coluna, conforme mostrado no código a seguir.

public class Student {

   public int ID { get; set; }
   public string LastName { get; set; }
   [Column("FirstName")]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Você pode ver que o atributo Column especifica a coluna como FirstName. Quando a tabela for gerada, você verá o nome da coluna FirstName, conforme mostrado na imagem a seguir.

Índice

O atributo Index foi introduzido no Entity Framework 6.1. Se você estiver usando uma versão anterior, as informações nesta seção não se aplicam.

  • Você pode criar um índice em uma ou mais colunas usando o IndexAttribute.

  • Adicionar o atributo a uma ou mais propriedades fará com que EF crie o índice correspondente no banco de dados ao criar o banco de dados.

  • Os índices tornam a recuperação de dados mais rápida e eficiente, na maioria dos casos. No entanto, sobrecarregar uma tabela ou exibição com índices pode afetar desagradavelmente o desempenho de outras operações, como inserções ou atualizações.

  • A indexação é o novo recurso no Entity Framework onde você pode melhorar o desempenho do seu aplicativo Code First reduzindo o tempo necessário para consultar dados do banco de dados.

  • Você pode adicionar índices ao seu banco de dados usando o atributo Index e substituir as configurações padrão Unique e Clustered para obter o índice mais adequado ao seu cenário.

  • Por padrão, o índice será denominado IX_ <nome da propriedade>

Vamos dar uma olhada no código a seguir, no qual o atributo Index é adicionado na classe do Curso para Créditos.

public class Course {
   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Você pode ver que o atributo Index é aplicado à propriedade Credits. Quando a tabela for gerada, você verá IX_Credits em Índices.

Por padrão, os índices não são únicos, mas você pode usar o IsUniqueparâmetro nomeado para especificar que um índice deve ser exclusivo. O exemplo a seguir apresenta um índice exclusivo, conforme mostrado no código a seguir.

public class Course {
   public int CourseID { get; set; }
   [Index(IsUnique = true)]
	
   public string Title { get; set; }
   [Index]
	
   public int Credits { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

ForeignKey

A convenção Code First cuidará dos relacionamentos mais comuns em seu modelo, mas há alguns casos em que ela precisa de ajuda. Por exemplo, ao alterar o nome da propriedade-chave na classe Aluno, criava-se um problema com seu relacionamento com a classe Enrollment.

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
   public Grade? Grade { get; set; }
	
   public virtual Course Course { get; set; }
   public virtual Student Student { get; set; }
}

public class Student {
   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Ao gerar o banco de dados, Code First vê a propriedade StudentID na classe Enrollment e a reconhece, pela convenção de que corresponde a um nome de classe mais “ID”, como uma chave estrangeira para a classe Student. No entanto, não há nenhuma propriedade StudentID na classe Student, mas a propriedade StdntID é uma classe Student.

A solução para isso é criar uma propriedade de navegação em Enrollment e usar ForeignKey DataAnnotation para ajudar o Code First a entender como construir o relacionamento entre as duas classes, conforme mostrado no código a seguir.

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
	
   public Grade? Grade { get; set; }
   public virtual Course Course { get; set; }
   [ForeignKey("StudentID")]
	
   public virtual Student Student { get; set; }
}

Você pode ver agora que o atributo ForeignKey é aplicado à propriedade de navegação.

NotMapped

Por convenções padrão do Code First, todas as propriedades de um tipo de dados com suporte e que incluem getters e setters são representadas no banco de dados. Mas nem sempre é o caso em seus aplicativos. O atributo NotMapped substitui essa convenção padrão. Por exemplo, você pode ter uma propriedade na classe Student, como FatherName, mas não precisa ser armazenada. Você pode aplicar o atributo NotMapped a uma propriedade FatherName da qual não deseja criar uma coluna no banco de dados, conforme mostrado no código a seguir.

public class Student {
   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
	
   public DateTime EnrollmentDate { get; set; }
   [NotMapped]

   public int FatherName { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Você pode ver que o atributo NotMapped é aplicado à propriedade FatherName. Quando a tabela for gerada você verá que a coluna FatherName não será criada em um banco de dados, mas está presente na classe Aluno.

O Code First não criará uma coluna para uma propriedade, que não tem getters ou setters, conforme mostrado no exemplo a seguir das propriedades Address e Age da classe Student.

InverseProperty

InverseProperty é usado quando você tem vários relacionamentos entre classes. Na classe de Inscrição, você pode querer acompanhar quem se inscreveu em um Curso Atual e um Curso Anterior. Vamos adicionar duas propriedades de navegação para a classe Enrollment.

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
   public Grade? Grade { get; set; }
	
   public virtual Course CurrCourse { get; set; }
   public virtual Course PrevCourse { get; set; }
   public virtual Student Student { get; set; }
}

Da mesma forma, você também precisará adicionar a classe Curso referenciada por essas propriedades. A classe Curso tem propriedades de navegação de volta para a classe Enrollment, que contém todas as matrículas atuais e anteriores.

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]

   public int Credits { get; set; }
   public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
   public virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}

O Code First cria a coluna de chave estrangeira {Class Name} _ {Primary Key}, se a propriedade da chave estrangeira não estiver incluída em uma classe particular, conforme mostrado nas classes acima. Quando o banco de dados for gerado, você verá as seguintes chaves estrangeiras.

Como você pode ver, Code first não é capaz de combinar as propriedades nas duas classes por conta própria. A tabela do banco de dados para Enrollments deve ter uma chave estrangeira para CurrCourse e outra para PrevCourse, mas o Code First criará quatro propriedades de chave estrangeira, ou seja,

  • CurrCourse _CourseID
  • PrevCourse _CourseID
  • Course_CourseID e
  • Course_CourseID1

Para corrigir esses problemas, você pode usar a anotação InverseProperty para especificar o alinhamento das propriedades.

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]

   public int Credits { get; set; }
   [InverseProperty("CurrCourse")]

   public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
   [InverseProperty("PrevCourse")]

   public virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}

Como você pode ver, o atributo InverseProperty é aplicado na classe Curso acima, especificando a qual propriedade de referência da classe Enrollment ela pertence. Agora, o Code First gerará um banco de dados e criará apenas duas colunas de chave estrangeira na tabela de inscrições, conforme mostrado na imagem a seguir.

Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.