GetEntityTypes: configure entity properties using the generic version of .Property<TEntity> in EF Core

落花浮王杯 提交于 2019-11-28 01:57:48

问题


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.


回答1:


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



回答2:


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



来源:https://stackoverflow.com/questions/49676508/getentitytypes-configure-entity-properties-using-the-generic-version-of-proper

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!