Step by step: Entity Framework Core and Shadow Properties

Today I’ll show you how to create a small console application with a Step by step: Entity Framework Core and Shadow Properties example.

We’ll be modifying the code from my previous post: Step by step: .NET Core and Entity Framework Core to make the Student entity auditable without creating public/protected properties or private fields to hold the typical audit info (CreatedBy, CreatedAt, UpdatedBy and UpdatedAt) database fields.

This means that your domain entities won’t expose the audit information which almost in every case is not part of the business behavior you’ll be trying to model.

First be aware of the following prerequisites:

Now let’s start:

1. Create a folder for your new project


Open a command promt an run

1mkdir efcore.shadowproperties.console

2. Create the project


1cd efcore.shadowproperties.console
2dotnet new

3. Create a settings file


Create an appsettings.json file to hold your connection string information. We’ll be using **SQLite **for this example, so add these lines:

1{
2  "ConnectionStrings": {
3    "Sample": "Data Source=sample.db"
4  }
5}

4. Modify the project file


Modify the project.json to add the EntityFrameworkCore dependencies and also specify that the appsettings.json file must be copied to the output (buildOptionssection) so it becomes available to the application once you build it.

Note that you’ll also have to add System.Security.Claims to simulate an authenticated user:

 1{
 2  "version": "1.0.0-*",
 3  "buildOptions": {
 4    "debugType": "portable",
 5    "emitEntryPoint": true,
 6    "copyToOutput": {
 7      "include": "appsettings.json"
 8    }
 9  },
10  "dependencies": {
11    "Microsoft.Extensions.Configuration": "1.0.0",
12    "Microsoft.Extensions.Configuration.Json": "1.0.0",
13    "Microsoft.EntityFrameworkCore": "1.0.0",
14    "Microsoft.EntityFrameworkCore.Sqlite": "1.0.0",
15    "System.Security.Claims": "4.0.1"
16  },
17  "frameworks": {
18    "netcoreapp1.0": {
19      "dependencies": {
20        "Microsoft.NETCore.App": {
21          "type": "platform",
22          "version": "1.0.0"
23        }
24      },
25      "imports": "dnxcore50"
26    }
27  }
28}

5. Restore packages


You just modified the project.json file with new dependencies so please restore the packages with the following command:

1dotnet restore

6. Create a marker interface in order to make your entities auditable


Every entity you mark with this interface will save audit info to the database.

1namespace ConsoleApplication
2{
3    public interface IAuditable
4    {
5
6    }   
7}

7. Create the Entity Framework context


