Macko
Macko

Reputation: 966

Where are Entity Framework Core conventions?

using EF 6.1+ there were times where we need to add or remove existing conentions. The code looks more or less like:

public class MyContext : DbContext
    {
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Conventions.AddFromAssembly(Assembly.GetExecutingAssembly());
                modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
                modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
                modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();

                base.OnModelCreating(modelBuilder);
            }
}

how do the same in EF core? Modelbuilder has no Conventions property :(

Upvotes: 27

Views: 17433

Answers (3)

Sebastian Widz
Sebastian Widz

Reputation: 2072

In order to set singular table names in EF Core 5 you can do the following

modelBuilder.Model.GetEntityTypes()            
    .Configure(et => et.SetTableName(et.DisplayName()));

but then if you have value objects in you domain they will all be treated as entity types and created as tables in the database. If your value objects all inherit from a base type like ValueObject you can use the following:

modelBuilder.Model.GetEntityTypes()            
    .Where(x => !x.ClrType.IsSubclassOf(typeof(ValueObject)))
    .Configure(et => et.SetTableName(et.DisplayName()));

Another drawback in EF Core 5 is that when you use inheritance in your entity model, setting tables names changes from Table-per-Hierarchy (TPH) to Table-per-Type (TPT)

You could use the following alternative (assuming your entities derive from BaseEntity)

modelBuilder.Model.GetEntityTypes()
    .Where(x => x.ClrType.BaseType == typeof(BaseEntity))
    .Configure(et => et.SetTableName(et.DisplayName()));

where Configure is defined as an extension method:

public static class ModelBuilderExtensions
{
    public static IEnumerable<IMutableEntityType> EntityTypes(this ModelBuilder builder)
    {
        return builder.Model.GetEntityTypes();
    }
    public static IEnumerable<IMutableProperty> Properties(this ModelBuilder builder)
    {
        return builder.EntityTypes().SelectMany(entityType => entityType.GetProperties());
    }

    public static IEnumerable<IMutableProperty> Properties<T>(this ModelBuilder builder)
    {
        return builder.EntityTypes().SelectMany(entityType => entityType.GetProperties().Where(x => x.ClrType == typeof(T)));
    }

    public static void Configure(this IEnumerable<IMutableEntityType> entityTypes, Action<IMutableEntityType> convention)
    {
        foreach (var entityType in entityTypes)
        {
            convention(entityType);
        }
    }

    public static void Configure(this IEnumerable<IMutableProperty> propertyTypes, Action<IMutableProperty> convention)
    {
        foreach (var propertyType in propertyTypes)
        {
            convention(propertyType);
        }
    }
}

Upvotes: 1

Paul Hatcher
Paul Hatcher

Reputation: 8156

I'm porting some code from EF to EF Core 2.1+ and can't wait for EF Core 3.0 so wrote a few extension methods which help a bit..

public static IEnumerable<IMutableEntityType> EntityTypes(this ModelBuilder builder)
{
    return builder.Model.GetEntityTypes();
}

public static IEnumerable<IMutableProperty> Properties(this ModelBuilder builder)
{
    return builder.EntityTypes().SelectMany(entityType => entityType.GetProperties());
}

public static IEnumerable<IMutableProperty> Properties<T>(this ModelBuilder builder)
{
    return builder.EntityTypes().SelectMany(entityType => entityType.GetProperties().Where(x => x.ClrType == typeof(T)));
}

public static void Configure(this IEnumerable<IMutableEntityType> entityTypes, Action<IMutableEntityType> convention)
{
    foreach (var entityType in entityTypes)
    {
        convention(entityType);
    }
}

public static void Configure(this IEnumerable<IMutableProperty> propertyTypes, Action<IMutableProperty> convention)
{
    foreach (var propertyType in propertyTypes)
    {
        convention(propertyType);
    }
}

with these you can write conventions similar to those in EF 6.1.x, for example.

// equivalent of modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.EntityTypes()
            .Configure(et => et.Relational().TableName = et.DisplayName());

// Put the table name on the primary key
modelBuilder.Properties()
            .Where(x => x.Name == "Id")
            .Configure(p => p.Relational().ColumnName = p.DeclaringEntityType.Name + "Id");

// Mark timestamp columns as concurrency tokens
modelBuilder.Properties()
            .Where(x => x.Name == "Timestamp")
            .Configure(p => p.IsConcurrencyToken = true);

For EF Core 3.0 the metamodel methods have changed slightly so you need

modelBuilder.EntityTypes()
            .Configure(et => et.SetTableName(et.DisplayName()));

modelBuilder.Properties()
            .Where(x => x.Name == "Id")
            .Configure(p => p.SetColumnName(BaseName(p.DeclaringEntityType.Name) + "Id"));

Haven't checked this for efficiency but unless your model is huge it shouldn't pose a problem

This can be extended with other helpers for foreign keys, indexes etc

Upvotes: 19

Balah
Balah

Reputation: 2540

Looks like it's still not in EF Core 2.0. So here's one way of achieving it. I had done this to apply consistent behaviour to certain attributes, but to address the examples in your question you could try this:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // equivalent of modelBuilder.Conventions.AddFromAssembly(Assembly.GetExecutingAssembly());
    // look at this answer: https://stackoverflow.com/a/43075152/3419825

    // for the other conventions, we do a metadata model loop
    foreach (var entityType in modelBuilder.Model.GetEntityTypes())
    {
        // equivalent of modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        entityType.Relational().TableName = entityType.DisplayName();

        // equivalent of modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
        // and modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
        entityType.GetForeignKeys()
            .Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade)
            .ToList()
            .ForEach(fk => fk.DeleteBehavior = DeleteBehavior.Restrict);
    }

    base.OnModelCreating(modelBuilder);
}

You should be able to do a lot of things with entityType

Upvotes: 13

Related Questions