Notas sobre Aplicação MVC Contoso University: Parte 4 Parte 4. Uso de Atributos para controlar Formatação, Validação e Mapeamento na Base de Dados Atributos: DisplayFormat (DataFormatString) MaxLength Column para mudar o nome da Coluna mapeada na base de dados Required (ErrorMessage) Display (Name) Key (chave primária) DatabaseGenerated (DatabaseGeneratedOption.None) Column para mudar o tipo de dados SQL mapeado na base de dados Acrescentar as entidades: Instrutor Escritorio Departamento Modificar a entidade Curso Atributo DisplayFormat As datas de inscrição de estudantes são mostradas juntamente com o tempo. Para que sejam mostradas num formato diferente (apenas a data) basta fazer uma única mudança no código colocar um atributo na propriedade DataInscricao na classe Estudante para que seja sempre mostrada nesse formato. Na classe Estudante acrescentar: [DisplayFormat(DataFormatString="0:d", ApplyFormatInEditMode=true)] public DateTime DataInscricao get; set; A string de formatação especifica que só a data deve ser mostrada. ApplyFormatInEditMode=true especifica que esta formatação também deve ser aplicada quando o valor da data é mostrado num campo de texto para edição. Testar. Atributo MaxLength Os atributos também servem para especificar regras de validação de dados e mensagens de erro. Para limitar o tamanho em caracteres dos atributos PrimeirosNomes e UltimoNome, fazemos as seguintes alterações à classe Estudante:
public string UltimoNome get; set; [MaxLength(50, ErrorMessage="Tamanho não pode exceder 50 carateres.")] public string PrimeirosNomes get; set; Testar. Um nome muito comprido no campo UltimoNome resulta numa mensagem de erro por omissão: The field UltimoNome must be a string or array type with a maximum length of '50'. Um nome muito comprido no campo PrimeirosNomes resulta na mensagem de erro configurada: Tamanho não pode exceder 50 carateres. A especificação do tamanho máximo para propriedades string faz com que as correspondentes colunas na base de dados tenham esse tamanho máximo. Na janela Server Explorer podemos verificar nas propriedades das colunas UltimoNome e PrimeirosNomes da tabela Estudantes que o tamanho é 50. Atributo Column Os atributos também servem para especificar como as classes e as propriedades são mapeadas na base de dados. Para designar por PrimNome a coluna da base de dados correspondente ao atributo PrimeirosNomes colocámos o atributo [Column( PrimNome )] antes da declaração da propriedade PrimeirosNomes. A tabela Estudantes será alterada de Estudantes( EstudanteId, UltimoNome, PrimeirosNomes, DataInscricao) para Estudantes( EstudanteId, UltimoNome, PrimNome, DataInscricao) Alterações à classe Estudante: [Column("PrimNome")] [MaxLength(50, ErrorMessage="Tamanho não pode exceder 50 carateres.")] public string PrimeirosNomes get; set; Testar. O acesso à página Estudante Index causa um acesso à base de dados, o que faz com que a base de dados seja automaticamente apagada e recriada.
Criar a entidade Instrutor using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace ContosoUniversity.Models public class Instrutor public int InstrutorId get; set; [Required(ErrorMessage="Último nome é necessário.")] [Display(Name="Último Nome")] public string UltimoNome get; set; [Required(ErrorMessage="Primeiro nome é necessário.")] [Column("PrimNome")] [Display(Name="Primeiro Nome")] public string PrimeirosNomes get; set; [DisplayFormat(DataFormatString="0:d", ApplyFormatInEditMode=true)] [Required(ErrorMessage="Data de contrataçao é necessário.")] [Display(Name = "Data de Contrataçao")] public DateTime? DataContratacao get; set; public string NomeCompleto get return UltimoNome + ", " + PrimeirosNomes; public virtual ICollection<Curso> Cursos get; set; public virtual Escritorio Escritorio get; set; Atributo Required Especifica que este campo de dados é necessário. Atributo Display Especifica que a legenda para este campo de texto deve ser a especificada em vez do nome da propriedade. Propriedade calculada A propriedade NomeCompleto é uma propriedade que retorna a concatenação das 2 propriedades UltimoNome e PrimeirosNomes. Como é uma propriedade só de leitura não gera qualquer coluna na base de dados. Propriedades de navegação As propriedades de navegação são referências para outras entidades. São declaradas como virtual para permitir que o EF reescreva essas propriedades em subclasses para realizar lazy loading. De um modo simplicado o EF cria o tipo derivado e reescreve a propriedade,
colocando no getter uma chamada de um método extra DoLazyLoad() para carregar o objecto ou a colecção de objectos se ainda não estão carregados. Se uma propriedade de navegação contém muitas entidades o tipo de dados deve ser ICollection. Um Instrutor pode ensinar 0 ou mais Cursos. [ Instrutor ] * * [ Curso ] Um Instrutor pode ter 0 ou 1 Escritorio. [ Instrutor ] 1 0..1 [ Escritorio ] Criar a entidade Escritorio using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace ContosoUniversity.Models public class Escritorio [Key] public int InstrutorId get; set; [Display(Name = "Localização do Escritório")] public string Localizacao get; set; public virtual Instrutor Instrutor get; set; Atributo Key Um Escritorio só existe se tem 1 Instrutor atribuído. A chave primária de Escritorio InstrutorId também é chave estrangeira para a entidade Instrutor. O Entity Framework, por convenção, reconhece uma propriedade como chave primária se tem o nome Id ou <nome_classe>id. O atributo Key identifica a propriedade como chave primária.
Modificar a entidade Curso Um Curso é atribuído a um Departamento [ Departamento ] 1 * [ Curso ] Um Curso pode ser ensinado por muitos Instrutores [ Instrutor ] * * [ Curso ] Um curso pode ter muitos Estudantes Inscritos [ Inscricao ] * 1 [ Curso ] A entidade Curso já tinha a relação com a entidade Inscricao. Vamos acrescentar as relações com as entidades Departamento e Instrutor, acrescentando: uma chave estrangeira DepartamentoId, uma propriedade de navegação Departamento, uma propriedade de navegação Instrutores. De: Para: public class Curso public int CursoId get; set; public string Titulo get; set; public int Creditos get; set; public virtual ICollection<Inscricao> Inscricoes get; set; public class Curso [DatabaseGenerated(DatabaseGeneratedOption.None)] [Display(Name = "Número")] public int CursoId get; set; [Required(ErrorMessage = "Título necessário.")] public string Titulo get; set; [Required(ErrorMessage = "Número de créditos necessário.")] [Range(0, 5, ErrorMessage = "Número de créditos deve ser entre 0 e 5.")] public int Creditos get; set; [Display(Name = "Departamento")] public int DepartamentoId get; set; public virtual Departamento Departamento get; set; public virtual ICollection<Inscricao> Inscricoes get; set; public virtual ICollection<Instrutor> Instrutores get; set;
Por omissão, o Entity Framework assume que as chaves primárias são geradas pela base de dados. Mas neste caso o número de cada curso (chave primária propriedade CursoId) é especificado pelo utilizador. A configuração da propriedade CursoId com o atributo [DatabaseGenerated(DatabaseGeneratedOption.None)] especifica que os valores da chave primária são fornecidos pelo utilizador, em vez de gerados pela base de dados. Criar a entidade Departamento Um Departamento pode ter ou não um Administrador, e um administrador é sempre um Instrutor [Instrutor] 0..1 * [ Departamento] Um Departamento pode ter muitos Cursos [Curso] * 1 [ Departamento] public class Departamento public int DepartamentoId get; set; [Required(ErrorMessage = "Nome do Departamento necessário.")] public string Name get; set; [DisplayFormat(DataFormatString = "0:c")] [Required(ErrorMessage = "Budget é necessário.")] [Column(TypeName = "money")] public decimal? Budget get; set; [DisplayFormat(DataFormatString = "0:d", ApplyFormatInEditMode = true)] [Required(ErrorMessage = "Data de início é necessária.")] public DateTime DataInicio get; set; [Display(Name = "Administrador")] public int? InstrutorId get; set; public virtual Instrutor Administrador get; set; public virtual ICollection<Curso> Cursos get; set; Atributo Column já vimos que pode ser usado para mudar o nome da Coluna mapeada na base de dados, mas também serve para mudar o tipo de dados SQL mapeado na base de dados. [Column(TypeName = "money")] public decimal? Budget get; set; Configuração necessária para mapear o tipo de dados decimal no tipo SQL money.
Modificar a entidade Estudante De: public class Estudante public int EstudanteId get; set; public string UltimoNome get; set; [Column("PrimNome")] [MaxLength(50, ErrorMessage="Tamanho não pode exceder 50 carateres.")] public string PrimeirosNomes get; set; [DisplayFormat(DataFormatString="0:d", ApplyFormatInEditMode=true)] public DateTime DataInscricao get; set; public virtual ICollection<Inscricao> Inscricoes get; set; Para: public class Estudante public int EstudanteId get; set; [Required(ErrorMessage = "Ultimo Nome é necessário.")] [Display(Name = "Ultimo Nome")] public string UltimoNome get; set; [Required(ErrorMessage = "Primeiro Nome é necessário.")] [Column("PrimNome")] [Display(Name = "Primeiro Nome")] [MaxLength(50, ErrorMessage="Tamanho não pode exceder 50 carateres.")] public string PrimeirosNomes get; set; [Required(ErrorMessage = "Data de Inscrição é necessária.")] [DisplayFormat(DataFormatString="0:d", ApplyFormatInEditMode=true)] [Display(Name = "Data de Inscrição")] public DateTime? DataInscricao get; set; public string NomeCompleto get return UltimoNome + ", " + PrimeirosNomes; public virtual ICollection<Inscricao> Inscricoes get; set;
Modificar a entidade Inscricao Um registo de Inscricao refere um único Curso [Curso] 1 * [ Inscricao] Um registo de Inscricao refere um único Estudante [ Estudante ] 1 * [ Inscricao] De: public class Inscricao public int InscricaoId get; set; public int CursoId get; set; public int EstudanteId get; set; public decimal? Nota get; set; public virtual Curso Curso get; set; public virtual Estudante Estudante get; set; Para: public class Inscricao public int InscricaoId get; set; public int CursoId get; set; public int EstudanteId get; set; [DisplayFormat(DataFormatString = "0:#.#", ApplyFormatInEditMode = true, NullDisplayText = "Sem nota")] public decimal? Nota get; set; public virtual Curso Curso get; set; public virtual Estudante Estudante get; set; Relação muitos para muitos As entidades Instrutor e Curso têm uma relação muitos para muitos. Entity Framework cria automaticamente uma tabela de junção designada CursoInstrutor e lê e actualiza essa tabela quando usámos no programa as propriedades de navegação Instrutor.Cursos e Curso.Instrutores. [Instrutor] * * [ Curso]
Atributo DisplayFormat [DisplayFormat(DataFormatString = "0:#.#", ApplyFormatInEditMode = true, NullDisplayText = "Sem nota")] public decimal? Nota get; set; O atributo DisplayFormat na propriedade Nota especifica: a Nota é mostrada como 2 dígitos separados por um ponto, no modo de edição a Nota também é mostrada deste modo, se a propriedade Nota não está inicializada (decimal? indica que é uma propriedade nullable), mostra o texto sem nota. Alteração da classe DAL.EscolaContexto public class EscolaContexto : DbContext public DbSet<Departamento> Departamentos get; set; public DbSet<Instrutor> Instrutores get; set; public DbSet<Estudante> Estudantes get; set; public DbSet<Escritorio> Escritorios get; set; public DbSet<Inscricao> Inscricoes get; set; public DbSet<Curso> Cursos get; set; Erro ao executar: InvalidOperationException was unhandled by user code Unable to determine the principal end of an association between the types 'ContosoUniversity.Models.Escritorio' and 'ContosoUniversity.Models.Instrutor'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations. Há um tipo de relação para a qual Code First necessita sempre de configuração: é a relação um para um. Quando se define uma relação de um para um no modelo, usa se uma propriedade de navegação em cada classe, e Code First não consegue determinar qual é a classe dependente nesta situação. Para resolver esta situação devemos colocar a anotação ForeignKey na classe dependente para identificar que ela tem a chave estrangeira. Quando configurámos uma relação um para um. Entity Framework requer que a chave primária da classe dependente também seja a chave estrangeira. ForeignKey Especifica uma propriedade usada como chave estrangeira numa relação. A anotação deve ser colocada na propriedade chave estrangeira e especificar o nome da propriedade de navegação associada, ou colocada numa propriedade de navegação e especificar o nome da chave estrangeira associada.
Numa relação 1:N temos de um lado uma colecção e do outro uma referência, e Code First pode inferir que a classe com a referência é a dependente e deve ter uma chave estrangeira. Exemplo: relação entre Estudante e Inscricao: Um registo de Inscricao refere um único Estudante: Inscricao tem uma propr. de nav. ref. Um Estudante pode ter muitas Inscricoes: Estudante tem uma prop. de nav. Coleção [ Estudante ] 1 * [ Inscricao] Chave estrangeira na classe Inscrição. Numa relação N:N temos duas colecções, uma de cada lado, e as chaves estrangeiras vão para uma tabela de junção separada. Alteração da classe Escritorio acrescentada o atributo Foreignkey using System.Linq; using System.Web; namespace ContosoUniversity.Models public class Escritorio [Key] [ForeignKey("Instrutor")] public int InstrutorId get; set; [Display(Name = "Localização do Escritório")] public string Localizacao get; set; public virtual Instrutor Instrutor get; set; Testar. Dá erro. DbEntityValidationException was unhandled by user code Validation failed for one or more entities. See 'EntityValidationErrors' property for more details var cursos = new List<Curso> new Curso Titulo = "Física", Creditos = 3, new Curso Titulo = "Informática", Creditos = 6, new Curso Titulo = "Matemática", Creditos = 3, new Curso Titulo = "Bases de Dados", Creditos = 5, new Curso Titulo = "Programação", Creditos = 5, new Curso Titulo = "Álgebra", Creditos = 3, new Curso Titulo = "Geometria", Creditos = 3 ; O Curso Informática tem um valor para Créditos fora da gama permitida. Alterado para: var cursos = new List<Curso> new Curso Titulo = "Física", Creditos = 3,
. ; new Curso Titulo = "Informática", Creditos = 5, new Curso Titulo = "Matemática", Creditos = 3, new Curso Titulo = "Bases de Dados", Creditos = 5, new Curso Titulo = "Programação", Creditos = 5, new Curso Titulo = "Álgebra", Creditos = 3, new Curso Titulo = "Geometria", Creditos = 3 Alteração do código de Inicialização da Base de Dados public class InicializadorEscola : DropCreateDatabaseIfModelChanges<EscolaContexto> protected override void Seed(EscolaContexto contexto) base.seed(contexto); var estudantes = new List<Estudante> new Estudante PrimeirosNomes="António Miguel", UltimoNome="Cunha", DataInscricao=DateTime.Parse("2005 09 1"), new Estudante PrimeirosNomes="Jose Antonio", UltimoNome="Mendes", DataInscricao=DateTime.Parse("2002 09 1"), new Estudante PrimeirosNomes="Alexandra", UltimoNome="Silva", DataInscricao=DateTime.Parse("2003 09 1"), new Estudante PrimeirosNomes="Rui", UltimoNome="Oliveira", DataInscricao=DateTime.Parse("2002 09 1"), new Estudante PrimeirosNomes="Carlos Miguel", UltimoNome="Soares", DataInscricao=DateTime.Parse("2002 09 1"), new Estudante PrimeirosNomes="José Miguel", UltimoNome="Cunha", DataInscricao=DateTime.Parse("2001 09 1"), new Estudante PrimeirosNomes="Joaquim", UltimoNome="Matos", DataInscricao=DateTime.Parse("2003 09 1"), new Estudante PrimeirosNomes="Guilherme", UltimoNome="Torres", DataInscricao=DateTime.Parse("2005 09 1") ; estudantes.foreach(e => contexto.estudantes.add(e)); var instrutores = new List<Instrutor> new Instrutor PrimeirosNomes="Joaquim", UltimoNome="Torres", DataContratacao=DateTime.Parse("1995 03 11"), new Instrutor PrimeirosNomes="António", UltimoNome="Oliveira", DataContratacao=DateTime.Parse("2002 07 06"), new Instrutor PrimeirosNomes="Miguel", UltimoNome="Mendes", DataContratacao=DateTime.Parse("1998 07 01"), new Instrutor PrimeirosNomes="José", UltimoNome="Silva", DataContratacao=DateTime.Parse("2001 01 15"), new Instrutor PrimeirosNomes="Rogério", UltimoNome="Santos", DataContratacao=DateTime.Parse("2004 02 12"), ; instrutores.foreach(e => contexto.instrutores.add(e)); var departamentos = new List<Departamento> new Departamento Nome = "Inglês", Budget = 350000, DataInicio = DateTime.Parse("2007 09 01"), InstrutorId = 1, new Departamento Nome = "Matemática", Budget = 100000, DataInicio = DateTime.Parse("2007 09 01"), InstrutorId = 2, new Departamento Nome = "Engenharia", Budget = 350000, DataInicio = DateTime.Parse("2007 09 01"), InstrutorId = 3, new Departamento Nome = "Economia", Budget = 100000,
DataInicio = DateTime.Parse("2007 09 01"), InstrutorId = 4, ; departamentos.foreach(e => contexto.departamentos.add(e)); var cursos = new List<Curso> new Curso CursoId = 1050, Titulo = "Quimica", Creditos = 3, DepartamentoId = 3, Instrutores = new List<Instrutor>(), new Curso CursoId = 4022, Titulo = "Microeconomia", Creditos = 3, DepartamentoId = 4, Instrutores = new List<Instrutor>(), new Curso CursoId = 4041, Titulo = "Macroeconomia", Creditos = 3, DepartamentoId = 4, Instrutores = new List<Instrutor>(), new Curso CursoId = 1045, Titulo = "Cálculo", Creditos = 4, DepartamentoId = 2, Instrutores = new List<Instrutor>(), new Curso CursoId = 3141, Titulo = "Trigonometria", Creditos = 4, DepartamentoId = 2, Instrutores = new List<Instrutor>(), new Curso CursoId = 2021, Titulo = "Composição", Creditos = 3, DepartamentoId = 1, Instrutores = new List<Instrutor>(), new Curso CursoId = 2042, Titulo = "Literatura", Creditos = 4, DepartamentoId = 1, Instrutores = new List<Instrutor>() ; cursos.foreach(c => contexto.cursos.add(c)); cursos[0].instrutores.add(instrutores[0]); cursos[0].instrutores.add(instrutores[1]); cursos[1].instrutores.add(instrutores[2]); cursos[2].instrutores.add(instrutores[2]); cursos[3].instrutores.add(instrutores[3]); cursos[4].instrutores.add(instrutores[3]); cursos[5].instrutores.add(instrutores[3]); cursos[6].instrutores.add(instrutores[3]); var inscricoes = new List<Inscricao> new Inscricao EstudanteId = 1, CursoId = 1050, Nota = 1, new Inscricao EstudanteId = 1, CursoId = 4022, Nota = 3, new Inscricao EstudanteId = 1, CursoId = 4041, Nota = 1, new Inscricao EstudanteId = 2, CursoId = 1045, Nota = 2, new Inscricao EstudanteId = 2, CursoId = 3141, Nota = 4, new Inscricao EstudanteId = 2, CursoId = 2021, Nota = 4, new Inscricao EstudanteId = 3, CursoId = 1050, new Inscricao EstudanteId = 4, CursoId = 1050, new Inscricao EstudanteId = 4, CursoId = 4022, Nota = 4, new Inscricao EstudanteId = 5, CursoId = 4041, Nota = 3, new Inscricao EstudanteId = 6, CursoId = 1045, new Inscricao EstudanteId = 7, CursoId = 3141, Nota = 2, ; inscricoes.foreach(i => contexto.inscricoes.add(i)); var escritorios = new List<Escritorio> new Escritorio InstrutorId = 1, Localizacao = "Smith 17", new Escritorio InstrutorId = 2, Localizacao = "Gowan 27", new Escritorio InstrutorId = 3, Localizacao = "Thompson 304", ; escritorios.foreach(i => contexto.escritorios.add(i));