Create a SampleContext.cs file and copy the following code

  1namespace ConsoleApplication
  2{
  3    using System;
  4    using System.Linq;
  5    using System.Reflection;
  6    using Microsoft.EntityFrameworkCore;
  7    using Microsoft.EntityFrameworkCore.ChangeTracking;
  8    using System.Security.Claims;
  9
 10    /// <summary>
 11    /// The entity framework context with a Students DbSet 
 12    /// </summary>
 13    public class StudentsContext : DbContext
 14    {
 15        public StudentsContext(DbContextOptions<StudentsContext> options)
 16            : base(options)
 17        { }
 18
 19        public DbSet<Student> Students { get; set; }
 20
 21        /// <summary>
 22        /// We overrride OnModelCreating to map the audit properties for every entity marked with the 
 23        /// IAuditable interface.
 24        /// </summary>
 25        /// 
 26        protected override void OnModelCreating(ModelBuilder modelBuilder)
 27        {
 28            foreach (var entityType in modelBuilder.Model.GetEntityTypes()
 29                .Where(e => typeof(IAuditable).IsAssignableFrom(e.ClrType)))
 30            {
 31                    modelBuilder.Entity(entityType.ClrType)
 32                        .Property<DateTime>("CreatedAt");
 33
 34                    modelBuilder.Entity(entityType.ClrType)
 35                        .Property<DateTime>("UpdatedAt");
 36
 37                    modelBuilder.Entity(entityType.ClrType)
 38                        .Property<string>("CreatedBy");
 39
 40                    modelBuilder.Entity(entityType.ClrType)
 41                        .Property<string>("UpdatedBy");
 42            }
 43
 44            base.OnModelCreating(modelBuilder);
 45        }
 46
 47        /// <summary>
 48        /// Override SaveChanges so we can call the new AuditEntities method.
 49        /// </summary>
 50        /// <returns></returns>
 51        public override int SaveChanges()
 52        {
 53            this.AuditEntities();
 54
 55            return base.SaveChanges();
 56        }
 57        
 58        /// <summary>
 59        /// Method that will set the Audit properties for every added or modified Entity marked with the 
 60        /// IAuditable interface.
 61        /// </summary>
 62        private void AuditEntities()
 63        {
 64
 65            // Get the authenticated user name 
 66            string userName = string.Empty;
 67
 68            var user = ClaimsPrincipal.Current;
 69            if (user != null)
 70            {
 71                var identity = user.Identity;
 72                if (identity != null)
 73                {
 74                    userName = identity.Name;
 75                }
 76            }
 77
 78            // Get current date & time
 79            DateTime now = DateTime.Now;
 80
 81            // For every changed entity marked as IAditable set the values for the audit properties
 82            foreach (EntityEntry<IAuditable> entry in ChangeTracker.Entries<IAuditable>())
 83            {
 84                // If the entity was added.
 85                if (entry.State == EntityState.Added)
 86                {
 87                    entry.Property("CreatedBy").CurrentValue = userName;
 88                    entry.Property("CreatedAt").CurrentValue = now;
 89                }
 90                else if (entry.State == EntityState.Modified) // If the entity was updated
 91                {
 92                    entry.Property("UpdatedBy").CurrentValue = userName;
 93                    entry.Property("UpdatedAt").CurrentValue = now;
 94                }
 95            }
 96        }
 97    }
 98
 99    /// <summary>
100    /// A factory to create an instance of the StudentsContext 
101    /// </summary>
102    public static class StudentsContextFactory
103    {
104        public static StudentsContext Create(string connectionString)
105        {
106            var optionsBuilder = new DbContextOptionsBuilder<StudentsContext>();
107            optionsBuilder.UseSqlite(connectionString);
108
109            var context = new StudentsContext(optionsBuilder.Options);
110            context.Database.EnsureCreated();
111
112            return context;
113        }
114    }
115
116    /// <summary>
117    /// A simple class representing a Student.
118    /// We've marked this entity as IAditable so we can save audit info for the entity.
119    /// </summary>
120    public class Student : IAuditable
121    {
122        public Student()
123        {
124        }
125
126        public int Id { get; set; }
127
128        public string Name { get; set; }
129
130        public string LastName { get; set; }
131    }
132}

Note that the beauty of shadow properties is that both the IAuditable interface and the Student entity don’t expose Audit info. We are mapping properties that are not represented in our domain model!

8. Modify Program.cs


Replace the contents of the Program.cs file with the following code

 1namespace ConsoleApplication
 2{
 3    using System;
 4    using Microsoft.Extensions.Configuration;
 5
 6    public class Program
 7    {
 8        public static void Main(string[] args)
 9        {
10            // Enable to app to read json setting files
11            var builder = new ConfigurationBuilder()
12                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
13
14            var configuration = builder.Build();
15
16            // Get the connection string
17            string connectionString = configuration.GetConnectionString("Sample");
18
19            // With this code we are going to simulate an authenticated user.
20            ClaimsPrincipal.ClaimsPrincipalSelector = () =>
21                {
22                    return new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("cmendibl3")));
23                };
24
25            // Create a Student instance
26            var user = new Student() { Name = "Carlos", LastName = "Mendible" };
27
28            // Add and Save the student in the database
29            using (var context = StudentsContextFactory.Create(connectionString))
30            {
31                context.Add(user);
32                context.SaveChanges();
33            }
34
35            Console.WriteLine($"Student was saved in the database with id: {user.Id}");
36        }
37    }
38}

Line 22 is the one were we simulate an authenticated user.

9. Build


Build the application with the following command

1dotnet build

10. Run


You are good to go so run the application

1dotnet run

If you check your SQlite database with a tool such as DB Browser for SQlite you’ll see the four Audit properties in the Students table as shown in the following picture:

You can get the code here: https://github.com/cmendible/dotnetcore.samples/tree/main/efcore.shadowproperties.console

Hope it helps!


Step by step: .NET Core, Azure Service Bus and AMQP
Detect and Blur Faces with .NET Core and Face API
comments powered by Disqus