ibubi
ibubi

Reputation: 2549

how to apply common configuration to all entities in ef core

I have entities derived from a base entity in my application which uses ef core code-first approach.

Base class

public abstract class BaseEntity<T> : IEntity<T>
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public T Id { get; set; }

    object IEntity.Id { get { return Id; } set { } }

    private DateTime? createdOn;
    [DataType(DataType.DateTime)]
    public DateTime CreatedOn { get => createdOn ?? DateTime.Now; set => createdOn = value; }

    [DataType(DataType.DateTime)]
    public DateTime? ModifiedOn { get; set; }

    public bool IsDeleted { get; set; }
    // Auto increment for all entities.
    public int OrderId { get; set; }
}

And an entity

public class UserEntity : BaseEntity<int>
{
    public string EmployeeId { get; set; }
    public string FullName { get; set; }
    public string Email { get; set; }
}

I can apply the .ValueGeneratedOnAdd() method on property OrderId in OnModelCreating for each entity however, is there a way to apply a general rule for all entities without repeatig yourself?

Upvotes: 17

Views: 10614

Answers (4)

Ogglas
Ogglas

Reputation: 70184

If you want every property named OrderId to be ValueGeneratedOnAddYou you could solve it like this without repeating yourself:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    foreach (var et in modelBuilder.Model.GetEntityTypes())
    {
        foreach (var prop in et.GetProperties())
        {
            if (prop.Name == "OrderId")
            {
                prop.ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.OnAdd;
            }
        }
    }
}

Source:

https://stackoverflow.com/a/62380293/3850405

This can also be used to set maximum string length for all strings as an example:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    
    foreach (var property in modelBuilder.Model.GetEntityTypes()
        .SelectMany(t => t.GetProperties())
        .Where(p => p.ClrType == typeof(string)))
    {
        if (property.GetMaxLength() == null)
            property.SetMaxLength(450);
    }
}

Source:

https://stackoverflow.com/a/50852517/3850405

You can also set mark every entity that inherits from IEntity as IsTemporal:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    foreach (var et in modelBuilder.Model.GetEntityTypes())
    {
        if (et.ClrType.IsAssignableTo(typeof(IEntity)))
        {
            et.SetIsTemporal(true);
        }
    }
}

Source:

https://stackoverflow.com/a/75496573/3850405

Upvotes: 2

wwwwww3q
wwwwww3q

Reputation: 21

foreach (var item in modelBuilder.Model.GetEntityTypes())
{
    item.FindProperty("CreatedOn")?.SetDefaultValueSql("GETDATE()");
    item.FindProperty("ModifiedOn")?.SetDefaultValueSql("GETDATE()");
    item.FindProperty("AAStatus")?.SetDefaultValue("Alive");
}

i researched and found this thread, my solution after reading it is

Upvotes: 0

Aldert
Aldert

Reputation: 4323

In EF6 you can use:

modelBuilder.Properties<int>().Where(p=>p.Name == "OrderId").Configure(c => c.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity));

Upvotes: 1

Ivan Stoev
Ivan Stoev

Reputation: 205829

With the lack of custom conventions, you could use the typical modelBuilder.Model.GetEntityTypes() loop, identify the target entity types and invoke common configuration.

Identification in your case is a bit complicated because of the base generic class, but doable by iterating down Type.BaseType and check for BaseEntity<>. Once you find it, you can retrieve the generic argument T which you'll need later.

If you don't want to use generic class implementing IEnityTypeConfiguration<TEntity>, then the idea is to put the implementation in generic constrained method like this

static void Configure<TEntity, T>(ModelBuilder modelBuilder)
    where TEntity : BaseEntity<T>
{
    modelBuilder.Entity<TEntity>(builder =>
    {
        builder.Property(e => e.OrderId).ValueGeneratedOnAdd();
    });
}

Passing the actual entity type TEntity to modelBuilder.Enity method is crucial, because otherwise EF Core will consider whatever you pass to be an entity type and configure TPH inheritance.

Calling the method requires reflection - finding the generic method definition, using MakeGenericMethod and then Invoke.

Here is all that encapsulated in a static class:

public static class BaseEntityConfiguration
{
    static void Configure<TEntity, T>(ModelBuilder modelBuilder)
        where TEntity : BaseEntity<T>
    {
        modelBuilder.Entity<TEntity>(builder =>
        {
            builder.Property(e => e.OrderId).ValueGeneratedOnAdd();
        });
    }

    public static ModelBuilder ApplyBaseEntityConfiguration(this ModelBuilder modelBuilder)
    {
        var method = typeof(BaseEntityConfiguration).GetTypeInfo().DeclaredMethods
            .Single(m => m.Name == nameof(Configure));
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            if (entityType.ClrType.IsBaseEntity(out var T))
                method.MakeGenericMethod(entityType.ClrType, T).Invoke(null, new[] { modelBuilder });
        }
        return modelBuilder;
    }

    static bool IsBaseEntity(this Type type, out Type T)
    {
        for (var baseType = type.BaseType; baseType != null; baseType = baseType.BaseType)
        {
            if (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == typeof(BaseEntity<>))
            {
                T = baseType.GetGenericArguments()[0];
                return true;
            }
        }
        T = null;
        return false;
    }
}

Now all you need is to call it from inside your OnModelCreating override:

modelBuilder.ApplyBaseEntityConfiguration();

Upvotes: 32

Related Questions