Entity Framework - Data Annotations



DataAnnotations is used to configure the classes which will highlight the most commonly needed configurations. DataAnnotations are also understood by a number of .NET applications, such as ASP.NET MVC which allows these applications to leverage the same annotations for client-side validations. DataAnnotation attributes override default CodeFirst conventions.

System.ComponentModel.DataAnnotations includes the following attributes that impacts the nullability or size of the column.

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

System.ComponentModel.DataAnnotations.Schema namespace includes the following attributes that impacts the schema of the database.

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

Key

Entity Framework relies on every entity having a key value that it uses for tracking entities. One of the conventions that Code First depends on is how it implies which property is the key in each of the Code First classes.

  • Convention is to look for a property named “Id” or one that combines the class name and “Id”, such as “StudentId”.

  • The property will map to a primary key column in the database.

  • The Student, Course and Enrollment classes follow this convention.

Now let’s suppose Student class used the name StdntID instead of ID. When Code First does not find a property that matches this convention, it will throw an exception because of Entity Framework’s requirement that you must have a key property. You can use the key annotation to specify which property is to be used as the EntityKey.

Let’s take a look at the following code of a Student class which contains StdntID, but it doesn’t follow the default Code First convention. So to handle this, a Key attribute is added which will make it a primary key.

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

When you run your application and look into your database in SQL Server Explorer you will see that the primary key is now StdntID in Students table.

Primary Key

Entity Framework also supports composite keys. Composite keys are also primary keys that consist of more than one property. For example, you have a DrivingLicense class whose primary key is a combination of LicenseNumber and 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; }
}

When you have composite keys, Entity Framework requires you to define an order of the key properties. You can do this using the Column annotation to specify an order.

Column Annotation

Timestamp

Code First will treat Timestamp properties the same as ConcurrencyCheck properties, but it will also ensure that the database field that code first generates is non-nullable.

  • It's more common to use rowversion or timestamp fields for concurrency checking.

  • Rather than using the ConcurrencyCheck annotation, you can use the more specific TimeStamp annotation as long as the type of the property is byte array.

  • You can only have one timestamp property in a given class.

Let’s take a look at a simple example by adding the TimeStamp property to the Course class −

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

As you can see in the above example, Timestamp attribute is applied to Byte[] property of the Course class. So, Code First will create a timestamp column TStamp in the Courses table.

ConcurrencyCheck

The ConcurrencyCheck annotation allows you to flag one or more properties to be used for concurrency checking in the database when a user edits or deletes an entity. If you've been working with the EF Designer, this aligns with setting a property's ConcurrencyMode to Fixed.

Let’s take a look at a simple example of how ConcurrencyCheck works by adding it to the Title property in Course class.

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

In the above Course class, ConcurrencyCheck attribute is applied to the existing Title property. Now, Code First will include Title column in update command to check for optimistic concurrency as shown in the following code.

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

Required Annotation

The Required annotation tells EF that a particular property is required. Let’s take a look at the following Student class in which Required id is added to the FirstMidName property. Required attribute will force EF to ensure that the property has data in it.

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

As seen in the above example, Required attribute is applied to FirstMidName and LastName. So, Code First will create a NOT NULL FirstMidName and LastName columns in the Students table as shown in the following image.

Not Null

MaxLength

The MaxLength attribute allows you to specify additional property validations. It can be applied to a string or array type property of a domain class. EF Code First will set the size of a column as specified in MaxLength attribute.

Let’s take a look at the following Course class in which MaxLength(24) attribute is applied to Title property.

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

When you run the above application, Code First will create a nvarchar(24) column Title in the CourseId table as shown in the following image.

nvarchar Column

When the user sets the Title which contains more than 24 characters, then EF will throw EntityValidationError.

MinLength

The MinLength attribute also allows you to specify additional property validations, just as you did with MaxLength. MinLength attribute can also be used with MaxLength attribute as shown in the following code.

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 will throw EntityValidationError, if you set a value of Title property less than the specified length in MinLength attribute or greater than specified length in MaxLength attribute.

StringLength

StringLength also allows you to specify additional property validations like MaxLength. The only difference is that StringLength attribute can only be applied to a string type property of Domain classes.

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 also validates the value of a property for StringLength attribute. If the user sets the Title which contains more than 24 characters, then EF will throw EntityValidationError.

Table

Default Code First convention creates a table name similar to the class name. If you are letting Code First create the database, and also want to change the name of the tables it is creating. Then −

  • You can use Code First with an existing database. But it's not always the case that the names of the classes match the names of the tables in your database.

  • Table attribute overrides this default convention.

  • EF Code First will create a table with a specified name in Table attribute for a given domain class.

Let’s take a look at the following example in which the class is named Student and by convention, Code First presumes this will map to a table named Students. If that's not the case, you can specify the name of the table with the Table attribute as shown in the following code.

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

