Compartilhar via


Anotações de dados do primeiro código

Observação

Somente EF4.1 e posteriores – os recursos, AS APIs etc. discutidos nesta página foram introduzidos no Entity Framework 4.1. Se você estiver usando uma versão anterior, algumas ou todas essas informações não se aplicarão.

O conteúdo desta página é adaptado de um artigo originalmente escrito por Julie Lerman (<http://thedatafarm.com>).

O Entity Framework Code First permite que você use suas próprias classes de domínio para representar o modelo no qual o EF depende para executar funções de consulta, controle de alterações e atualização. O Code First aproveita um padrão de programação conhecido como "convenção sobre configuração". O Code First assumirá que suas classes seguem as convenções do Entity Framework e, nesse caso, descobrirão automaticamente como executar seu trabalho. No entanto, se suas classes não seguirem essas convenções, você poderá adicionar configurações às suas classes para fornecer ao EF as informações necessárias.

O Code First oferece duas maneiras de adicionar essas configurações às suas classes. Um deles é usar atributos simples chamados DataAnnotations, e o segundo é usar a API Fluent do Code First, que fornece uma maneira de descrever as configurações imperativamente, no código.

Este artigo se concentrará em usar DataAnnotations (no namespace System.ComponentModel.DataAnnotations) para configurar suas classes, realçando 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.

O modelo

Demonstrarei Code First DataAnnotations com um par simples de classes: Blog e Postagem.

    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; }
    }

Do jeito que estão, as classes Blog e Post seguem a convenção de código primeiro e não exigem ajustes para habilitar a compatibilidade com o EF. No entanto, você também pode usar as anotações para fornecer mais informações ao EF sobre as classes e o banco de dados para o qual elas são mapeadas.

 

Chave

O Entity Framework depende de cada entidade ter um valor de chave que é usado para acompanhamento de entidade. Uma convenção do Code First é propriedades de chave implícitas; O Code First procurará uma propriedade chamada "Id" ou uma combinação de nome de classe e "ID", como "BlogId". Essa propriedade será mapeada para uma coluna de chave primária no banco de dados.

As classes Blog e Post seguem esta convenção. E se não o fizeram? E se o Blog usou o nome PrimaryTrackingKey ou até mesmo foo? Se o código primeiro não encontrar uma propriedade que corresponda a essa convenção, ele gerará 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.

    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 você estiver usando o recurso de geração de banco de dados do Code First, a tabela Blog terá uma coluna de chave primária chamada PrimaryTrackingKey, que também é definida como Identidade por padrão.

Tabela de blog com chave primária

Chaves compostas

O Entity Framework dá suporte a chaves compostas – chaves primárias que consistem em mais de uma propriedade. Por exemplo, você pode ter uma classe Passport cuja chave primária é uma combinação de PassportNumber e IssuingCountry.

    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; }
    }

A tentativa de usar a classe acima em seu modelo EF resultaria em um InvalidOperationException:

Não é possível determinar a ordenação de chave primária composta para o tipo 'Passport'. Use ColumnAttribute ou o método HasKey para especificar uma ordem para chaves primárias compostas.

Para usar chaves compostas, o Entity Framework exige que você defina uma ordem para as propriedades de chave. Você pode fazer isso usando a anotação Coluna para especificar uma ordem.

Observação

O valor da ordem é relativo (em vez de baseado em índice) para que todos os valores possam ser usados. Por exemplo, 100 e 200 seriam aceitáveis no lugar 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 você tiver entidades com chaves estrangeiras compostas, especifique a mesma ordenação de coluna usada para as propriedades de chave primária correspondentes.

Somente a ordenação relativa dentro das propriedades de chave estrangeira precisa ser a mesma, os valores exatos atribuídos à Ordem não precisam corresponder. Por exemplo, na classe a seguir, 3 e 4 podem ser usados no lugar 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 anotação Required informa ao EF que uma propriedade específica é necessária.

Adicionar Required à propriedade Title forçará EF (e MVC) a garantir que a propriedade tenha dados nela.

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

Sem alterações adicionais de código ou marcação no aplicativo, um aplicativo MVC executará a validação do lado do cliente, mesmo criando dinamicamente uma mensagem usando os nomes de propriedade e anotação.

Criar página com o título é um erro obrigatório

