In my EF core project I have a few entities that inherit from a base class DBEntity:
public abstract class DBEntity
{
public int Id { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
public EntityStatus EntityStatus { get; set; }
}
I want to set some specific properties with default value for every entity that inherits from DBEntity. Currently I'm using this code:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var entities = modelBuilder.Model
.GetEntityTypes()
.Where(w => w.ClrType.IsSubclassOf(typeof(DBEntity)))
.Select(p => modelBuilder.Entity(p.ClrType));
foreach (var entity in entities)
{
entity
.Property("CreatedOn")
.HasDefaultValueSql("GETDATE()");
entity
.Property("UpdatedOn")
.HasComputedColumnSql("GETDATE()");
entity
.Property("EntityStatus")
.HasDefaultValue(EntityStatus.Created);
}
}
This works fine but I don't want to specify property names using string constants (bad for refactoring etc.).
Is there a way to loop through entities that inherit from DBEntity and use property expression to configure default values like this:
foreach (var entity in entities)
{
entity
.Property(k => k.CreatedOn)
.HasDefaultValueSql("GETDATE()");
// ....
}
Important constraint: I cannot directly call modelBuilder.Entity().Property(k => k.CreatedOn) because it will mess up all tables.
As pointed out in the comments, you can simply replace the string constants with nameof(DBEntity.CreatedOn)
etc.
But if you want to work with typed accessors, you can move the base entity configuration code to a generic method (with the generic argument being the actual entity type) and call it via reflection.
For instance, add the following to your DbContext
derived class:
static void ConfigureDBEntity<TEntity>(ModelBuilder modelBuilder)
where TEntity : DBEntity
{
var entity = modelBuilder.Entity<TEntity>();
entity
.Property(e => e.CreatedOn)
.HasDefaultValueSql("GETDATE()");
entity
.Property(e => e.UpdatedOn)
.HasComputedColumnSql("GETDATE()");
entity
.Property(e => e.EntityStatus)
.HasDefaultValue(EntityStatus.Created);
}
and then use something like this:
var entityTypes = modelBuilder.Model
.GetEntityTypes()
.Where(t => t.ClrType.IsSubclassOf(typeof(DBEntity)));
var configureMethod = GetType().GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ConfigureDBEntity));
var args = new object[] { modelBuilder };
foreach (var entityType in entityTypes)
configureMethod.MakeGenericMethod(entityType.ClrType).Invoke(null, args);
What about, in your context, adding something like this
public override int SaveChanges()
{
AddTimestamps();
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
AddTimestamps();
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void AddTimestamps()
{
var entities = ChangeTracker.Entries()
.Where(x => (x.Entity is DBEntity) && (x.State == EntityState.Added || x.State == EntityState.Modified));
var now = DateTime.UtcNow; // current datetime
foreach (var entity in entities)
{
if (entity.State == EntityState.Added)
{
((DBEntity)entity.Entity).CreatedOn = now;
((DBEntity)entity.Entity).EntityStatus = EntityStatus.Created;
}
((DBEntity)entity.Entity).UpdatedOn= now;
}
}
You have the drawback to not enforcing those default values at db level (at this point you are db agnostic) but stil able to refactor
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With