Code First DataAnnotations
Remarks#
Entity Framework Code-First provides a set of DataAnnotation attributes, which you can apply to your domain classes and properties. DataAnnotation attributes override default Code-First conventions.
- System.ComponentModel.DataAnnotations includes attributes that impacts on nullability or size of the column.
- System.ComponentModel.DataAnnotations.Schema namespace includes attributes that impacts the schema of the database.
Note: DataAnnotations only give you a subset of configuration options. Fluent API provides a full set of configuration options available in Code-First.
[Key] attribute
Key is a field in a table which uniquely identifies each row/record in a database table.
Use this attribute to override the default Code-First convention. If applied to a property, it will be used as the primary key column for this class.
using System.ComponentModel.DataAnnotations;
public class Person
{
[Key]
public int PersonKey { get; set; } // <- will be used as primary key
public string PersonName { get; set; }
}
If a composite primary key is required, the [Key] attribute can also be added to multiple properties. The order of the columns within the composite key must be provided in the form [Key, Column(Order = x)].
using System.ComponentModel.DataAnnotations;
public class Person
{
[Key, Column(Order = 0)]
public int PersonKey1 { get; set; } // <- will be used as part of the primary key
[Key, Column(Order = 1)]
public int PersonKey2 { get; set; } // <- will be used as part of the primary key
public string PersonName { get; set; }
}
Without the [Key] attribute, EntityFramework will fall back to the default convention which is to use the property of the class as a primary key that is named “Id” or “{ClassName}Id”.
public class Person
{
public int PersonID { get; set; } // <- will be used as primary key
public string PersonName { get; set; }
}
[Required] attribute
When applied to a property of a domain class, the database will create a NOT NULL column.
using System.ComponentModel.DataAnnotations;
public class Person
{
public int PersonID { get; set; }
[Required]
public string PersonName { get; set; }
}
The resulting column with the NOT NULL constraint:
Note: It can also be used with asp.net-mvc as a validation attribute.
[MaxLength] and [MinLength] attributes
[MaxLength(int)] attribute can be applied to a string or array type property of a domain class. Entity Framework will set the size of a column to the specified value.
using System.ComponentModel.DataAnnotations;
public class Person
{
public int PersonID { get; set; }
[MinLength(3), MaxLength(100)]
public string PersonName { get; set; }
}
The resulting column with the specified column length:
[MinLength(int)] attribute is a validation attribute, it does not affect the database structure. If we try to insert/update a Person with PersonName with length less than 3 characters, this commit will fail. We’ll get a DbUpdateConcurrencyException
that we’ll need to handle.
using (var db = new ApplicationDbContext())
{
db.Staff.Add(new Person() { PersonName = "ng" });
try
{
db.SaveChanges();
}
catch (DbEntityValidationException ex)
{
//ErrorMessage = "The field PersonName must be a string or array type with a minimum length of '3'."
}
}
Both [MaxLength] and [MinLength] attributes can also be used with asp.net-mvc as a validation attribute.
[Range(min,max)] attribute
Specifies a numeric minimum and maximum range for a property
using System.ComponentModel.DataAnnotations;
public partial class Enrollment
{
public int EnrollmentID { get; set; }
[Range(0, 4)]
public Nullable<decimal> Grade { get; set; }
}
If we try to insert/update a Grade with value out of range, this commit will fail. We’ll get a DbUpdateConcurrencyException
that we’ll need to handle.
using (var db = new ApplicationDbContext())
{
db.Enrollments.Add(new Enrollment() { Grade = 1000 });
try
{
db.SaveChanges();
}
catch (DbEntityValidationException ex)
{
// Validation failed for one or more entities
}
}
It can also be used with asp.net-mvc as a validation attribute.
Result:
[DatabaseGenerated] attribute
Specifies how the database generates values for the property. There are three possible values:
None
specifies that the values are not generated by the database.Identity
specifies that the column is an identity column, which is typically used for integer primary keys.Computed
specifies that the database generates the value for the column.
If the value is anything other than None
, Entity Framework will not commit changes made to the property back to the database.
By default (based on the StoreGeneratedIdentityKeyConvention
) an integer key property will be treated as an identity column. To override this convention and force it to be treated as a non-identity column you can use the DatabaseGenerated
attribute with a value of None
.
using System.ComponentModel.DataAnnotations.Schema;
public class Foo
{
[Key]
public int Id { get; set; } // identity (auto-increment) column
}
public class Bar
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; } // non-identity column
}
The following SQL creates a table with a computed column:
CREATE TABLE [Person] (
Name varchar(100) PRIMARY KEY,
DateOfBirth Date NOT NULL,
Age AS DATEDIFF(year, DateOfBirth, GETDATE())
)
GO
To create an entity for representing the records in the above table, you would need to use the DatabaseGenerated
attribute with a value of Computed
.
[Table("Person")]
public class Person
{
[Key, StringLength(100)]
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public int Age { get; set; }
}
[NotMapped] attribute
By Code-First convention, Entity Framework creates a column for every public property that is of a supported data type and has both a getter and a setter. [NotMapped] annotation must be applied to any properties that we do NOT want a column in a database table for.
An example of a property that we might not want to store in the database is a student’s full name based on their first and last name. That can be calculated on the fly and there is no need to store it in the database.
public string FullName => string.Format("{0} {1}", FirstName, LastName);
The “FullName” property has only a getter and no setter, so by default, Entity Framework will NOT create a column for it.
Another example of a property that we might not want to store in the database is a student’s “AverageGrade”. We do not want to get the AverageGrade on-demand; instead we might have a routine elsewhere that calculates it.
[NotMapped]
public float AverageGrade { set; get; }
The “AverageGrade” must be marked [NotMapped] annotation, else Entity Framework will create a column for it.
using System.ComponentModel.DataAnnotations.Schema;
public class Student
{
public int Id { set; get; }
public string FirstName { set; get; }
public string LastName { set; get; }
public string FullName => string.Format("{0} {1}", FirstName, LastName);
[NotMapped]
public float AverageGrade { set; get; }
}
For the above Entity we will see inside DbMigration.cs
CreateTable(
"dbo.Students",
c => new
{
Id = c.Int(nullable: false, identity: true),
FirstName = c.String(),
LastName = c.String(),
})
.PrimaryKey(t => t.Id);
and in SQL Server Management Studio
[Table] attribute
[Table("People")]
public class Person
{
public int PersonID { get; set; }
public string PersonName { get; set; }
}
Tells Entity Framework to use a specific table name instead of generating one (i.e. Person
or Persons
)
We can also specify a schema for the table using [Table] attribute
[Table("People", Schema = "domain")]
[Column] attribute
public class Person
{
public int PersonID { get; set; }
[Column("NameOfPerson")]
public string PersonName { get; set; }
}
Tells Entity Framework to use a specific column name instead using the name of the property. You can also specify the database data type and the order of the column in table:
[Column("NameOfPerson", TypeName = "varchar", Order = 1)]
public string PersonName { get; set; }
[Index] attribute
public class Person
{
public int PersonID { get; set; }
public string PersonName { get; set; }
[Index]
public int Age { get; set; }
}
Creates a database index for a column or set of columns.
[Index("IX_Person_Age")]
public int Age { get; set; }
This creates an index with a specific name.
[Index(IsUnique = true)]
public int Age { get; set; }
This creates a unique index.
[Index("IX_Person_NameAndAge", 1)]
public int Age { get; set; }
[Index("IX_Person_NameAndAge", 2)]
public string PersonName { get; set; }
This creates a composite index using 2 columns. To do this you must specify the same index name and provide a column order.
Note: 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.
[ForeignKey(string)] attribute
Specifies custom foreign key name if a foreign key not following EF’s convention is desired.
public class Person
{
public int IdAddress { get; set; }
[ForeignKey(nameof(IdAddress))]
public virtual Address HomeAddress { get; set; }
}
This can also be used when you have multiple relationships to the same entity type.
using System.ComponentModel.DataAnnotations.Schema;
public class Customer
{
...
public int MailingAddressID { get; set; }
public int BillingAddressID { get; set; }
[ForeignKey("MailingAddressID")]
public virtual Address MailingAddress { get; set; }
[ForeignKey("BillingAddressID")]
public virtual Address BillingAddress { get; set; }
}
Without the ForeignKey
attributes, EF might get them mixed up and use the value of BillingAddressID
when fetching the MailingAddress
, or it might just come up with a different name for the column based on its own naming conventions (like Address_MailingAddress_Id
) and try to use that instead (which would result in an error if you’re using this with an existing database).
[StringLength(int)] attribute
using System.ComponentModel.DataAnnotations;
public class Post
{
public int Id { get; set; }
[StringLength(100)]
public string Title { get; set;}
[StringLength(300)]
public string Abstract { get; set; }
public string Description { get; set; }
}
Defines a maximum length for a string field.
Note: It can also be used with asp.net-mvc as a validation attribute.
[Timestamp] attribute
[TimeStamp] attribute can be applied to only one byte array property in a given Entity class. Entity Framework will create a non-nullable timestamp column in the database table for that property. Entity Framework will automatically use this TimeStamp column in concurrency check.
using System.ComponentModel.DataAnnotations.Schema;
public class Student
{
public int Id { set; get; }
public string FirstName { set; get; }
public string LastName { set; get; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
[ConcurrencyCheck] Attribute
This attribute is applied to the class property. You can use ConcurrencyCheck attribute when you want to use existing columns for concurrency check and not a separate timestamp column for concurrency.
using System.ComponentModel.DataAnnotations;
public class Author
{
public int AuthorId { get; set; }
[ConcurrencyCheck]
public string AuthorName { get; set; }
}
From above example, ConcurrencyCheck attribute is applied to AuthorName property of the Author class. So, Code-First will include AuthorName column in update command (where clause) to check for optimistic concurrency.
[InverseProperty(string)] attribute
using System.ComponentModel.DataAnnotations.Schema;
public class Department
{
...
public virtual ICollection<Employee> PrimaryEmployees { get; set; }
public virtual ICollection<Employee> SecondaryEmployees { get; set; }
}
public class Employee
{
...
[InverseProperty("PrimaryEmployees")]
public virtual Department PrimaryDepartment { get; set; }
[InverseProperty("SecondaryEmployees")]
public virtual Department SecondaryDepartment { get; set; }
}
InverseProperty can be used to identify two way relationships when multiple two way relationships exist between two entities.
It tells Entity Framework which navigation properties it should match with properties on the other side.
Entity Framework doesn’t know which navigation property map with which properties on the other side when multiple bidirectional relationships exist between two entities.
It needs the name of the corresponding navigation property in the related class as its parameter.
This can also be used for entities that have a relationship to other entities of the same type, forming a recursive relationship.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
public class TreeNode
{
[Key]
public int ID { get; set; }
public int ParentID { get; set; }
...
[ForeignKey("ParentID")]
public TreeNode ParentNode { get; set; }
[InverseProperty("ParentNode")]
public virtual ICollection<TreeNode> ChildNodes { get; set; }
}
Note also the use of the ForeignKey
attribute to specify the column that is used for the foreign key on the table. In the first example, the two properties on the Employee
class could have had the ForeignKey
attribute applied to define the column names.
[ComplexType] attribute
using System.ComponentModel.DataAnnotations.Schema;
[ComplexType]
public class BlogDetails
{
public DateTime? DateCreated { get; set; }
[MaxLength(250)]
public string Description { get; set; }
}
public class Blog
{
...
public BlogDetails BlogDetail { get; set; }
}
Mark the class as complex type in Entity Framework.
Complex Types (Or Value Objects In Domain Driven Design) cannot be tracked on their own but they are tracked as part of an entity. This is why BlogDetails in the example does not have a key property.
They can be useful when describing domain entities across multiple classes and layering those classes into a complete entity.