Entity Framework Core introduced a new type of property called shadow property which did not exist in EF 6.x.
Shadow properties are the properties that are not defined in your .NET entity class directly; instead, you configure them for the particular entity type in the entity data model. They can be configured in the OnModelCreating() method of the context class.
The following figure illustrates the shadow property.
As you can see in the above figure, shadow properties are not part of your entity class. So, you cannot access them as you access other properties of an entity. Shadow properties can only be configured for an entity type while building an Entity Data Model and they are also mapped to a database column. The value and state of the shadow properties are maintained purely in the Change Tracker.
Let's understand the practical aspect of the shadow property. Assume that we need to maintain the created and updated date of each record in the database table.
You learned how to set created and modified date of entities in EF Core by defining CreatedDate and UpdatedDate properties in entity classes.
Here, we will see how to achieve the same result by using shadow properties without including them in entity classes.
Consider the following Student entity class.
public class Student { public int StudentID { get; set; } public string StudentName { get; set; } public DateTime? DateOfBirth { get; set; } public decimal Height { get; set; } public float Weight { get; set; } }
The above Student class does not include CreatedDate and UpdatedDate properties to maintain created or updated time.
We will configure them as shadow properties on the Student entity.
You can define the shadow properties for an entity type using the Fluent API in the OnModelCreating() using the Property() method.
The following configures two shadow properties CreatedDate and UpdatedDate on the Student entity.
public class SchoolContext : DbContext { public SchoolContext() : base() { } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Student>().Property<DateTime>("CreatedDate"); modelBuilder.Entity<Student>().Property<DateTime>("UpdatedDate"); } public DbSet<Student> Students { get; set; } }
As you can see, the Property() method is used to configure a shadow property. Specify the name of the shadow property as a string and the type as a generic parameter. If the name specified in the Property() method matches the name of an existing property, then the EF Core will configure that existing property as a shadow property rather than introducing a new shadow property.
Once we define shadow properties, we need to update the database schema because shadow properties will be mapped to the corresponding database column.
To do this, add database migration using the following command in Package Manager Console in Visual Studio.
PM> add-migration <migration-name>
Now, the Student table will include two columns, CreatedDate and UpdatedDate in SQL Server, as shown below.
Thus, the database will have corresponding columns even if we haven't included these properties in the Student class and configured them as shadow properties.
You can get or set the values of the shadow properties using the Property() method of EntityEntry. The following code accesses the value of the shadow property.
using (var context = new SchoolContext()) { var std = new Student(){ StudentName = "Bill" }; // sets the value to the shadow property context.Entry(std).Property("CreatedDate").CurrentValue = DateTime.Now; // gets the value of the shadow property var createdDate = context.Entry(std).Property("CreatedDate").CurrentValue; }
However, in our scenario, we want to set the value to these shadow properties automatically on the SaveChanges() method, so that we don't have to set them manually on each entity object. So, override the SaveChanges() method in the context class, as shown below.
public override int SaveChanges() { var entries = ChangeTracker .Entries() .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified); foreach (var entityEntry in entries) { entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now; if (entityEntry.State == EntityState.Added) { entityEntry.Property("CreatedDate").CurrentValue = DateTime.Now; } } return base.SaveChanges(); }
This will automatically set values to CreatedDate and UpdatedDate shadow properties.
Now, execute the following code and check the record in the database.
using (var context = new SchoolContext()) { var std = new Student(){ StudentName = "Bill" }; context.Add(std); context.SaveChanges(); }
The above code will insert the following record with CreatedDate and UpdatedDate in the Students table.
Thus, by configuring shadow properties, we don't have to include them in the entity class.
You can configure shadow properties on all entities at once, rather than configuring them manually for each.
For example, we can configure CreatedDate and UpdatedDate on all the entities at once, as shown below.
protected override void OnModelCreating(ModelBuilder modelBuilder) { var allEntities = modelBuilder.Model.GetEntityTypes(); foreach (var entity in allEntities) { entity.AddProperty("CreatedDate",typeof(DateTime)); entity.AddProperty("UpdatedDate",typeof(DateTime)); } }
Shadow properties can be used in two scenarios:
Learn how to check shadow properties at runtime.