O atributo Obrigatório também afetará o banco de dados gerado tornando a propriedade mapeada não anulável. Observe que o campo Título foi alterado para "não nulo".

Observação

Em alguns casos, talvez não seja possível que a coluna no banco de dados seja não anulável, mesmo que a propriedade seja necessária. Por exemplo, ao usar dados de estratégia de herança TPH para vários tipos é armazenado em uma única tabela. Se um tipo derivado incluir uma propriedade necessária, a coluna não poderá ser tornada não anulável, pois nem todos os tipos na hierarquia terão essa propriedade.

 

Tabela de blogs

 

MaxLength e MinLength

Os atributos MaxLength e MinLength permitem que você especifique validações de propriedade adicionais, assim como você fez com Required.

Aqui está o BloggerName com requisitos de comprimento. O exemplo também demonstra como combinar atributos.

    [MaxLength(10),MinLength(5)]
    public string BloggerName { get; set; }

A anotação MaxLength afetará o banco de dados definindo o comprimento da propriedade como 10.

Tabela de blogs mostrando o comprimento máximo na coluna BloggerName

A anotação do lado do cliente MVC e a anotação do lado do servidor EF 4.1 respeitarão essa validação, criando novamente dinamicamente uma mensagem de erro: "O bloggerName de campo deve ser um tipo de cadeia de caracteres ou matriz com um comprimento máximo de '10'." Essa mensagem é um pouco longa. Muitas anotações permitem que você especifique uma mensagem de erro com o atributo ErrorMessage.

    [MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
    public string BloggerName { get; set; }

Você também pode especificar ErrorMessage na anotação necessária.

Criar página com mensagem de erro personalizada

 

NotMapped

A primeira convenção de código determina que todas as propriedades que são de um tipo de dados com suporte são representadas no banco de dados. Mas nem sempre esse é o caso em seus aplicativos. Por exemplo, você pode ter uma propriedade na classe Blog que cria um código com base nos campos Título e BloggerName. Essa propriedade pode ser criada dinamicamente e não precisa ser armazenada. Você pode marcar todas as propriedades que não são mapeadas para o banco 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);
        }
    }

 

ComplexType

Não é incomum descrever suas entidades de domínio em um conjunto de classes e, em seguida, colocar essas classes em camadas para descrever uma entidade completa. Por exemplo, você pode adicionar uma classe chamada BlogDetails ao seu modelo.

    public class BlogDetails
    {
        public DateTime? DateCreated { get; set; }

        [MaxLength(250)]
        public string Description { get; set; }
    }

Observe que BlogDetails não tem nenhum tipo de propriedade de chave. No design controlado pelo domínio, BlogDetails é conhecido como um objeto de valor. Entity Framework refere-se a objetos de valor como tipos complexos.  Tipos complexos não podem ser acompanhados por conta própria.

No entanto, como uma propriedade na classe Blog, BlogDetails será rastreada como parte de um objeto Blog. Para que o código primeiro reconheça isso, você deve marcar a classe BlogDetails como um ComplexType.

    [ComplexType]
    public class BlogDetails
    {
        public DateTime? DateCreated { get; set; }

        [MaxLength(250)]
        public string Description { get; set; }
    }

Agora você pode adicionar uma propriedade na classe Blog para representar o BlogDetails desse blog.

        public BlogDetails BlogDetail { get; set; }

No banco de dados, a tabela Blog conterá todas as propriedades do blog, incluindo as propriedades contidas em sua propriedade BlogDetail. Por padrão, cada um deles é precedido com o nome do tipo complexo, "BlogDetail".

Tabela de blog com tipo complexo

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 Designer de EF, isso se alinhará à configuração do ConcurrencyMode de uma propriedade para Fixed.

