English 中文(简体)
ASP.NET MVC - Data Annotations
  • 时间:2024-11-03

ASP.NET MVC - Data Annotations


Previous Page Next Page  

DataAnnotations is used to configure your model classes, which will highpght the most commonly needed configurations. DataAnnotations are also understood by a number of .NET apppcations, such as ASP.NET MVC, which allows these apppcations to leverage the same annotations for cpent-side vapdations. DataAnnotation attributes override default Code-First conventions.

System.ComponentModel.DataAnnotations includes the following attributes that impacts the nullabipty 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 repes 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 imppes which property is the key in each of the Code First classes.

The 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 Student class which contains StdntID. It doesn’t follow the default Code First convention so to handle this, Key attribute is added, which will make it a primary key.

pubpc class Student{
   [Key]
   pubpc int StdntID { get; set; }
   pubpc string LastName { get; set; }
   pubpc string FirstMidName { get; set; }
   pubpc DateTime EnrollmentDate { get; set; }

   pubpc virtual ICollection<Enrollment> Enrollments { get; set; }
}

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

Primary Key StdntID

Entity Framework also supports composite keys. Composite keys are 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.

pubpc class DrivingLicense{
   [Key, Column(Order = 1)]
   pubpc int LicenseNumber { get; set; }
	
   [Key, Column(Order = 2)]
   pubpc string IssuingCountry { get; set; }
   pubpc DateTime Issued { get; set; }
   pubpc 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.

Composite Keys

Timestamp

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

It s more common to use rowversion or timestamp fields for concurrency checking. But 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.

pubpc class Course{
   pubpc int CourseID { get; set; }
   pubpc string Title { get; set; }
   pubpc int Credits { get; set; }
   [Timestamp]
   pubpc byte[] TStamp { get; set; }

   pubpc virtual ICollection<Enrollment> Enrollments { get; set; }
}

As you can see in the above example, Timestamp attribute is appped 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 apgns with setting a property s ConcurrencyMode to Fixed.

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

pubpc class Course{
   pubpc int CourseID { get; set; }
	
   [ConcurrencyCheck]
   pubpc string Title { get; set; }
   pubpc int Credits { get; set; }
	
   [Timestamp, DataType("timestamp")]
   pubpc byte[] TimeStamp { get; set; }

   pubpc virtual ICollection<Enrollment> Enrollments { get; set; }
}

In the above Course class, ConcurrencyCheck attribute is appped to the existing Title property. 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

The Required annotation tells EF that a particular property is required. Let’s have 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.

pubpc class Student{
   [Key]
   pubpc int StdntID { get; set; }
	
   [Required]
   pubpc string LastName { get; set; }
	
   [Required]
   pubpc string FirstMidName { get; set; }
   pubpc DateTime EnrollmentDate { get; set; }

   pubpc virtual ICollection<Enrollment> Enrollments { get; set; }
}

You can see in the above example of Student class Required attribute is appped to FirstMidName and LastName. So, Code First will create a NOT NULL FirstMidName and LastName column in the Students table as shown in the following screenshot.

Students Table

MaxLength

The MaxLength attribute allows you to specify additional property vapdations. It can be appped 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 appped to Title property.

pubpc class Course{
   pubpc int CourseID { get; set; }
   [ConcurrencyCheck]
   [MaxLength(24)]
	
   pubpc string Title { get; set; }
   pubpc int Credits { get; set; }

   pubpc virtual ICollection<Enrollment> Enrollments { get; set; }
}

When you run the above apppcation, Code-First will create a nvarchar(24) column Title in the Coursed table as shown in the following screenshot.

Column Title Coursed Table

Now when the user sets the Title which contains more than 24 characters, EF will throw EntityVapdationError.

MinLength

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

pubpc class Course{
   pubpc int CourseID { get; set; }
   [ConcurrencyCheck]
   [MaxLength(24) , MinLength(5)]
   pubpc string Title { get; set; }
   pubpc int Credits { get; set; }

   pubpc virtual ICollection<Enrollment> Enrollments { get; set; }
}

EF will throw EntityVapdationError, if you set a value of Title property less than the specified length in MinLength attribute or greater than the specified length in MaxLength attribute.

StringLength

StringLength also allows you to specify additional property vapdations pke MaxLength. The difference being StringLength attribute can only be appped to a string type property of Domain classes.

pubpc class Course{
   pubpc int CourseID { get; set; }
   [StringLength (24)]
   pubpc string Title { get; set; }
   pubpc int Credits { get; set; }

   pubpc virtual ICollection<Enrollment> Enrollments { get; set; }
}

Entity Framework also vapdates the value of a property for StringLength attribute. Now, if the user sets the Title, which contains more than 24 characters, then EF will throw EntityVapdationError.

Table

Default Code First convention creates a table name same as the class name. If you are letting Code First create the database, you can also change the name of the tables it is creating. 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 an 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")]
pubpc class Student{
   [Key]
   pubpc int StdntID { get; set; }
	
   [Required]
   pubpc string LastName { get; set; }
	
   [Required]
   pubpc string FirstMidName { get; set; }
   pubpc DateTime EnrollmentDate { get; set; }

   pubpc virtual ICollection<Enrollment> Enrollments { get; set; }
}

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

Table Name StudentsInfo

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

[Table("StudentsInfo", Schema = "Admin")]

pubpc class Student{
   [Key]
   pubpc int StdntID { get; set; }
	
   [Required]
   pubpc string LastName { get; set; }
	
   [Required]
   pubpc string FirstMidName { get; set; }
   pubpc DateTime EnrollmentDate { get; set; }

   pubpc virtual ICollection<Enrollment> Enrollments { get; set; }
}

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 screenshot.

StudentsInfo Table in 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 same as the property name.

If you are letting Code First create the database, and you also want to change the name of the columns in your tables. Column attribute overrides this 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 again 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.

pubpc class Student{
   pubpc int ID { get; set; }
   pubpc string LastName { get; set; }
	
   [Column("FirstName")]
   pubpc string FirstMidName { get; set; }
   pubpc DateTime EnrollmentDate { get; set; }

   pubpc virtual ICollection<Enrollment> Enrollments { get; set; }
}

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

Column Name FirstName

Index

The Index attribute was introduced in Entity Framework 6.1. Note − If you are using an earper 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 apppcation 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.

pubpc class Cours{
   pubpc int CourseID { get; set; }
   pubpc string Title { get; set; }
   [Index]
   pubpc int Credits { get; set; }

   pubpc virtual ICollection<Enrollment> Enrollments { get; set; }
}

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

IX_Credits in Indexes

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.

pubpc class Course{
   pubpc int CourseID { get; set; }
   [Index(IsUnique = true)]
	
   pubpc string Title { get; set; }
   [Index]
	
   pubpc int Credits { get; set; }

   pubpc 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.

pubpc class Enrollment{
   pubpc int EnrollmentID { get; set; }
   pubpc int CourseID { get; set; }
   pubpc int StudentID { get; set; }
   pubpc Grade? Grade { get; set; }
   pubpc virtual Course Course { get; set; }
   pubpc virtual Student Student { get; set; }
}

pubpc class Student{
   [Key]
   pubpc int StdntID { get; set; }
   pubpc string LastName { get; set; }
   pubpc string FirstMidName { get; set; }
   pubpc DateTime EnrollmentDate { get; set; }

   pubpc 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. But there is no StudentID property in the Student class, rather it is StdntID property in 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.

pubpc class Enrollment{
   pubpc int EnrollmentID { get; set; }
   pubpc int CourseID { get; set; }
   pubpc int StudentID { get; set; }
   pubpc Grade? Grade { get; set; }
   pubpc virtual Course Course { get; set; }
	
   [ForeignKey("StudentID")]
   pubpc virtual Student Student { get; set; }
}

You can see now that the ForeignKey attribute is appped to 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 apppcations. 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 in a database. Following is the code.

pubpc class Student{
   [Key]
   pubpc int StdntID { get; set; }
   pubpc string LastName { get; set; }
   pubpc string FirstMidName { get; set; }
   pubpc DateTime EnrollmentDate { get; set; }
   [NotMapped]
   pubpc int FatherName { get; set; }

   pubpc virtual ICollection<Enrollment> Enrollments { get; set; }
}

You can see that NotMapped attribute is appped to the FatherName property. Now 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.

FatherName Column Created

Code First will not create a column for a property which does not have either getters or setters.

InverseProperty

The 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 who enrolled a Previous Course.

Let’s add two navigation properties for the Enrollment class.

pubpc class Enrollment{
   pubpc int EnrollmentID { get; set; }
   pubpc int CourseID { get; set; }
   pubpc int StudentID { get; set; }
   pubpc Grade? Grade { get; set; }
	
   pubpc virtual Course CurrCourse { get; set; }
   pubpc virtual Course PrevCourse { get; set; }
   pubpc 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.

pubpc class Course{
   pubpc int CourseID { get; set; }
   pubpc string Title { get; set; }
   [Index]
	
   pubpc int Credits { get; set; }
   pubpc virtual ICollection<Enrollment> CurrEnrollments { get; set; }
   pubpc 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 a number of foreign keys as seen in the following screenshot.

Number of ForeignKeys

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

    Course_CourseID1

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

pubpc class Course{
   pubpc int CourseID { get; set; }
   pubpc string Title { get; set; }
	
   [Index]
   pubpc int Credits { get; set; }
	
   [InverseProperty("CurrCourse")]
   pubpc virtual ICollection<Enrollment> CurrEnrollments { get; set; }
	
   [InverseProperty("PrevCourse")]
   pubpc virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}

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

ForeignKey Enrollments Table

We recommend you to execute the above example for better understanding.

Advertisements