How to separate entity's primary key from base class? - c#

I want to accomplish clean architecture with EF Core.
I trid to separate primary key from base class, but it failed with below exception.
System.InvalidOperationException: 'A key cannot be configured on 'ManagerEntity' because it is a derived type. The key must be configured on the root type 'Manager'. If you did not intend for 'Manager' to be included in the model, ensure that it is not referenced by a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation on a type that is included in the model.'
Core
public class Manager
{
public Manager(Guid identifier, string email)
{
Identifier = identifier;
Email = email;
}
public Guid Identifier { get; }
public string Email { get; }
public void FixPrinter(Printer printer)
{
printer.IsOutOfControl = true;
}
}
public class Printer
{
public Printer(Guid token)
{
Token = token;
Manager = null;
IsOutOfControl = false;
}
public Guid Token { get; }
public Manager? Manager { get; set; }
public bool IsOutOfControl { get; set; }
}
Infrastructure
public class ApplicationContext
: DbContext
{
// ...
public DbSet<ManagerEntity> ManagerSet { get; set; }
public DbSet<PrinterEntity> PrinterSet { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new ManagerEntityConfiguration(Database));
modelBuilder.ApplyConfiguration(new PrinterEntityConfiguration(Database));
}
}
Configure Manager
public sealed class ManagerEntity
: Manager
{
public ManagerEntity(string email)
: base(Guid.Empty, email)
{
}
// Primary key for database.
public long Id { get; }
}
internal sealed class ManagerEntityConfiguration
: IEntityTypeConfiguration<ManagerEntity>
{
private readonly DatabaseFacade _database;
public ManagerEntityConfiguration(DatabaseFacade database)
{
_database = database;
}
public void Configure(EntityTypeBuilder<ManagerEntity> builder)
{
builder
.Property(e => e.Id)
.ValueGeneratedOnAdd();
// The exception occurs here.
builder
.HasKey(e => e.Id);
// ...
}
}
Configure Printer
public sealed class PrinterEntity
: Printer
{
public PrinterEntity()
: base(Guid.Empty)
{
}
// Primary key for database.
public long Id { get; }
}
internal sealed class PrinterEntityConfiguration
: IEntityTypeConfiguration<PrinterEntity>
{
private readonly DatabaseFacade _database;
public PrinterEntityConfiguration(DatabaseFacade database)
{
_database = database;
}
public void Configure(EntityTypeBuilder<PrinterEntity> builder)
{
builder
.Property(e => e.Id)
.ValueGeneratedOnAdd();
builder
.HasKey(e => e.Id);
// ...
}
}
Web API
app.MapPost("/printer", async (ApplicationContext context) =>
{
PrinterEntity printer = new()
{
Manager = new ManagerEntity("master#google.com"),
};
await context.PrinterSet.AddAsync(printer);
await context.SaveChangesAsync();
return printer;
});
Should I architect it by using interface, not inheritance?
At Core,
public interface IPrinter
{
public Manager? Manager { get; set; }
}
At Infrastructure,
public sealed class PrinterEntity : IPrinter
{
// ...
}
Github source code

Thanks, #roji!
https://github.com/dotnet/efcore/issues/27421#issuecomment-1034762908
This is likely happening because you're mapping both Manager and ManagerEntity in your model, which means you're configuring inheritance mapping (i.e. EF thinks you intend to store both Manager and ManagerEntity instances in the database). With inheritance mapping, the key must be specified at the root.
However, it seems like you only want the class separation on the .NET side, without needing any actual hierarchy, so make sure you are not mapping the base class (Manager in the above). See the minimal code sample below.
await using var ctx = new BlogContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
public class BlogContext : DbContext
{
// Uncomment the below to make the exception appear
// public DbSet<Manager> Managers { get; set; }
public DbSet<ManagerEntity> ManagerEntities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(#"Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0")
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ManagerEntity>().HasKey(b => b.Id);
}
}
public class Manager
{
public string Name { get; set; }
}
public class ManagerEntity : Manager
{
public int Id { get; set; }
}
However... if the goal is simply not to expose an Id property on Manager, there are simpler ways to do that rather than introducing a .NET hierarchy. You can have a private _id field instead, which would be used by EF Core but not otherwise exposed in your application, keeping your data model clean (see docs). Alternatively, you can have an Id shadow property, removing the field/property from your CLR type altogether.

