Let's say I have some interface like:
public interface ISoftDeletable
{
bool IsActive { get; set }
}
And I have many entities that implement it:
public class Entity1 : ISoftDeletable
{
public int Id { get; set }
public bool IsActive { get; set; }
}
public class Entity2 : ISoftDeletable
{
public int Id { get; set }
public bool IsActive { get; set; }
}
In OnModelCreating
:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity1>().Property(e => e.IsActive).HasDefaultValue(true);
modelBuilder.Entity<Entity2>().Property(e => e.IsActive).HasDefaultValue(true);
}
Is there any way to refactor this so I can set HasDefaultValue
for all entities implementing ISoftDeletable
instead of doing this like above?
I can probably solve this specific case using a default constructor for each entity with IsActive = true
or even create a base abstract class but I don't like it very much.
Similar question: Ef core fluent api set all column types of interface
Is there any better way?
I found some answer here: GetEntityTypes: configure entity properties using the generic version of .Property<TEntity> in EF Core
Apart from comments above, there's a way to do it without calling it for each entity. This probably could be refactored to some extension method as mentioned by Erndob's comment under my question.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (typeof(ISoftDeletable).IsAssignableFrom(entityType.ClrType))
{
modelBuilder.Entity(entityType.ClrType).Property<bool>(nameof(ISoftDeletable.IsActive)).HasDefaultValue(true);
}
}
}
The solution is to use ModelBuilder.Model.GetEntityTypes()
and find entity types that are assignable from ISoftDeletable
.
In my opinion, this is much better than configuring it manually or even creating an abstract IEntityTypeConfiguration<>
class because you don't have to remember to use it for all ISoftDeletable
classes.
More clean looking:
public static class ModelBuilderExtensions
{
public static ModelBuilder EntitiesOfType<T>(this ModelBuilder modelBuilder,
Action<EntityTypeBuilder> buildAction) where T : class
{
return modelBuilder.EntitiesOfType(typeof(T), buildAction);
}
public static ModelBuilder EntitiesOfType(this ModelBuilder modelBuilder, Type type,
Action<EntityTypeBuilder> buildAction)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
if (type.IsAssignableFrom(entityType.ClrType))
buildAction(modelBuilder.Entity(entityType.ClrType));
return modelBuilder;
}
}
And OnModelCreating
:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.EntitiesOfType<ISoftDeletable>(builder =>
{
builder.Property<bool>(nameof(ISoftDeletable.IsActive)).HasDefaultValue(true);
// query filters :)
var param = Expression.Parameter(builder.Metadata.ClrType, "p");
var body = Expression.Equal(Expression.Property(param, nameof(ISoftDeletable.IsActive)), Expression.Constant(true));
builder.HasQueryFilter(Expression.Lambda(body, param));
});
}
I wanted to do something similar to this but use the IEntityTypeConfiguration
interface to hold my generic configurations. I ended up having to use reflection but it works:
Interface
:
public interface IHasDisplayId
{
Guid DisplayId { get; }
}
EntityTypeConfig
:
public class HasDisplayIdEntityTypeConfiguration<T> : IEntityTypeConfiguration<T> where T : class, IHasDisplayId
{
public void Configure(EntityTypeBuilder<T> builder)
{
builder.Property(e => e.DisplayId).IsRequired();
builder.HasIndex(e => e.DisplayId);
}
}
Extension method
:
public static ModelBuilder ApplyConfiguration<T>(this ModelBuilder modelBuilder, Type configurationType, Type entityType)
{
if (typeof(T).IsAssignableFrom(entityType))
{
// Build IEntityTypeConfiguration type with generic type parameter
var configurationGenericType = configurationType.MakeGenericType(entityType);
// Create an instance of the IEntityTypeConfiguration implementation
var configuration = Activator.CreateInstance(configurationGenericType);
// Get the ApplyConfiguration method of ModelBuilder via reflection
var applyEntityConfigurationMethod = typeof(ModelBuilder)
.GetMethods()
.Single(e => e.Name == nameof(ModelBuilder.ApplyConfiguration)
&& e.ContainsGenericParameters
&& e.GetParameters().SingleOrDefault()?.ParameterType.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>));
// Create a generic ApplyConfiguration method with our entity type
var target = applyEntityConfigurationMethod.MakeGenericMethod(entityType);
// Invoke ApplyConfiguration, passing our IEntityTypeConfiguration instance
target.Invoke(modelBuilder, new[] { configuration });
}
return modelBuilder;
}
Usage
:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
modelBuilder.ApplyConfiguration<IHasDisplayId>(typeof(HasDisplayIdEntityTypeConfiguration<>), entityType.ClrType);
}
}