Entity Framework: anotaciones de datos

DataAnnotations se usa para configurar las clases que resaltarán las configuraciones más comúnmente necesarias. Las anotaciones de datos también se entienden por una serie de aplicaciones .NET, como ASP.NET MVC, que permite que estas aplicaciones aprovechen las mismas anotaciones para las validaciones del lado del cliente. Los atributos de DataAnnotation anulan las convenciones predeterminadas de CodeFirst.

System.ComponentModel.DataAnnotations incluye los siguientes atributos que afectan la nulabilidad o el tamaño de la columna.

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

System.ComponentModel.DataAnnotations.Schema El espacio de nombres incluye los siguientes atributos que afectan el esquema de la base de datos.

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

Llave

Entity Framework se basa en que cada entidad tiene un valor clave que utiliza para rastrear entidades. Una de las convenciones de las que depende Code First es cómo implica qué propiedad es la clave en cada una de las clases de Code First.

  • La convención es buscar una propiedad llamada "Id" o una que combine el nombre de la clase y "Id", como "StudentId".

  • La propiedad se asignará a una columna de clave principal en la base de datos.

  • Las clases de estudiantes, cursos e inscripción siguen esta convención.

Ahora supongamos que la clase Student usó el nombre StdntID en lugar de ID. Cuando Code First no encuentra una propiedad que coincida con esta convención, generará una excepción debido al requisito de Entity Framework de que debe tener una propiedad clave. Puede utilizar la anotación de clave para especificar qué propiedad se utilizará como EntityKey.

Echemos un vistazo al siguiente código de una clase Student que contiene StdntID, pero no sigue la convención predeterminada de Code First. Entonces, para manejar esto, se agrega un atributo de clave que lo convertirá en una clave principal.

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

Cuando ejecute su aplicación y busque en su base de datos en el Explorador de SQL Server, verá que la clave principal ahora es StdntID en la tabla Estudiantes.

Entity Framework también admite claves compuestas. Composite keystambién son claves primarias que constan de más de una propiedad. Por ejemplo, tiene una clase DrivingLicense cuya clave principal es una combinación de LicenseNumber y 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; }
}

Cuando tiene claves compuestas, Entity Framework requiere que defina un orden de propiedades clave. Puede hacer esto usando la anotación de columna para especificar un orden.

Marca de tiempo

Code First tratará las propiedades de marca de tiempo de la misma forma que las propiedades de ConcurrencyCheck, pero también garantizará que el campo de base de datos que genera el código primero no acepta valores NULL.

  • Es más común usar campos de versión de fila o marca de tiempo para la verificación de simultaneidad.

  • En lugar de utilizar la anotación ConcurrencyCheck, puede utilizar la anotación TimeStamp más específica siempre que el tipo de propiedad sea una matriz de bytes.

  • Solo puede tener una propiedad de marca de tiempo en una clase determinada.

Echemos un vistazo a un ejemplo simple agregando la propiedad TimeStamp a la clase del curso:

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 puede ver en el ejemplo anterior, el atributo Timestamp se aplica a la propiedad Byte [] de la clase Course. Entonces, Code First creará una columna de marca de tiempo TStampen la tabla Cursos.

ConcurrencyCheck

La anotación ConcurrencyCheck le permite marcar una o más propiedades que se utilizarán para la verificación de concurrencia en la base de datos cuando un usuario edita o elimina una entidad. Si ha estado trabajando con EF Designer, esto se alinea con la configuración de ConcurrencyMode de una propiedad en Fixed.

Echemos un vistazo a un ejemplo simple de cómo funciona ConcurrencyCheck agregándolo a la propiedad Título en la clase del curso.

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

En la clase de curso anterior, el atributo ConcurrencyCheck se aplica a la propiedad de título existente. Ahora, Code First incluirá la columna Título en el comando de actualización para verificar la simultaneidad optimista como se muestra en el siguiente código.

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

Anotación requerida

La anotación Requerida le dice a EF que se requiere una propiedad en particular. Echemos un vistazo a la siguiente clase de estudiante en la que se agrega el ID requerido a la propiedad FirstMidName. El atributo obligatorio obligará a EF a asegurarse de que la propiedad contenga datos.

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

Como se ve en el ejemplo anterior, el atributo Requerido se aplica a FirstMidName y LastName. Por lo tanto, Code First creará columnas NOT NULL FirstMidName y LastName en la tabla Estudiantes como se muestra en la siguiente imagen.