Related

Add/update for related entity using aggregates. Having multiple domain classes

This is my main domain Model.
public class Transform : IValue
{
public int Id { get; protected set; }
public string Target { get; set; }
public Transform() { }
}
}
And below is my Mapping Class.
Here I am creating table by using multiple domain classes like DefaultTransform, DirectTransform, InitialTransform and LoadTransform.
public class TransformMapping : IEntityTypeConfiguration<Transform>
{
public void Configure(EntityTypeBuilder<Transform> builder)
{
builder.ToTable("Transform");
builder.HasKey(x => x.Id);
builder.Property(x => x.Id).HasColumnName("TransformId").ValueGeneratedOnAdd();
builder.HasDiscriminator<string>("TransformType")
.HasValue<Transform>("Base")
.HasValue<DefaultTransform>("Default")
.HasValue<DirectTransform>("Direct")
.HasValue<InitialTransform >("Initial")
.HasValue<LoadTransform>("Load");
}
}
Below I have created Db context instance of Transform Class
public class TransRepository : DbContext
{
public virtual DbSet<Transform> Transforms { get; protected set; }
}
And want to add and update the data. But I am unable to get access of property of other domain classes like DefaultTransform, DirectTransform, InitialTransform and LoadTransform.
How can I update and add using above dbContext instance Transforms.

How to implement Composite Pattern using TPT in EF Core?

I am using DDD to implement a domain and using EF Core and SQL as infrastructure for persistence. I have a model which is implemented by "Composite Design Pattern". I use fake name that does not show actual model but consider there is a School entity that has many Rooms:
public class Person: AggregateRoot<long>
{
public long Id { get; set; }
public string Name { get; set; }
private List<Document> _documents;
public IReadOnlyList<Document> Documents=> _documents.AsReadOnly();
}
Document is an abstract class that can be PersonDocument or CategoryDocument:
public abstract class Document: ValueObject
{
public string Title { get; set; }
}
and:
public class PersonDocument : Document
{
public string Code { get; set; }
}
public class CategoryDocument: Document
{
private List<Document> _documents;
public IReadOnlyList<Document> Documents=> _documents.AsReadOnly();
}
Three tables will be generated in SQL. Here is my mapping:
public override void EntityTypeConfiguration(EntityTypeBuilder<Person> builder)
{
builder.ToTable("Persons");
builder.HasKey(w => w.Id);
builder.Property(w => w.Id)
.ValueGeneratedNever();
builder.Property(x => x.Name).IsRequired().HasMaxLength(500);
}
public class DocumentMapping : IEntityTypeConfiguration<Document>
{
public void Configure(EntityTypeBuilder<Document> builder)
{
builder.Property<long>("Id").ValueGeneratedOnAdd();
builder.ToTable("Documents").HasKey("Id");
builder.UsePropertyAccessMode(PropertyAccessMode.Field);
builder.Property(x => x.Title).IsRequired().HasMaxLength(500);
builder.HasOne<Person>().WithMany(x => x.Documents).HasForeignKey("PersonId");
}
}
public class PersonDocumentMapping : IEntityTypeConfiguration<PersonDocument>
{
public void Configure(EntityTypeBuilder<PersonDocument> builder)
{
builder.Property<long>("Id").ValueGeneratedOnAdd();
builder.ToTable("PersonDocuments");
//builder.HasBaseType<Document>();
builder.Property(x => x.Code).IsRequired().HasMaxLength(500);
}
}
public class CategoryDocumentMapping : IEntityTypeConfiguration<CategoryDocument>
{
public void Configure(EntityTypeBuilder<CategoryDocument> builder)
{
builder.Property<long>("Id").ValueGeneratedOnAdd();
builder.ToTable("Categories");
// builder.HasBaseType<Document>();
builder.HasMany<Document>(x => x.Documents).WithOne().HasForeignKey("CategoryId");
}
}
result of mapping is this:
Tables and relations in database is as I expect. But when I try to fetch a person from database, desired person get fetched but its documents don't.

