database
EF Core Shadow Properties
Use shadow properties in Entity Framework Core.
By Emem IsaacDecember 30, 20222 min read
#entity framework#shadow properties#configuration#tracking
Share:
A Simple Analogy
Shadow properties are like secret attributes. They exist in the database but not in your C# class.
Why Shadow Properties?
- Clean entities: Keep DTOs simple
- Audit tracking: Add CreatedAt, ModifiedBy
- Infrastructure: Don't pollute domain
- Flexibility: Change without class changes
- Relationships: Foreign keys optional
Defining Shadow Properties
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.Property<DateTime>("CreatedAt")
.HasDefaultValueSql("GETUTCDATE()");
modelBuilder.Entity<User>()
.Property<DateTime?>("ModifiedAt");
modelBuilder.Entity<User>()
.Property<string>("ModifiedBy")
.HasMaxLength(256);
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
// CreatedAt, ModifiedAt, ModifiedBy are shadow properties
}
Accessing Shadow Properties
var user = new User { Name = "Alice" };
context.Users.Add(user);
// Set shadow property via Entry
context.Entry(user).Property("CreatedAt").CurrentValue = DateTime.UtcNow;
context.Entry(user).Property("ModifiedBy").CurrentValue = "admin";
await context.SaveChangesAsync();
// Read shadow property
var createdAt = context.Entry(user).Property("CreatedAt").CurrentValue;
Console.WriteLine($"User created at: {createdAt}");
Querying with Shadow Properties
// Include shadow properties in queries
var users = await context.Users
.Select(u => new
{
u.Id,
u.Name,
CreatedAt = EF.Property<DateTime>(u, "CreatedAt"),
ModifiedBy = EF.Property<string>(u, "ModifiedBy")
})
.ToListAsync();
foreach (var user in users)
{
Console.WriteLine($"{user.Name} created at {user.CreatedAt} by {user.ModifiedBy}");
}
Interceptor Pattern
public class AuditInterceptor : SaveChangesInterceptor
{
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken ct = default)
{
var context = eventData.Context;
foreach (var entry in context.ChangeTracker.Entries())
{
if (entry.State == EntityState.Added)
{
entry.Property("CreatedAt").CurrentValue = DateTime.UtcNow;
entry.Property("CreatedBy").CurrentValue = GetCurrentUser();
}
if (entry.State == EntityState.Modified)
{
entry.Property("ModifiedAt").CurrentValue = DateTime.UtcNow;
entry.Property("ModifiedBy").CurrentValue = GetCurrentUser();
}
}
return base.SavingChangesAsync(eventData, result, ct);
}
}
// Register
builder.Services.AddDbContext<AppContext>(options =>
options.AddInterceptors(new AuditInterceptor()));
Best Practices
- Use for audit: CreatedAt, ModifiedAt
- Keep simple: Avoid complex logic
- Document clearly: Shadow properties are hidden
- Test queries: Ensure they work
- Consider visibility: When to use vs properties
Related Concepts
- Entity auditing
- Change tracking
- Database computed columns
- Interceptors
Summary
Shadow properties add columns to the database without adding properties to your C# classes. Useful for audit fields and infrastructure concerns.
Share:
Related Articles
database
EF Core JSON Columns
Store and query JSON data with Entity Framework Core.
Read More databaseEF Core Migrations
Version control for your database schema with Entity Framework Core.
Read More databaseEF Core Performance Optimization
Optimize Entity Framework Core queries for maximum performance.
Read More