Vamos ver como ConcurrencyCheck funciona adicionando-o à propriedade BloggerName.

    [ConcurrencyCheck, MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
    public string BloggerName { get; set; }

Quando SaveChanges for chamado, devido à 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 apenas no valor da chave, mas também no valor original de BloggerName.  Aqui estão as partes críticas do comando UPDATE enviadas ao banco de dados, onde você pode ver que o comando atualizará a linha que tem um PrimaryTrackingKey é 1 e uma BloggerName de “Julie”, que era o valor original quando esse blog foi recuperado do banco de dados.

    where (([PrimaryTrackingKey] = @4) and ([BloggerName] = @5))
    @4=1,@5=N'Julie'

Se alguém tiver alterado o nome do blogueiro para esse blog enquanto isso, essa atualização falhará e você’receberá uma DbUpdateConcurrencyException que você precisará manipular.

 

TimeStamp

É mais comum usar os campos rowversion ou timestamp para verificação de simultaneidade. Mas, em vez de usar a anotação ConcurrencyCheck, você pode usar a anotação de TimeStamp mais específica, desde que o tipo da propriedade seja matriz de bytes. O código primeiro tratará as propriedades Timestamp da mesma forma que as propriedades ConcurrencyCheck, mas também garantirá que o campo de banco de dados gerado primeiro seja não anulável. Você só pode ter uma propriedade de carimbo de data/hora em uma determinada classe.

Adicionando a seguinte propriedade à classe Blog:

    [Timestamp]
    public Byte[] TimeStamp { get; set; }

resulta no código primeiro criando uma coluna de carimbo de data/hora não anulável na tabela de banco de dados.

Tabela de blogs com coluna de carimbo de data/hora

 

Tabela e coluna

Se você estiver permitindo que o Code First crie o banco de dados, talvez você queira alterar o nome das tabelas e colunas que ele está criando. Você também pode usar o Code First com um banco de dados existente. Mas nem sempre é o caso de os nomes das classes e propriedades em seu domínio corresponderem aos nomes das tabelas e colunas no banco de dados.

Minha classe é denominada Blog e, por convenção, o código primeiro presume que isso será mapeado para uma tabela chamada Blogs. Se esse não for o caso, você poderá especificar o nome da tabela com o atributo Table. Aqui, por exemplo, a anotação está especificando que o nome da tabela é InternalBlogs.

    [Table("InternalBlogs")]
    public class Blog

A anotação Column é mais hábil em especificar os atributos de uma coluna mapeada. Você pode estipular um nome, um tipo de dados ou até mesmo a ordem em que uma coluna aparece na tabela. Aqui está um exemplo do atributo Column.

    [Column("BlogDescription", TypeName="ntext")]
    public String Description {get;set;}

Não confunda o atributo TypeName da Coluna com a DataType DataAnnotation. DataType é uma anotação usada para a interface do usuário e é ignorada pelo Code First.

Aqui está a tabela depois que ela foi regenerada. O nome da tabela foi alterado para internalblogs e Description coluna do tipo complexo agora está BlogDescription. Como o nome foi especificado na anotação, o código primeiro não usará a convenção de iniciar o nome da coluna com o nome do tipo complexo.

Tabela e coluna de blogs renomeadas

 

DatabaseGenerated

Um recurso de banco de dados importante é a capacidade de ter propriedades computadas. Se você estiver mapeando suas classes Code First para tabelas que contêm colunas computadas, não deseja que o Entity Framework tente atualizar essas colunas. Mas você deseja que o EF retorne esses valores do banco de dados depois de inserir ou atualizar dados. Você pode usar a anotação DatabaseGenerated para sinalizar essas propriedades em sua classe juntamente com a enumeração Computed. Outras enumerações são None e Identity.

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime DateCreated { get; set; }

Você pode usar o banco de dados gerado em colunas de byte ou carimbo de data/hora quando o código está gerando primeiro o banco de dados, caso contrário, você só deve usá-lo ao apontar para bancos de dados existentes porque o código primeiro não será capaz de determinar a fórmula para a coluna computada.

Você lê acima que, por padrão, uma propriedade de chave que é um inteiro se tornará uma chave de identidade no banco de dados. Isso seria o mesmo que definir DatabaseGenerated para DatabaseGeneratedOption.Identity. Se você não quiser que ela seja uma chave de identidade, você pode definir o valor como DatabaseGeneratedOption.None.

 

Índice

Observação

Somente EF6.1 e posteriores – 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 aplicarão.

Você pode criar um índice em uma ou mais colunas usando o IndexAttribute. Adicionar o atributo a uma ou mais propriedades fará com que o EF crie o índice correspondente no banco de dados quando ele cria o banco de dados ou faça scaffold das chamadas de CreateIndex correspondentes se você estiver usando as Migrações do Code First.

Por exemplo, o código a seguir resultará na criação de um índice na coluna Rating da tabela Posts no banco 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 padrão, o índice será nomeado IX_<nome da propriedade> (IX_Rating no exemplo acima). No entanto, você também pode especificar um nome para o índice. O exemplo a seguir especifica que o índice deve ser nomeado PostRatingIndex.

    [Index("PostRatingIndex")]
    public int Rating { get; set; }

Por padrão, os índices não são exclusivos, mas você pode usar o parâmetro nomeado IsUnique para especificar que um índice deve ser exclusivo. O exemplo a seguir apresenta um índice exclusivo em um nome de logon 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 várias colunas

Índices que abrangem várias colunas são especificados usando o mesmo nome em várias anotações de índice para uma determinada tabela. Ao criar índices de várias colunas, você precisa especificar uma ordem para as colunas no índice. Por exemplo, o código a seguir cria um índice de várias colunas em Rating e BlogId chamado IX_BlogIdAndRating. BlogId é a primeira coluna no í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 relação: InverseProperty e ForeignKey

Observação

Esta página fornece informações sobre como configurar relações em seu modelo Code First usando anotações de dados. Para obter informações gerais sobre relacionamentos no EF e como acessar e manipular dados usando relacionamentos, consulte Relacionamentos e propriedades de navegação.*

A primeira convenção de código cuidará das relações mais comuns em seu modelo, mas há alguns casos em que ele precisa de ajuda.

Alterar o nome da propriedade de chave na classe Blog criou um problema com sua relação com Post

Ao gerar o banco de dados, o código vê primeiro a propriedade BlogId na classe Post e a reconhece, pela convenção de que ele corresponde a um nome de classe mais ID, como uma chave estrangeira para a classe Blog. Mas não há nenhuma propriedade BlogId na classe de blog. A solução para isso é criar uma propriedade de navegação no Post e usar o ForeignKey DataAnnotation para ajudar o código a entender primeiro como criar a relação entre as duas classes (usando a propriedade Post.BlogId), bem como especificar restrições no banco 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 no banco de dados mostra uma relação entre InternalBlogs.PrimaryTrackingKey e Posts.BlogId

relação entre InternalBlogs.PrimaryTrackingKey e Posts.BlogId

O InverseProperty é usado quando você tem várias relações entre classes.

Na classe Post, convém acompanhar quem escreveu uma postagem no blog, bem como quem a editou. Aqui estão duas novas propriedades de navegação para a classe Post.

    public Person CreatedBy { get; set; }
    public Person UpdatedBy { get; set; }

Você’também precisará adicionar a classe Person referenciada por essas propriedades. A classe Person tem propriedades de navegação de volta à Post, uma para todas as postagens escritas pela pessoa e outra para todas as postagens 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 é capaz de corresponder as propriedades nas duas classes por conta própria. A tabela de banco de dados para Posts deve ter uma chave estrangeira para a pessoa CreatedBy e outra para a pessoa UpdatedBy, mas o código primeiro criará quatro propriedades de chave estrangeira: Person_Id, Person_Id1, CreatedBy_Id e UpdatedBy_Id.

Tabela de postagens com chaves estrangeiras extras

Para corrigir esses problemas, você pode usar a anotação InverseProperty 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 isso se refere ao tipo Post, ele criará a relação para Post.CreatedBy. Da mesma forma, PostsUpdated será conectado a Post.UpdatedBy. E o código primeiro não criará as chaves estrangeiras extras.

Tabela de postagens sem chaves estrangeiras extras

 

Resumo

DataAnnotations não só permitem descrever a validação do lado do cliente e do servidor em suas primeiras classes de código, mas também permitem que você aprimore e até corrija as suposições que o código primeiro fará sobre suas classes com base em suas convenções. Com DataAnnotations, você não só pode impulsionar a geração de esquema de banco de dados, mas também pode mapear suas primeiras classes de código para um banco de dados pré-existente.

Embora sejam muito flexíveis, tenha em mente que DataAnnotations fornecem apenas as alterações de configuração mais comumente necessárias que você pode fazer em suas primeiras classes de código. Para configurar suas classes para alguns dos casos de borda, você deve procurar o mecanismo de configuração alternativo, a API Fluent do Code First.