Data Annotations are a set of predefined attributes used in C# to apply specific configurations to class properties.
These attributes help configure how the entities and properties of our model should be treated when mapped to database tables and columns.
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 need 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 one.
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 a property’s values 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 text string.
public class Product
{
[MaxLength(100)]
public string Name { get; set; } // NVARCHAR(100)
}
If you try to save a text string that exceeds 100 characters, EF Core will reject the operation.
The [StringLength] attribute defines a maximum and optionally a minimum length.
[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 that refers 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 lets EF Core 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 are related.
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 see a complete example of what a very simple blog data model could look like, with only 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 you can clearly see the strengths and weaknesses of Data Annotations. It’s simple and convenient, but it “clutters” our objects a lot.