Longitud máxima

El atributo MaxLength le permite especificar validaciones de propiedad adicionales. Se puede aplicar a una propiedad de tipo cadena o matriz de una clase de dominio. EF Code First establecerá el tamaño de una columna como se especifica en el atributo MaxLength.

Echemos un vistazo a la siguiente clase del curso en la que se aplica el atributo MaxLength (24) a la propiedad del título.

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

Cuando ejecute la aplicación anterior, Code First creará un título de columna nvarchar (24) en la tabla CourseId como se muestra en la siguiente imagen.

Cuando el usuario establece el título que contiene más de 24 caracteres, EF lanzará EntityValidationError.

Longitud mínima

El atributo MinLength también le permite especificar validaciones de propiedades adicionales, tal como lo hizo con MaxLength. El atributo MinLength también se puede usar con el atributo MaxLength como se muestra en el siguiente código.

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 arrojará EntityValidationError, si establece un valor de la propiedad Title menor que la longitud especificada en el atributo MinLength o mayor que la longitud especificada en el atributo MaxLength.

Longitud de la cuerda

StringLength también le permite especificar validaciones de propiedades adicionales como MaxLength. La única diferencia es que el atributo StringLength solo se puede aplicar a una propiedad de tipo cadena de clases de dominio.

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 también valida el valor de una propiedad para el atributo StringLength. Si el usuario establece el Título que contiene más de 24 caracteres, EF lanzará EntityValidationError.

Mesa

La convención Default Code First crea un nombre de tabla similar al nombre de la clase. Si permite que Code First cree la base de datos y también desea cambiar el nombre de las tablas que está creando. Entonces ...

  • Puede utilizar Code First con una base de datos existente. Pero no siempre es el caso que los nombres de las clases coincidan con los nombres de las tablas en su base de datos.

  • El atributo de tabla anula esta convención predeterminada.

  • EF Code First creará una tabla con un nombre específico en el atributo Table para una clase de dominio determinada.

Echemos un vistazo al siguiente ejemplo en el que la clase se llama Estudiante y, por convención, Code First supone que se asignará a una tabla llamada Estudiantes. Si ese no es el caso, puede especificar el nombre de la tabla con el atributo Table como se muestra en el siguiente código.

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

Ahora puede ver que el atributo Tabla especifica la tabla como StudentsInfo. Cuando se genera la tabla, verá el nombre de la tabla StudentsInfo como se muestra en la siguiente imagen.

No solo puede especificar el nombre de la tabla, sino que también puede especificar un esquema para la tabla utilizando el atributo Table como se muestra en el siguiente código.

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

Puede ver en el ejemplo anterior, la tabla se especifica con el esquema de administración. Ahora Code First creará la tabla StudentsInfo en el esquema de administración como se muestra en la siguiente imagen.

Columna

También es lo mismo que el atributo Tabla, pero el atributo Tabla anula el comportamiento de la tabla, mientras que el atributo Columna anula el comportamiento de la columna. La convención Default Code First crea un nombre de columna similar al nombre de propiedad. Si permite que Code First cree la base de datos y también desea cambiar el nombre de las columnas en sus tablas. Entonces ...

  • El atributo de columna anula la convención predeterminada.

  • EF Code First creará una columna con un nombre específico en el atributo Column para una propiedad determinada.

Echemos un vistazo al siguiente ejemplo en el que la propiedad se denomina FirstMidName y, por convención, Code First supone que se asignará a una columna denominada FirstMidName.

Si ese no es el caso, puede especificar el nombre de la columna con el atributo Columna como se muestra en el siguiente código.

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

Puede ver que el atributo Columna especifica la columna como Nombre. Cuando se genera la tabla, verá el nombre de la columna FirstName como se muestra en la siguiente imagen.

Índice

El atributo Index se introdujo en Entity Framework 6.1. Si está utilizando una versión anterior, la información de esta sección no se aplica.

  • Puede crear un índice en una o más columnas utilizando IndexAttribute.

  • Agregar el atributo a una o más propiedades hará que EF cree el índice correspondiente en la base de datos cuando cree la base de datos.

  • Los índices hacen que la recuperación de datos sea más rápida y eficiente, en la mayoría de los casos. Sin embargo, sobrecargar una tabla o vista con índices podría afectar de manera desagradable el rendimiento de otras operaciones como inserciones o actualizaciones.

  • La indexación es la nueva característica de Entity Framework en la que puede mejorar el rendimiento de su aplicación Code First al reducir el tiempo necesario para consultar datos de la base de datos.

  • Puede agregar índices a su base de datos mediante el atributo Índice y anular la configuración predeterminada Única y Agrupada para obtener el índice que mejor se adapte a su escenario.

  • Por defecto, el índice se llamará IX_ <nombre de propiedad>

