Entity Framework Core – Shadow Properties

Today’s header image was created by Brad Huchteman, the original source for the image is available here

What Are Shadow Properties?

Let’s say that you wanted to store some values against a database record (for example, audit data), but you didn’t want to expose those values to a consumer of your domain model in an application. That’s where you could use Shadow Properties. This means that your domain model is simpler, with less exposed data, meaning that you could hide any complex or sensitive data from the consumer of the domain model.

It also means that you don’t need to waste time mapping those fields to view models (because they won’t be there on the domain model)

The values of all Shadow Properties are available via the Change Tracker (we’ll see how to access them, in a moment), which means that code which exposes them requires access to the ChangeTracker – usually this will be in DB services and other Data Layer components.

Shadow Properties also allow developers to add fields to a Database Entry (via migrations, mainly) without having to create the relative properties in the domain model. This happens most often when a DBA needs to add a properties to a Database Entity after the schema has been defined and tables created.

You ARE getting a DBA to check your schema designs, right?

Each time that a new Shadow Property is added to a data model, a migration is added and run against the target database. So it is easy to keep track of them and how they are applied.

Not to mention them being committed into source control, too.

Where You Might Use Them

Let’s look at an example where we might use Shadow Properties to implement basic auditing (Created, CreatedBy, Modified and ModifiedBy). We’re going to create a Book entity and class, have it implement an Interface (IAuditable), have Shadow Properties attached to it via the IAuditable interface, and update those properties when we persist to the database.

Creating The Domain Model

Before we can add Shadow Properties to anything, we need a domain model. As I said earlier, we’re going to create a really simple Book entity. Let’s take a look:

namespace DataModels
{
public class Book : IAuditable
{
public int BookId { get; set; }
public string BookName { get; set; }
}
}
view raw Book.cs hosted with ❤ by GitHub

Here we’ve created the Book data model, which is simple enough. Just an Id and Name pair (for now).

namespace DataModels
{
public interface IAuditable
{
}
}
view raw IAuditable.cs hosted with ❤ by GitHub

Here we’ve created an IAuditable interface. This, we’ll see, is only used to help us target all entity classes which we want to add our Audit properties (as Shadow Properties) to.

Creating The Shadow Properties

We typically add the Shadow Properties to database schemas when we’re creating the DbModel, and we typically do that in the DbContext. Here’s our ExampleContext class (we’ll dissect it in a moment):

using DataModels;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
namespace DataLayer.Contexts
{
public class ExampleContext : DbContext
{
public ExampleContext(DbContextOptions<DwContext> options) : base(options) { }
public ExampleContext() { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Create shadow properties
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(IAuditable).IsAssignableFrom(e.ClrType)))
{
modelBuilder.Entity(entityType.ClrType)
.Property<DateTime>("Created");
modelBuilder.Entity(entityType.ClrType)
.Property<DateTime>("Modified");
modelBuilder.Entity(entityType.ClrType)
.Property<string>("CreatedBy");
modelBuilder.Entity(entityType.ClrType)
.Property<string>("ModifiedBy");
}
base.OnModelCreating(modelBuilder);
}
public override int SaveChanges()
{
ApplyAuditInformation();
return base.SaveChanges();
}
private void ApplyAuditInformation()
{
var modifiedEntities = ChangeTracker.Entries<IAuditable>()
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified);
foreach (var entity in modifiedEntities)
{
entity.Property("Modified").CurrentValue = DateTime.UtcNow;
if (entity.State == EntityState.Added)
{
entity.Property("Created").CurrentValue = DateTime.UtcNow;
}
}
}
public DbSet<Book> Books { get; set; }
}
}
view raw ExampleContext.cs hosted with ❤ by GitHub

Now that you’ve read through that, let’s take a look at the three methods which use Shadow Properties. First, we’ll look at how we add them to the model:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Create shadow properties
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(IAuditable).IsAssignableFrom(e.ClrType)))
{
modelBuilder.Entity(entityType.ClrType)
.Property<DateTime>("Created");
modelBuilder.Entity(entityType.ClrType)
.Property<DateTime>("Modified");
modelBuilder.Entity(entityType.ClrType)
.Property<string>("CreatedBy");
modelBuilder.Entity(entityType.ClrType)
.Property<string>("ModifiedBy");
}
base.OnModelCreating(modelBuilder);
}
view raw ExampleContext.cs hosted with ❤ by GitHub

In our OnModelCreating method, we’re looping through all of the entity types in the model builder which are of type (or implement) IAuditble, then adding our Shadow Properties to them.

Next, let’s take a look at how we can update the values for our Shadow Properties:

public override int SaveChanges()
{
ApplyAuditInformation();
return base.SaveChanges();
}
private void ApplyAuditInformation()
{
var modifiedEntities = ChangeTracker.Entries<IAuditable>()
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified);
foreach (var entity in modifiedEntities)
{
entity.Property("Modified").CurrentValue = DateTime.UtcNow;
if (entity.State == EntityState.Added)
{
entity.Property("Created").CurrentValue = DateTime.UtcNow;
}
}
}
view raw ExampleContext.cs hosted with ❤ by GitHub