Now you can see that Table attribute specifies the table as StudentsInfo. When the table is generated, you will see the table name StudentsInfo as shown in the following image.

StudentsInfo

You cannot only specify the table name but you can also specify a schema for the table using Table attribute as shown in the following code.

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

You can see in the above example, the table is specified with admin schema. Now Code First will create StudentsInfo table in Admin schema as shown in the following image.

Admin Schema

Column

It is also the same as Table attribute, but Table attribute overrides the table behavior while Column attribute overrides the column behavior. Default Code First convention creates a column name similar to the property name. If you are letting Code First create the database, and also want to change the name of the columns in your tables. Then −

  • Column attribute overrides the default convention.

  • EF Code First will create a column with a specified name in the Column attribute for a given property.

Let’s take a look at the following example in which the property is named FirstMidName and by convention, Code First presumes this will map to a column named FirstMidName.

If that's not the case you can specify the name of the column with the Column attribute as shown in the following code.

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

You can see that Column attribute specifies the column as FirstName. When the table is generated, you will see the column name FirstName as shown in the following image.

FirstName

Index

The Index attribute was introduced in Entity Framework 6.1. If you are using an earlier version, the information in this section does not apply.

  • You can create an index on one or more columns using the IndexAttribute.

  • Adding the attribute to one or more properties will cause EF to create the corresponding index in the database when it creates the database.

  • Indexes make the retrieval of data faster and efficient, in most cases. However, overloading a table or view with indexes could unpleasantly affect the performance of other operations such as inserts or updates.

  • Indexing is the new feature in Entity Framework where you can improve the performance of your Code First application by reducing the time required to query data from the database.

  • You can add indexes to your database using the Index attribute, and override the default Unique and Clustered settings to get the index best suited to your scenario.

  • By default, the index will be named IX_<property name>

Let’s take a look at the following code in which Index attribute is added in Course class for Credits.

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

You can see that the Index attribute is applied to the Credits property. When the table is generated, you will see IX_Credits in Indexes.

IX Credits

By default, indexes are non-unique, but you can use the IsUnique named parameter to specify that an index should be unique. The following example introduces a unique index as shown in the following code.

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

Code First convention will take care of the most common relationships in your model, but there are some cases where it needs help. For example, by changing the name of the key property in the Student class created a problem with its relationship to Enrollment class.

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

While generating the database, Code First sees the StudentID property in the Enrollment class and recognizes it, by the convention that it matches a class name plus “ID”, as a foreign key to the Student class. However, there is no StudentID property in the Student class, but it is StdntID property is Student class.

The solution for this is to create a navigation property in the Enrollment and use the ForeignKey DataAnnotation to help Code First understand how to build the relationship between the two classes as shown in the following code.

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

You can see now that the ForeignKey attribute is applied to the navigation property.

ForeignKey Attribute

NotMapped

By default conventions of Code First, every property that is of a supported data type and which includes getters and setters are represented in the database. But this isn’t always the case in your applications. NotMapped attribute overrides this default convention. For example, you might have a property in the Student class such as FatherName, but it does not need to be stored. You can apply NotMapped attribute to a FatherName property which you do not want to create a column of in the database as shown in the following code.

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

You can see that NotMapped attribute is applied to the FatherName property. When the table is generated you will see that FatherName column will not be created in a database, but it is present in Student class.

NotMapped Attribute

Code First will not create a column for a property, which does not have either getters or setters as shown in the following example of Address and Age properties of Student class.

InverseProperty

InverseProperty is used when you have multiple relationships between classes. In the Enrollment class, you may want to keep track of who enrolled a Current Course and Previous Course. Let’s add two navigation properties for the Enrollment class.

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

Similarly, you’ll also need to add in the Course class referenced by these properties. The Course class has navigation properties back to the Enrollment class, which contains all the current and previous enrollments.

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 creates {Class Name}_{Primary Key} foreign key column, if the foreign key property is not included in a particular class as shown in the above classes. When the database is generated, you will see the following foreign keys.

Foreign Keys

As you can see that Code first is not able to match up the properties in the two classes on its own. The database table for Enrollments should have one foreign key for the CurrCourse and one for the PrevCourse, but Code First will create four foreign key properties, i.e.

  • CurrCourse _CourseID
  • PrevCourse _CourseID
  • Course_CourseID, and
  • Course_CourseID1

To fix these problems, you can use the InverseProperty annotation to specify the alignment of the properties.

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

As you can see the InverseProperty attribute is applied in the above Course class by specifying which reference property of Enrollment class it belongs to. Now, Code First will generate a database and create only two foreign key columns in Enrollments table as shown in the following image.

Foreign Key Columns

We recommend that you execute the above example in a step-by-step manner for better understanding.

Advertisements