Common configurations for entities implementing an interface

c# entity-framework-core

Question

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?

1
0
8/9/2018 9:24:19 AM

Accepted Answer

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));
    });
}
3
8/10/2018 11:22:22 AM

Popular Answer

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);
    }
}


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow