Data Annotations are a set of predefined attributes used in C# to apply specific configurations to the properties of classes.
These attributes help configure how entities and properties in our model should be treated when mapped to the tables and columns of the database.
For example, they allow us to define properties as primary keys, required fields, maximum text lengths, and more.
Like any other attribute, to apply it we simply have to place it in front of the class or property we want to modify.
They are available in System.ComponentModel.DataAnnotations
, so you will need to add the namespace.
Main Data Annotations
Let’s look at some of the most important Data Annotations in Entity Framework and how to use each of them.
Table and Column Configuration
The [Table]
attribute specifies the name of the table associated with an entity.
[Table("Products")]
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
The [Column]
attribute allows specifying the name of the database column that corresponds to a property.
public class Student
{
public int StudentId { get; set; }
[Column("Full_Name")] // Maps the property "Name" to the column "Full_Name"
public string Name { get; set; }
}
The [DataType]
attribute allows specifying the data type of a property.
public class Student
{
public int StudentId { get; set; }
[DataType(DataType.EmailAddress)] // Specifies that this property should be an email address
public string Email { get; set; }
}
Key Configuration
The [Key]
attribute is used to mark a property as the primary key of the entity.
public class Student
{
[Key] // Marks this property as the primary key
public int StudentId { get; set; }
public string Name { get; set; }
}
The [DatabaseGenerated]
attribute controls how values of a property are generated in the database. It is especially useful for auto-increment fields, calculated values, or automatically generated dates.
public class Product
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; } // Automatically generated upon insertion (IDENTITY)
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime LastUpdated { get; set; } // Calculated by the database (e.g., GETDATE())
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string ProductCode { get; set; } // Not automatically generated (manual value)
}
Validation and Constraints
The [Required]
attribute is used to indicate that a property cannot be null in the database. It is similar to the NOT NULL
constraint in SQL.
public class Student
{
public int StudentId { get; set; }
[Required] // Indicates that the name is required
public string Name { get; set; }
}
The [MaxLength]
attribute allows setting a limit for the length of a string.
public class Product
{
[MaxLength(100)]
public string Name { get; set; } // NVARCHAR(100)
}
If you try to save a string that exceeds 100 characters, EF Core will reject the operation.
The [StringLength]
attribute defines a maximum length and optionally a minimum one.
[StringLength(50, MinimumLength = 3)]
public string ShortDescription { get; set; }
The [Range]
attribute is used to specify a range of valid values for a numeric property.
public class Course
{
public int CourseId { get; set; }
[Range(1, 10)] // The range of allowed values for the number of credits
public int Credits { get; set; }
}
Relationship Configuration
The [ForeignKey]
attribute is used to establish a relationship between two entities. In this case, it can be used to specify that a property is a foreign key referring to another entity.
public class Enrollment
{
public int EnrollmentId { get; set; }
[ForeignKey("Student")] // Establishes the relationship with the Student entity
public int StudentId { get; set; }
public Student Student { get; set; } // Navigation property
}
This attribute allows EF Core to know that StudentId
is a foreign key referencing the Student
table.
The [InverseProperty]
attribute is used to specify the inverse navigation property in a relationship between two entities.
This is useful when there are multiple relationships between the same entities, and EF Core needs help determining how they relate.
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
[InverseProperty("Student")] // Establishes the inverse navigation property
public ICollection<Enrollment> Enrollments { get; set; }
}
This attribute tells EF Core that the Enrollments
property in Student
is the inverse of the Student
property in Enrollment
, avoiding ambiguities in relationship mapping.
Complete Example
Let’s look at it with a complete example of what the data model for a very simple blog could look like, one that only had Author and Posts.
[Table("BlogPosts")]
public class Post
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PostId { get; set; }
[Required]
[MaxLength(200)]
public string Title { get; set; }
[Column("Content", TypeName = "nvarchar(max)")]
public string Content { get; set; }
[ForeignKey("Author")]
public int AuthorId { get; set; }
public Author Author { get; set; }
public ICollection<Comment> Comments { get; set; }
}
[Table("Authors")]
public class Author
{
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
[InverseProperty("Author")]
public ICollection<Post> Posts { get; set; }
}
Here we can clearly see the strengths and weaknesses of Data Annotations. It’s simple and convenient, but it “clutters” our objects a lot.