Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Observação
EF4.1 Onwards Only - Os recursos, APIs, etc. discutidos nesta página foram introduzidos no Entity Framework 4.1. Se estiver a usar uma versão anterior, parte ou toda esta informação não se aplica.
O conteúdo desta página é adaptado de um artigo originalmente escrito por Julie Lerman (<http://thedatafarm.com>).
O Entity Framework Code First permite-lhe usar as suas próprias classes de domínio para representar o modelo em que a EF depende para realizar consultas, monitorização de alterações e funções de atualização. O Code First utiliza um padrão de programação conhecido como 'convenção sobre configuração'. O Code First assumirá que as suas classes seguem as convenções do Entity Framework e, nesse caso, irá automaticamente determinar como desempenhar o seu trabalho. No entanto, se as suas aulas não seguirem essas convenções, tem a possibilidade de adicionar configurações às suas turmas para fornecer à EF a informação necessária.
O Code First dá-te duas formas de adicionar estas configurações às tuas classes. Um é usar atributos simples chamados DataAnnotations, e o segundo é usar a API Fluent do Code First, que lhe permite descrever configurações de forma imperativa, em código.
Este artigo irá focar-se na utilização de DataAnnotations (no espaço de nomes System.ComponentModel.DataAnnotations) para configurar as suas classes – destacando as configurações mais frequentemente necessárias. As Anotações de Dados também são compreendidas por várias aplicações .NET, como ASP.NET MVC, que permite a estas aplicações aproveitar as mesmas anotações para validações do lado do cliente.
O modelo
Vou demonstrar o Code First DataAnnotations com um par simples de classes: Blog e Post.
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public string BloggerName { get; set;}
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DateCreated { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public ICollection<Comment> Comments { get; set; }
}
Tal como estão, as classes Blog e Post seguem convenientemente a convenção code first e não requerem ajustes para permitir a compatibilidade com EF. No entanto, também pode usar as anotações para fornecer mais informações ao EF sobre as classes e a base de dados para a qual correspondem.
Chave
O Entity Framework baseia-se em cada entidade ter um valor-chave utilizado para o rastreio da entidade. Uma convenção do Code First é as propriedades implícitas das chaves; O Code First procura uma propriedade chamada "Id", ou uma combinação de nome de classe e "Id", como "BlogId". Esta propriedade será mapeada para uma coluna de chave primária na base de dados.
As classes Blog e Post seguem ambas esta convenção. E se não o fizessem? E se o Blog usasse o nome PrimaryTrackingKey em vez disso, ou até foo? Se o código primeiro não encontrar uma propriedade que corresponda a esta convenção, lançará uma exceção devido ao requisito do Entity Framework de que deve ter uma propriedade de chave. Pode usar a anotação 'key' para especificar qual propriedade deve ser utilizada como EntityKey.
public class Blog
{
[Key]
public int PrimaryTrackingKey { get; set; }
public string Title { get; set; }
public string BloggerName { get; set;}
public virtual ICollection<Post> Posts { get; set; }
}
Se estiver a usar a funcionalidade de geração de base de dados do Code First, a tabela Blog terá uma coluna de chave primária chamada PrimaryTrackingKey, que é definida como Identidade por predefinição.
Chaves compostas
O Entity Framework suporta chaves compostas – chaves primárias que consistem em mais do que uma propriedade. Por exemplo, pode ter uma classe de Passaporte cuja chave principal é uma combinação de Número de Passaporte e País de Emissão.
public class Passport
{
[Key]
public int PassportNumber { get; set; }
[Key]
public string IssuingCountry { get; set; }
public DateTime Issued { get; set; }
public DateTime Expires { get; set; }
}
Tentar usar a classe acima no seu modelo EF resultaria num InvalidOperationException:
Não foi possível determinar a ordem das chaves primárias compostas para o tipo 'Passport'. Use o método ColumnAttribute ou HasKey para especificar uma ordem para chaves primárias compostas.
Para usar chaves compostas, o Entity Framework exige que defina uma ordem para as propriedades da chave. Pode fazer isso usando a anotação de coluna para especificar uma ordem.
Observação
O valor de ordem é relativo (em vez de baseado em índice), pelo que qualquer valor pode ser usado. Por exemplo, 100 e 200 seriam aceitáveis em vez de 1 e 2.
public class Passport
{
[Key]
[Column(Order=1)]
public int PassportNumber { get; set; }
[Key]
[Column(Order = 2)]
public string IssuingCountry { get; set; }
public DateTime Issued { get; set; }
public DateTime Expires { get; set; }
}
Se tiver entidades com chaves estrangeiras compostas, então deve especificar a mesma ordem das colunas que usou para as propriedades correspondentes da chave primária.
Apenas a ordenação relativa dentro das propriedades da chave estrangeira precisa de ser a mesma, os valores exatos atribuídos à Ordem não precisam de coincidir. Por exemplo, na classe seguinte, 3 e 4 poderiam ser usados em vez de 1 e 2.
public class PassportStamp
{
[Key]
public int StampId { get; set; }
public DateTime Stamped { get; set; }
public string StampingCountry { get; set; }
[ForeignKey("Passport")]
[Column(Order = 1)]
public int PassportNumber { get; set; }
[ForeignKey("Passport")]
[Column(Order = 2)]
public string IssuingCountry { get; set; }
public Passport Passport { get; set; }
}
Obrigatório
A Required anotação indica a EF que uma determinada propriedade é necessária.
Adicionar Required à propriedade de Título obrigará o EF (e o MVC) a garantir que a propriedade tem dados.
[Required]
public string Title { get; set; }
Sem alterações adicionais de código ou marcação na aplicação, uma aplicação MVC realizará validação do lado do cliente, até mesmo construindo dinamicamente uma mensagem usando os nomes da propriedade e da anotação.
O atributo Required também afetará a base de dados gerada, tornando a propriedade mapeada não anulável. Note que o campo Título mudou para "não nulo".
Observação
Em alguns casos, pode não ser possível que a coluna na base de dados não seja anulável, mesmo que a propriedade seja necessária. Por exemplo, ao usar uma estratégia de herança TPH, os dados para múltiplos tipos são armazenados numa única tabela. Se um tipo derivado incluir uma propriedade exigida, a coluna não pode ser tornada inanulável, pois nem todos os tipos na hierarquia terão esta propriedade.
MaxLength e MinLength
Os MaxLength atributos e MinLength permitem-lhe especificar validações adicionais de propriedades, tal como fez com Required.
Aqui está o BloggerName com os requisitos de comprimento. O exemplo também demonstra como combinar atributos.
[MaxLength(10),MinLength(5)]
public string BloggerName { get; set; }
A anotação MaxLength irá impactar a base de dados ao definir o comprimento da propriedade para 10.
A anotação do lado do cliente MVC e a anotação do lado do servidor EF 4.1 vão ambas honrar esta validação, novamente construindo dinamicamente uma mensagem de erro: "O campo BloggerName deve ser um tipo de string ou array com comprimento máximo de '10'." Essa mensagem é um pouco longa. Muitas anotações permitem especificar uma mensagem de erro com o atributo Mensagem de Erro.
[MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
public string BloggerName { get; set; }
Também pode especificar ErrorMessage na anotação Required.
NotMapped
A convenção Code First dita que todas as propriedades de um tipo de dado suportado são representadas na base de dados. Mas isso nem sempre acontece nas tuas candidaturas. Por exemplo, pode ter uma propriedade na classe Blog que cria um código baseado nos campos Título e BloggerName. Essa propriedade pode ser criada dinamicamente e não precisa de ser armazenada. Pode marcar quaisquer propriedades que não correspondam à base de dados com a anotação NotMapped, como esta propriedade BlogCode.
[NotMapped]
public string BlogCode
{
get
{
return Title.Substring(0, 1) + ":" + BloggerName.Substring(0, 1);
}
}
Tipo Complexo
Não é incomum descrever as suas entidades de domínio através de um conjunto de classes e depois sobrepor essas classes para descrever uma entidade completa. Por exemplo, pode adicionar uma classe chamada BlogDetails ao seu modelo.
public class BlogDetails
{
public DateTime? DateCreated { get; set; }
[MaxLength(250)]
public string Description { get; set; }
}
Note que BlogDetails não tem qualquer tipo de propriedade de chave. No design orientado por domínio, BlogDetails é referido como objeto valor. Entity Framework refere-se a objetos de valor como tipos complexos. Tipos complexos não podem ser rastreados isoladamente.
No entanto, como propriedade na Blog classe, BlogDetails será rastreada como parte de um Blog objeto. Para que o Code First reconheça isto, deve marcar a classe BlogDetails como uma ComplexType.
[ComplexType]
public class BlogDetails
{
public DateTime? DateCreated { get; set; }
[MaxLength(250)]
public string Description { get; set; }
}
Agora podes adicionar uma propriedade na Blog classe para representar o BlogDetails para esse blogue.
public BlogDetails BlogDetail { get; set; }
Na base de dados, a Blog tabela conterá todas as propriedades do blogue, incluindo as propriedades contidas na sua BlogDetail propriedade. Por defeito, cada um é precedido pelo nome do tipo complexo: "BlogDetail".
Verificação de Concorrência
A ConcurrencyCheck anotação permite-lhe sinalizar uma ou mais propriedades para serem usadas para verificação de concorrência na base de dados quando um utilizador edita ou elimina uma entidade. Se tens trabalhado com o EF Designer, isto está alinhado com configurar uma propriedade como ConcurrencyMode para Fixed.
Vamos ver como ConcurrencyCheck funciona ao adicioná-lo à BloggerName propriedade.
[ConcurrencyCheck, MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
public string BloggerName { get; set; }
Quando SaveChanges é chamado, graças à anotação ConcurrencyCheck no campo BloggerName, o valor original dessa propriedade será usado na atualização. O comando tentará localizar a linha correta filtrando não só pelo valor-chave, mas também pelo valor original de BloggerName. Aqui estão as partes críticas do comando UPDATE enviadas para a base de dados, onde pode-se ver que o comando irá atualizar a linha que tem um PrimaryTrackingKey igual a 1 e uma BloggerName de "Julie", que era o valor original quando esse blog foi recuperado da base de dados.
where (([PrimaryTrackingKey] = @4) and ([BloggerName] = @5))
@4=1,@5=N'Julie'
Se alguém mudou o nome do blogger desse blog entretanto, esta atualização falhará e receberá uma DbUpdateConcurrencyException que terá de gerir.
Marca Temporal
É mais comum usar campos de rowversion ou timestamp para verificação de concorrência. Mas em vez de usar a anotação ConcurrencyCheck, pode-se usar a anotação mais específica TimeStamp, desde que o tipo da propriedade seja uma matriz de bytes. Primeiro, o Code First tratará as propriedades Timestamp da mesma forma que as propriedades ConcurrencyCheck, mas também garantirá que o campo de base de dados que gera seja não anulável. Apenas pode ter uma propriedade de carimbo temporal numa determinada classe.
Adicionando a seguinte propriedade à classe Blog:
[Timestamp]
public Byte[] TimeStamp { get; set; }
resulta em Code First criar uma coluna de carimbo temporal não anulável na tabela da base de dados.
Tabela e Coluna
Se está a deixar o Code First criar a base de dados, pode querer alterar o nome das tabelas e colunas que está a criar. Também pode usar o Code First com uma base de dados existente. Mas nem sempre é verdade que os nomes das classes e propriedades do teu domínio coincidam com os nomes das tabelas e colunas da tua base de dados.
A minha classe tem um nome Blog e, por convenção, code first pressupõe que isto irá mapear para uma tabela chamada Blogs. Se não for esse o caso, podes especificar o nome da tabela com o Table atributo. Aqui, por exemplo, a anotação especifica que o nome da tabela é InternalBlogs.
[Table("InternalBlogs")]
public class Blog
A Column anotação é mais eficaz na especificação dos atributos de uma coluna mapeada. Pode estipular um nome, tipo de dado ou até a ordem em que uma coluna aparece na tabela. Aqui está um exemplo do Column atributo.
[Column("BlogDescription", TypeName="ntext")]
public String Description {get;set;}
Não confunda o atributo da TypeName Coluna com a DataType DataAnnotation. DataType é uma anotação usada para a interface e é ignorada pelo Code First.
Aqui está a tabela depois de ter sido regenerada. O nome da tabela mudou para InternalBlogs e Description a coluna do tipo complexo é agora BlogDescription. Como o nome foi especificado na anotação, o código primeiro não usará a convenção de começar o nome da coluna com o nome do tipo complexo.
DatabaseGerado
Uma funcionalidade importante da base de dados é a capacidade de ter propriedades computadas. Se estiveres a mapear as tuas classes Code First para tabelas que contenham colunas computadas, não queres que o Entity Framework tente atualizar essas colunas. Mas queres que o EF devolva esses valores da base de dados depois de inserires ou atualizares os dados. Podes usar a DatabaseGenerated anotação para assinalar essas propriedades na tua classe juntamente com o Computed enum. Outros enums são None e Identity.
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateCreated { get; set; }
Podes usar a base de dados gerada em colunas de bytes ou timestamp quando o código primeiro está a gerar a base de dados, caso contrário só deves usar isto ao apontar para bases de dados existentes porque o código primeiro não conseguirá determinar a fórmula para a coluna computada.
Conforme lido acima, por padrão, uma propriedade de chave que é um inteiro tornar-se-á uma chave de identidade na base de dados. Isso seria o mesmo que definir DatabaseGenerated para DatabaseGeneratedOption.Identity. Se não quiser que seja uma chave de identidade, pode definir o valor para DatabaseGeneratedOption.None.
Índice
Observação
EF6.1 A partir de - O atributo Index foi introduzido na versão 6.1 do Entity Framework. Se estiver a usar uma versão anterior, a informação desta secção não se aplica.
Pode criar um índice numa ou mais colunas usando o IndexAttribute. Adicionar o atributo a uma ou mais propriedades fará com que o EF crie o índice correspondente na base de dados ao criar a base de dados, ou organize as chamadas CreateIndex correspondentes se estiver a usar migrações Code First.
Por exemplo, o código seguinte resultará na criação de um índice na Rating coluna da Posts tabela da base de dados.
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[Index]
public int Rating { get; set; }
public int BlogId { get; set; }
}
Por defeito, o índice será denominado IX_<nome da propriedade> (IX_Rating no exemplo acima). Também podes especificar um nome para o índice. O exemplo seguinte especifica que o índice deve ser chamado PostRatingIndex.
[Index("PostRatingIndex")]
public int Rating { get; set; }
Por padrão, os índices não são únicos, mas pode usar o parâmetro nomeado IsUnique para especificar que um índice deve ser único. O exemplo seguinte introduz um índice único no nome de login de um User.
public class User
{
public int UserId { get; set; }
[Index(IsUnique = true)]
[StringLength(200)]
public string Username { get; set; }
public string DisplayName { get; set; }
}
Índices de Múltiplas Colunas
Índices que abrangem várias colunas são especificados usando o mesmo nome em múltiplas anotações de índice para uma dada tabela. Quando crias índices com múltiplas colunas, precisas de especificar uma ordem para as colunas do índice. Por exemplo, o código seguinte cria um índice de várias colunas em Rating e BlogId chamado IX_BlogIdAndRating.
BlogId é a primeira coluna do índice e Rating é a segunda.
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[Index("IX_BlogIdAndRating", 2)]
public int Rating { get; set; }
[Index("IX_BlogIdAndRating", 1)]
public int BlogId { get; set; }
}
Atributos de Relacionamento: InverseProperty e ForeignKey
Observação
Esta página fornece informações sobre como estabelecer relações no seu modelo Code First usando Anotações de Dados. Para informações gerais sobre relações em EF e como aceder e manipular dados usando relações, consulte Relações e Propriedades de Navegação.*
A convenção Code First trata das relações mais comuns no teu modelo, mas há casos em que precisa de ajuda.
Alterar o nome da propriedade chave na Blog classe criou um problema com a sua relação com Post.
Ao gerar a base de dados, o código vê primeiro a BlogId propriedade na classe Post e reconhece-a, pela convenção de que corresponde a um nome de classe mais Id, como uma chave estrangeira da Blog classe. Mas não há nenhuma BlogId propriedade na classe de blog. A solução para isto é criar uma propriedade de navegação em Post e usar o ForeignKey DataAnnotation para ajudar a code first a compreender como construir a relação entre as duas classes (usando a propriedade Post.BlogId), bem como especificar restrições na base de dados.
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DateCreated { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
[ForeignKey("BlogId")]
public Blog Blog { get; set; }
public ICollection<Comment> Comments { get; set; }
}
A restrição na base de dados mostra uma relação entre InternalBlogs.PrimaryTrackingKey e Posts.BlogId.
O InverseProperty é utilizado quando existem múltiplas relações entre classes.
Na Post aula, pode querer acompanhar quem escreveu um artigo no blogue e quem o editou. Aqui estão duas novas propriedades de navegação para a classe Post.
public Person CreatedBy { get; set; }
public Person UpdatedBy { get; set; }
Também terá de adicionar a classe Person referenciada por estas propriedades. A classe Person tem propriedades de navegação de volta para Post: uma para todas as publicações escritas pela pessoa e outra para todas as publicações atualizadas por essa pessoa.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public List<Post> PostsWritten { get; set; }
public List<Post> PostsUpdated { get; set; }
}
O código primeiro não consegue alinhar as propriedades das duas classes por si só. A tabela da base de dados para Posts deve ter uma chave estrangeira para a CreatedBy pessoa e outra para a UpdatedBy pessoa, mas o código primeiro criará quatro propriedades de chave estrangeira: Person_Id, Person_Id1, CreatedBy_Id e UpdatedBy_Id.
Para resolver estes problemas, pode usar a InverseProperty anotação para especificar o alinhamento das propriedades.
[InverseProperty("CreatedBy")]
public List<Post> PostsWritten { get; set; }
[InverseProperty("UpdatedBy")]
public List<Post> PostsUpdated { get; set; }
Como a propriedade PostsWritten em Person sabe que isto se refere ao tipo Post, construirá a relação com Post.CreatedBy. De forma semelhante, PostsUpdated estará ligado a Post.UpdatedBy. E o código primeiro não vai criar as chaves estrangeiras extra.
Resumo
Os DataAnnotations não só permitem descrever a validação do lado do cliente e do servidor nas suas classes de "code first", como também permitem melhorar e até corrigir as suposições que o "code first" fará sobre as suas classes com base nas convenções dele. Com o DataAnnotations pode não só gerar esquemas de base de dados, como também pode mapear as primeiras classes do seu código para uma base de dados pré-existente.
Embora sejam muito flexíveis, tenha em mente que o DataAnnotations fornece apenas as alterações de configuração mais comuns necessárias que pode fazer nas suas primeiras classes de código. Para configurar as suas classes para alguns dos casos limites, deve consultar o mecanismo alternativo de configuração, a API Fluent do Code First.