When we call our SaveChanges method, the ApplyAuditInformation method will be called.This iterates through all of the entities in the change tracker which have been created or modified and are of type (or implement) IAuditable, and adds our audit information to them.

Using The Shadow Properties

Shadow Properties can only be used where we have access to the change tracker. It just so happens that we have access to the change tracker when we use the Microsoft.EntityFrameworkCore namespace, so we can access it in our services.

Let’s say that we have a BookService class (and it’s interface, so that we can make use of dependency Injection).

If you don’t remember how we use DI to inject services, then take a look at my first post on EF Core.

using DataModels;
using System.Collections.Generic;
namespace Services
{
public interface IBookService
{
Book FindById(int id);
IEnumerable<Book> GetAll();
}
}
view raw IBookService.cs hosted with ❤ by GitHub

Nothing too exciting in the interface.

In a real project, you might not make use of a GetAll because of the potential number of records you will end up returning.

Better to be specific, rather than blasé, in your queries.

The real stuff is in the implementation of our BookService, so that’s next:

using DataModels;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
namespace Services
{
public class BookService : IBookService
{
private ExampleContext _context;
public BookService (ExampleContext context)
{
_context = context;
}
public Book FindById(int id)
{
return _context.FirstOrDefault(book => book.BookId == id);
}
public IEnumerable<Book> GetAll()
{
return _context.Books.OrderBy(b => EF.Property<DateTime>(b, "Created"));
}
}
}
view raw BookService.cs hosted with ❤ by GitHub

In our GetAll method, we’re performing an OrderBy using the ShadowProperty “Created”.

I’ve highlighted the line where this happens.

The only problem here is that we’ve had to hard code the string name for the Shadow Property that we want. Meaning that if we wanted to use the same OrderBy in several methods, then we run the risk of misspelling it and causing a run time exception.

Let’s take a look some ways to we can get around this:

using DataModels;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
namespace Services
{
public class BookService : IBookService
{
private ExampleContext _context;
private readonly string NameOfCreatedField = "Created";
public BookService (ExampleContext context)
{
_context = context;
}
public Book FindById(int id)
{
return BaseQuery().FirstOrDefault(book => book.BookId == id);
}
public IEnumerable<Book> GetAll()
{
return BaseQuery();
}
private IEnumerable<Book> BaseQuery()
{
return _context.Books.OrderBy(b => EF.Property<DateTime>(b, NameOfCreatedField));
}
}
}
view raw BetterBookService.cs hosted with ❤ by GitHub

Looking at each of the highlighted lines in turn:

private readonly string NameOfCreatedField = "Created";
view raw BetterBookService.cs hosted with ❤ by GitHub

Here we’re making a private readonly string which contains the name of our Shadow Property. We could just as easily have this as a writable variable and pass it into the class constructor, or read it from an external config file (perhaps a strings.json, or a web.config), but we’ll leave it like this for now.

private IEnumerable<Book> BaseQuery()
{
return _context.Books.OrderBy(b => EF.Property<DateTime>(b, NameOfCreatedField));
}
view raw BetterBookService.cs hosted with ❤ by GitHub

Here we’ve created a private method for this class which will perform a base query for us. Our other methods will build on this base query. Since each method in this class will work on the Books DbSet, we can use this method in each of the public facing methods in this class.

public Book FindById(int id)
{
return BaseQuery().FirstOrDefault(book => book.BookId == id);
}
public IEnumerable<Book> GetAll()
{
return BaseQuery();
}
view raw BetterBookService.cs hosted with ❤ by GitHub

Here we’re making use of that BaseQuery method, which will return the records ordered by Created date.

Ordering by Created date then picking a record by Id seems a little redundant here.

Seeing as the Id is the Primary Key, it shouldn’t matter how the records are ordered. I’ve included it here just as an example, you wouldn’t do this with a real code base.

We could extend our BaseQuery method to include no tracking, which essentially tells the change tracker that we don’t expect to make changes with to the data that we’ll get from our query. Here is how we would do that:

private IEnumerable<Book> BaseQuery()
{
return _context.Books.AsNoTracking().OrderBy(b => EF.Property<DateTime>(b, NameOfCreatedField));
}

The problem with this, is that ALL of our queries would (effectively) be read only. This means that the change tracker wouldn’t track any changes made to our Books entities, and thus would not persist any of those changes back to the database.

Conclusion

We’ve seen how audit data (and other fields) can be applied to the rows in a database table, and how those values are hidden from the consumer of your data models. We’ve also seen how these Shadow Properties can be accessed via Entity Framework Core’s Change Tracker.

Its been a little bit of a shorter post today (compared to some of my longer ones of recent weeks), but now we know almost everything we need to know in order to run through the next of my tutorials. Next time: we’re going to start building a webApi application, which serves JSON data based on database records using EF Core. 

Does that sound exciting, or what?

A more advanced version of what we’ll build, is already in use by one of my own projects – see if you can spot it on Github.com and maybe leave a comment, if you can.

Related Posts

A .NET developer specialising in ASP.NET MVC websites and services, with a background in WinForms and Games Development. When not programming using .NET, he is either learning about .NET Core (and usually building something cross platform with it), speaking Japanese to anyone who'll listen, learning about languages, writing for his non-dev blog, or writing for a blog about video games (which he runs with his brother)