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:
Create a folder for your new project
Open a command promt an run
1mkdir efcore.shadowproperties.console
Create the project
1cd efcore.shadowproperties.console
2dotnet new
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}
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}
Restore packages
You just modified the project.json file with new dependencies so please restore the packages with the following command:
1dotnet restore
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}
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!
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.
Build
Build the application with the following command
1dotnet build
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!
Comments