EF Core One-to-One relationship with Shadow properties as a Keys using Fluent API

I can't resolve the One-to-One relationship between two classes using Shadow Properties as a Keys. Unfortunately I can't create real key properties into my classes due to I used 3rd party class library.
using Microsoft.EntityFrameworkCore;
using System;
namespace EFCoreTest
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Create database");
using (var context = new ClassDbContext())
{
if (!context.Database.EnsureDeleted())
{
Console.WriteLine(" => Can't delete database");
}
if (context.Database.EnsureCreated())
{
context.SaveChanges();
Console.WriteLine(" => Done");
}
else
{
Console.WriteLine(" => Can't create database");
}
}
Console.WriteLine("End");
}
}
public class ClassDbContext : DbContext
{
public virtual DbSet<ClassA> ClassA { get; set; }
public virtual DbSet<ClassB> ClassB { get; set; }
public ClassDbContext()
{
}
public ClassDbContext(DbContextOptions<ClassDbContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("Server=XXX.XXX.XXX.XXX;Database=XXXXX;User ID=XXXXX;Password=XXXXX");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ClassA>().Property<int>("ClassAId");
modelBuilder.Entity<ClassB>().Property<int>("ClassBId");
modelBuilder.Entity<ClassB>().Property<int>("AnotherClassId");
modelBuilder.Entity<ClassB>().HasOne(p => p.AnotherClass).WithMany().HasForeignKey("AnotherClassId");
}
}
public class ClassA
{
public string PropertyA { get; set; }
}
public class ClassB
{
public string PropertyB { get; set; }
public ClassA AnotherClass { get; set; }
}
}
EF Core returns with the following error, which is very confused me:
System.InvalidOperationException: 'The relationship from 'ClassB.AnotherClass' to 'ClassA' with foreign key properties
{'AnotherClassId' : int} cannot target the primary key {'ClassAId'
: int} because it is not compatible. Configure a principal key
or a set of compatible foreign key properties for this relationship.'
It turns out that the automatic binding of properties to keys, implemented in EF Core through the naming of properties, does not work in this case!
They require you to force a key name before creating a connection!
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ClassA>().Property<int>("ClassAId");
modelBuilder.Entity<ClassA>().HasKey("ClassAId"); // <=== This is a SOLUTION
modelBuilder.Entity<ClassB>().Property<int>("ClassBId");
modelBuilder.Entity<ClassB>().Property<int>("AnotherClassId");
modelBuilder.Entity<ClassB>().HasOne(p => p.AnotherClass).WithMany().HasForeignKey("AnotherClassId");
}

EF Core - One to many with parent and child Inheritance

