Share via


Entity Framework Core shadow properties (C#)

Introduction

Shadow properties are properties that are not defined in your .NET entity class but are defined for that entity type in the EF Core model. The value and state of these properties is maintained purely in the Change Tracker. Shadow properties are useful when there is data in the database that should not be exposed on the mapped entity types. Code samples presented will provided simple, useful examples for using shadow properties using a class project for Entity Framework Core code logic with a Windows form project to present data from a SQL-Server database,

Configuring shadow properties

Using the two tables, shadow properties will be LastUpdated, LastUser, CreatedBy, CreatedAt and IsDeleted. 

Open the dbContext class for a project, traverse to OnModelCreating and add the following for each column to be defined as a shadow property.

modelBuilder.Entity<T>().Property<Type>("PropertyName");

For the properties mentioned earlier this is what's needed to define those properties as shadow properties in OnModelCreating method.

modelBuilder.Entity<Contact1>().Property<DateTime?>("LastUpdated");
modelBuilder.Entity<Contact1>().Property<string>("LastUser");
modelBuilder.Entity<Contact1>().Property<DateTime?>("CreatedAt");
modelBuilder.Entity<Contact1>().Property<string>("CreatedBy");
modelBuilder.Entity<Contact1>().Property<bool>("isDeleted");

Since information read, added, edited and removed will need to be seen in a DataGridView and several TextBoxes the model implements INotificationPropertyChanged so the model shown below has been modified after reverse engineering the database table. The properties in the region only are needed if those properties will be displayed in the user interface and if not they can be removed from the model.

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Runtime.CompilerServices;
 
namespace Backend.Models
{
  [Table("Contact1")]
  public partial  class Contact1 : INotifyPropertyChanged
  {
    private int  _contactId;
    private string  _firstName;
    private string  _lastName;
    private DateTime? _lastUpdated;
    private string  _lastUser;
    private DateTime? _createdAt;
    private string  _createdBy;
 
    public int  ContactId
    {
      get => _contactId;
      set
      {
        _contactId = value;
        OnPropertyChanged();
      }
    }
 
    public string  FirstName
    {
      get => _firstName;
      set
      {
        _firstName = value;
        OnPropertyChanged();
      }
    }
 
    public string  LastName
    {
      get => _lastName;
      set
      {
        _lastName = value;
        OnPropertyChanged();
      }
    }
    #region Not needed for shadow properties only for displaying in DataGridView
 
    public DateTime? LastUpdated
    {
      get => _lastUpdated;
      set
      {
        _lastUpdated = value;
        OnPropertyChanged();
      }
    }
 
    public string  LastUser
    {
      get => _lastUser;
      set
      {
        _lastUser = value;
        OnPropertyChanged();
      }
    }
 
    public DateTime? CreatedAt
    {
      get => _createdAt;
      set
      {
        _createdAt = value;
        OnPropertyChanged();
      }
    }
 
    public string  CreatedBy
    {
      get => _createdBy;
      set
      {
        _createdBy = value;
        OnPropertyChanged();
      }
    }
    #endregion
 
 
    public override  string ToString() => $"{FirstName} {LastName}";
 
    public event  PropertyChangedEventHandler PropertyChanged;
    protected virtual  void OnPropertyChanged([CallerMemberName]  string  propertyName = null)
    {
      PropertyChanged?.Invoke(this, new  PropertyChangedEventArgs(propertyName));
    }
  }
}

Setting shadow property values

Shadow property values can be set in a method prior to executing SaveChanges or SaveChangesAsync. The following overridden SaveChanges, first a scan is done for tracked changes using ChangeTracker.DetectChanges.

Those entities which have change are looped through with a conditional for add/modified and deleted. If added or modified the LastUpdated and LastUser property values are set then an additional if for added records which will set the CreatedAt and CreatedBy properties. For records marked for removal their state is changed to Modified and the IsDeleted property is set to true. Note for records marked IsDeleted = true the following filters deleted records. Note that soft deletes to work properly outside of Entity Framework core code those who work with this data need to understand the IsDeleted property.

modelBuilder.Entity<Contact1>()
  .HasQueryFilter(m => 
    EF.Property<bool>(m, "isDeleted") == false);

Once the above steps have been followed a developer can forget about the columns setup as shadow properties with a caution, if the model property changes in the underlying database table make sure to update code which touches the columns. 

Take time to read the Microsoft documentation for in depth details on other aspects of shadow properties.

Front end code samples

In a Windows Form project there are two forms, ContactForm which works solely with adding/modifying data while taking advantage of proper data binding to immediately reflect changes while Contact1Form tacks on removal of records.

When a shadow property are not defined in the model the following shows how to obtain their values. In general the main place to use these values in in a filter or within SSMS.

var contact = _bindingListContacts[dataGridView1.CurrentRow.Index];
var lastUser = (string)_context.Entry(contact).Property("LastUser").CurrentValue;
var lastUpdated = (DateTime?)_context.Entry(contact).Property("LastUpdated").CurrentValue;
var creationDate = (DateTime?)_context.Entry(contact).Property("CreatedAt").CurrentValue;

Summary

Shadow properties provide options to work with properties not defined in a model were in some cases using  A column in the database table to have a default value may not work according to business requirement. Best to seek a database solution then if this fails to meet business requirements consider shadow properties. 

There are two cases for using shadow properties

  • When foreign key properties are need for relations without exposing them to the public eye for security reasons.
  • For columns which are handled by the backend e.g. setting last changed.

See also

External resources

Source code

See the following GitHub repository which includes full source code, database creation scripts. Before building the solution first perform NuGet restore all packages.