Echemos un vistazo al siguiente código en el que se agrega el atributo de índice en la clase del 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; }
}

Puede ver que el atributo Índice se aplica a la propiedad Créditos. Cuando se genera la tabla, verá IX_Credits en índices.

De forma predeterminada, los índices no son únicos, pero puede utilizar la IsUniqueparámetro con nombre para especificar que un índice debe ser único. El siguiente ejemplo presenta un índice único como se muestra en el siguiente código.

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

Clave externa

La convención Code First se ocupará de las relaciones más comunes en su modelo, pero hay algunos casos en los que necesita ayuda. Por ejemplo, al cambiar el nombre de la propiedad de la clave en la clase del estudiante, se creó un problema con su relación con la clase de inscripción.

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

Al generar la base de datos, Code First ve la propiedad StudentID en la clase Enrollment y la reconoce, por la convención de que coincide con un nombre de clase más "ID", como una clave externa para la clase Student. Sin embargo, no hay propiedad StudentID en la clase Student, pero su propiedad StdntID es clase Student.

La solución para esto es crear una propiedad de navegación en Enrollment y usar ForeignKey DataAnnotation para ayudar a Code First a comprender cómo construir la relación entre las dos clases como se muestra en el siguiente código.

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

Ahora puede ver que el atributo ForeignKey se aplica a la propiedad de navegación.

NotMapped

Por convenciones predeterminadas de Code First, todas las propiedades que son de un tipo de datos admitido y que incluyen getters y setters están representadas en la base de datos. Pero este no es siempre el caso en sus aplicaciones. El atributo NotMapped anula esta convención predeterminada. Por ejemplo, es posible que tenga una propiedad en la clase Estudiante como FatherName, pero no es necesario almacenarla. Puede aplicar el atributo NotMapped a una propiedad FatherName de la que no desea crear una columna en la base de datos como se muestra en el siguiente código.

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

Puede ver que el atributo NotMapped se aplica a la propiedad FatherName. Cuando se genere la tabla, verá que la columna FatherName no se creará en una base de datos, pero está presente en la clase Student.

Code First no creará una columna para una propiedad que no tenga getters ni setters, como se muestra en el siguiente ejemplo de propiedades de dirección y edad de la clase Student.

InverseProperty

InverseProperty se usa cuando tiene varias relaciones entre clases. En la clase de inscripción, es posible que desee realizar un seguimiento de quién inscribió un curso actual y un curso anterior. Agreguemos dos propiedades de navegación para la clase 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; }
}

Del mismo modo, también deberá agregar la clase del curso a la que hacen referencia estas propiedades. La clase del curso tiene propiedades de navegación de regreso a la clase de inscripción, que contiene todas las inscripciones actuales y 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; }
}

Code First crea la columna de clave externa {Class Name} _ {Primary Key}, si la propiedad de la clave externa no está incluida en una clase en particular, como se muestra en las clases anteriores. Cuando se genera la base de datos, verá las siguientes claves externas.

Como puede ver, Code first no puede hacer coincidir las propiedades en las dos clases por sí solo. La tabla de la base de datos para las inscripciones debe tener una clave externa para CurrCourse y otra para PrevCourse, pero Code First creará cuatro propiedades de clave externa, es decir

  • CurrCourse _CourseID
  • PrevCourse _CourseID
  • Course_CourseID y
  • Course_CourseID1

Para solucionar estos problemas, puede utilizar la anotación InverseProperty para especificar la alineación de las propiedades.

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 puede ver, el atributo InverseProperty se aplica en la clase de curso anterior especificando a qué propiedad de referencia de la clase de inscripción pertenece. Ahora, Code First generará una base de datos y creará solo dos columnas de clave externa en la tabla de Inscripciones como se muestra en la siguiente imagen.

Le recomendamos que ejecute el ejemplo anterior paso a paso para una mejor comprensión.