Summary
I am trying to use Table Per Hierarchy inheritance in conjunction with a one to many relationship in a .Net Core 1.1 project. Both the Parent and child entities use inheritance.
I have a very simple entity model. I have a one base parent entity: Session which has two entities which extend from it: QuotingSession and BackOfficeSession. Both of these two parent entities contain a collection of child entities (a one to many relationship). The child entities are also built using inheritance. There is a base child entity: Policy. This base child entity is extended by two entities: QuotingPolicy and BackOfficePolicy. When I construct either of the Parent entities and attempt to save I receive this exception:
InvalidCastException: Unable to cast object of type 'EFTest.QuotingSession' to type 'EFTest.BackOfficeSession
However, when I tell entity framework to ignore one of the the child collections on either of the parents, the save works for both parent entities with no exception. For example:
var entity = modelBuilder.Entity<BackOfficeSession>();
entity.Ignore(c => c.Policies);
In addition if I do not configure one of the Parents (QuotingSession) at all, and just save the other Parent (BackOfficeSession), everything saves as excepted.
Repo
https://github.com/seantarogers/EFTest
Details
I am using Microsoft.EntityFrameworkCore 1.1.1 and Microsoft.EntityFrameworkCore.SqlServer 1.1.1 in a .Net core 1.1 project.
I have the following simple database schema:
My classes look like this:
1. Session
namespace EFTest
{
public abstract class Session
{
public int Id { get; private set; }
}
}
2. QuotingSession
using System.Collections.Generic;
namespace EFTest
{
public class QuotingSession : Session
{
public string QuotingName { get; private set; }
public List<QuotingPolicy> Policies { get; private set; }
private QuotingSession()
{
}
public QuotingSession(string name, List<QuotingPolicy> quotingPolicies)
{
QuotingName = name;
Policies = quotingPolicies;
}
}
}
3. BackOfficeSession
using System.Collections.Generic;
namespace EFTest
{
public class BackOfficeSession : Session
{
public List<BackOfficePolicy> Policies { get; private set; }
public string BackOfficeName { get; private set; }
private BackOfficeSession()
{
}
public BackOfficeSession(string name, List<BackOfficePolicy> policies)
{
BackOfficeName = name;
Policies = policies;
}
}
}
4. Policy
namespace EFTest
{
public abstract class Policy
{
public int Id { get; set; }
public int SessionId { get; set; }
}
}
5. QuotingPolicy
namespace EFTest
{
public class QuotingPolicy : Policy
{
public string QuotingPolicyName { get; private set; }
private QuotingPolicy()
{
}
public QuotingPolicy(string name)
{
QuotingPolicyName = name;
}
}
}
6. BackOfficePolicy
namespace EFTest
{
public class BackOfficePolicy : Policy
{
public string BackOfficePolicyName { get; private set; }
private BackOfficePolicy()
{
}
public BackOfficePolicy(string name)
{
BackOfficePolicyName = name;
}
}
}
7. EF DB Context and Fluent Configuration
using Microsoft.EntityFrameworkCore;
namespace EFTest
{
public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet<QuotingSession> QuotingSessions { get; set; }
public DbSet<BackOfficeSession> BackOfficeSessions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
ConfigureSession(modelBuilder);
ConfigurePolicy(modelBuilder);
ConfigureQuotingSession(modelBuilder);
ConfigureBackOfficeSession(modelBuilder);
ConfigureBackOfficePolicy(modelBuilder);
ConfigureQuotingPolicy(modelBuilder);
}
public static void ConfigurePolicy(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<Policy>();
entity.ToTable("Policy", "dbo");
entity.HasKey(x => x.Id);
entity.HasDiscriminator<int>("SessionType")
.HasValue<QuotingPolicy>(1)
.HasValue<BackOfficePolicy>(2);
}
public static void ConfigureBackOfficePolicy(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<BackOfficePolicy>();
entity.Property(x => x.BackOfficePolicyName);
}
public static void ConfigureQuotingPolicy(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<QuotingPolicy>();
entity.Property(x => x.QuotingPolicyName);
}
public static void ConfigureSession(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<Session>();
entity.ToTable("Session", "dbo");
entity.HasKey(x => x.Id);
entity.HasDiscriminator<int>("SessionType")
.HasValue<QuotingSession>(1)
.HasValue<BackOfficeSession>(2);
}
public static void ConfigureBackOfficeSession(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<BackOfficeSession>();
entity.Property(x => x.BackOfficeName);
entity.HasMany(c => c.Policies).WithOne().HasForeignKey(c => c.SessionId);
// entity.Ignore(c => c.Policies); uncomment this to see it working
}
public static void ConfigureQuotingSession(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<QuotingSession>();
entity.Property(x => x.QuotingName);
entity.HasMany(c => c.Policies).WithOne().HasForeignKey(c => c.SessionId);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
}
}
}
8. To test it
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
namespace EFTest
{
class Program
{
static void Main(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<TestDbContext>();
const string conn = "Server=.\\SqlServer2014;Database=EFTest;Trusted_Connection=True"
optionsBuilder.UseSqlServer(conn);
using (var dbContext = new TestDbContext(optionsBuilder.Options))
{
var quotingPolicy = new QuotingPolicy("quotingPolicyname");
var quotingSession = new QuotingSession("quotingSessionName", new List<QuotingPolicy> {quotingPolicy});
dbContext.QuotingSessions.Add(quotingSession);
dbContext.SaveChanges(); // BLOWS UP HERE!
}
}
}
}
Thanks for your help

Use IEntityTypeConfiguration with a base entity

In EF Core 2.0, we have the ability to derive from IEntityTypeConfiguration for cleaner Fluent API mappings (source).
How can I extend this pattern to utilize a base entity? In the example below, how can I have a BaseEntityConfiguration to reduce duplication in LanguageConfiguration and MaintainerConfiguration, modifying properties that are in the BaseEntity only in the BaseEntityConfiguration? What would such a BaseEntityConfiguration look like; and how would it be used, if at all, in OnModelCreating()? See the TODOs in-code near the end of the example.
Example:
public abstract class BaseEntity
{
public long Id { get; set; }
public DateTime CreatedDateUtc { get; set; }
public DateTime? ModifiedDateUtc { get; set; }
}
public class Language : BaseEntity
{
public string Iso6392 { get; set; }
public string LocalName { get; set; }
public string Name { get; set; }
}
public class Maintainer : BaseEntity
{
public string Email { get; set; }
public string Name { get; set; }
}
public class FilterListsDbContext : DbContext
{
public FilterListsDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<Language> Languages { get; set; }
public DbSet<Maintainer> Maintainers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//TODO: Possibly add something like BaseEntityConfiguration?
modelBuilder.ApplyConfiguration(new LanguageConfiguration());
modelBuilder.ApplyConfiguration(new MaintainerConfiguration());
}
}
public class LanguageConfiguration : IEntityTypeConfiguration<Language>
{
public void Configure(EntityTypeBuilder<Language> entityTypeBuilder)
{
//TODO: Move this to something like BaseEntityConfiguration?
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
}
}
public class MaintainerConfiguration : IEntityTypeConfiguration<Maintainer>
{
public void Configure(EntityTypeBuilder<Maintainer> entityTypeBuilder)
{
//TODO: Move this to something like BaseEntityConfiguration?
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
}
}
Something like this could work (untested)?
public abstract class BaseEntityTypeConfiguration<TBase> : IEntityTypeConfiguration<TBase>
where TBase : BaseEntity
{
public virtual void Configure(EntityTypeBuilder<TBase> entityTypeBuilder)
{
//Base Configuration
}
}
public class MaintainerConfiguration : BaseEntityTypeConfiguration<Maintainer>
{
public override void Configure(EntityTypeBuilder<Maintainer> entityTypeBuilder)
{
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
base.Configure(entityTypeBuilder);
}
}
There is another way to solve the problem, and that is to use Template Method Design Pattern. Like this:
public abstract class BaseEntityTypeConfiguration<TBase> : IEntityTypeConfiguration<TBase>
where TBase : BaseEntity
{
public void Configure(EntityTypeBuilder<TBase> entityTypeBuilder)
{
//Base Configuration
ConfigureOtherProperties(builder);
}
public abstract void ConfigureOtherProperties(EntityTypeBuilder<TEntity> builder);
}
public class MaintainerConfiguration : BaseEntityTypeConfiguration<Maintainer>
{
public override void ConfigureOtherProperties(EntityTypeBuilder<Maintainer> entityTypeBuilder)
{
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
}
}
With this way you don't need to write any single line in child configuration.
Another approach if you dont want to repeat the column Definitions for all of your Models that inherit from the same base Entity like this:
protected override void OnModelCreating(ModelBuilder modelBuilder){
modelBuilder.Entity<Order>()
.Property(b => b.CreatedDateTime)
.HasDefaultValueSql("CURRENT_TIMESTAMP ");
modelBuilder.Entity<Adress>()
.Property(b => b.CreatedDateTime)
.HasDefaultValueSql("CURRENT_TIMESTAMP ");
// …
}
is to find all the Entites that inhert from the base Entity, loop over them and call the generic Method as shown below, in which the redundant Logic is placed:
protected override void OnModelCreating(ModelBuilder modelBuilder){
foreach (Type type in GetEntityTypes(typeof(BaseEntity))){
var method = SetGlobalQueryMethod.MakeGenericMethod(type);
method.Invoke(this, new object[] { modelBuilder });
}
}
static readonly MethodInfo SetGlobalQueryMethod = typeof(/*your*/Context)
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Single(t => t.IsGenericMethod && t.Name == "SetGlobalQuery");
public void SetGlobalQuery<T>(ModelBuilder builder) where T : BaseEntity{
builder.Entity<T>().Property(o => o.CreatedDateTime).HasDefaultValueSql("CURRENT_TIMESTAMP");
// Additional Statements
}
For the "GetEntityTypes" Method you need the Nuget Package „Microsoft.Extensions.DependencyModel“
private static IList<Type> _entityTypeCache;
private static IList<Type> GetEntityTypes(Type type)
{
if (_entityTypeCache != null && _entityTypeCache.First().BaseType == type)
{
return _entityTypeCache.ToList();
}
_entityTypeCache = (from a in GetReferencingAssemblies()
from t in a.DefinedTypes
where t.BaseType == type
select t.AsType()).ToList();
return _entityTypeCache;
}
private static IEnumerable<Assembly> GetReferencingAssemblies()
{
var assemblies = new List<Assembly>();
var dependencies = DependencyContext.Default.RuntimeLibraries;
foreach (var library in dependencies)
{
try
{
var assembly = Assembly.Load(new AssemblyName(library.Name));
assemblies.Add(assembly);
}
catch (FileNotFoundException)
{ }
}
return assemblies;
}
Its a bit hacky in my opinion, but works fine for me!
The source with more details:
https://www.codingame.com/playgrounds/5514/multi-tenant-asp-net-core-4---applying-tenant-rules-to-all-enitites
I'm late to the party, but this is what I did in the OnModelCreating method to achieve similar results.
Basically, I have (4) properties that inherit from a BaseEntity. Two of those are dates why two are strings.
For the dates, I wanted the default to be SQL's GETUTCDATE and the string to be "SystemGenerated." Using a static helper that allows me to retrieve the property name from BaseEntity in a strongly-typed manner, I grab the (4) property names. Then, I iterate over all of the iterate over all of the ModelBuilder entities after my primary mappings are set-up. This allows modelBuilder.Model.GetEntityTypes to return the entities that the modelBuidler is aware of. Then it's a matter of looking at the ClrType.BaseType to see if the type inherits from my BaseEntity and setting the defaults on the PropertyBuilder.
I tested this directly and through EF Migrations which confirmed that the proper SQL was generated.
var createdAtUtc = StaticHelpers.GetPropertyName<BaseEntity>(x => x.CreatedAtUtc);
var lastModifiedAtUtc = StaticHelpers.GetPropertyName<BaseEntity>(x => x.LastModifiedAtUtc);
var createdBy = StaticHelpers.GetPropertyName<BaseEntity>(x => x.CreatedBy);
var lastModifiedBy = StaticHelpers.GetPropertyName<BaseEntity>(x => x.LastModifiedBy);
foreach (var t in modelBuilder.Model.GetEntityTypes())
{
if (t.ClrType.BaseType == typeof(BaseEntity))
{
modelBuilder.Entity(t.ClrType).Property(createdAtUtc).HasDefaultValueSql("GETUTCDATE()");
modelBuilder.Entity(t.ClrType).Property(lastModifiedAtUtc).HasDefaultValueSql("GETUTCDATE()");
modelBuilder.Entity(t.ClrType).Property(createdBy).HasDefaultValueSql("SystemGenerated");
modelBuilder.Entity(t.ClrType).Property(lastModifiedBy).HasDefaultValueSql("SystemGenerated");
}
}
Here is the the static helper for getting property names for a given type..
public static string GetPropertyName<T>(Expression<Func<T, object>> expression)
{
if (expression.Body is MemberExpression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
else
{
var op = ((UnaryExpression)expression.Body).Operand;
return ((MemberExpression)op).Member.Name;
}
}

Categories

Resources