Change Delete Behavior and more on EF Core

Entity Framework had objects called conventions. These objects can be used to make configurations on the entity framework context to change its default behavior.

Two examples of behaviors we usually configure are the pluralize behavior for object names and delete cascade for foreign keys.

Entity Framework Core, on the other hand, doesn’t have conventions. How could we achieve the same configuration on entity framework core?

Configuring Conventions in Entity Framework Core

Instead of depending on conventions we need to make the configurations in a “manual” way. It means setting the configuration on each object of the schema as needed.

We may need a bit more code and also need to take care to execute this code after the definition of the schema. In this way the behaviors we are configuring will apply to all the objects in the schema.

This is an example of the OnModelCreating applying the code to avoid the pluralize behavior and remove the delete cascade behavior from all foreign keys:

 

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            if (modelBuilder == null)
            throw new ArgumentNullException("modelBuilder");

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

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

            base.OnModelCreating(modelBuilder);
        }

 

Creating Extension Methods

We can do a bit better, turning this code more reusable using extension methods. Our first attempt to use extension methods can be like this:

 

public static class ContextExtensions
{
    public static void RemovePluralizeBehavior(this ModelBuilder builder)
    {
        builder.EntityLoop(et => et.SetTableName(et.DisplayName()));
    }

    public static void RemoveOneToManyCascade(this ModelBuilder builder)
    {
        builder.EntityLoop(et => et.GetForeignKeys()
            .Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade)
            .ToList()
            .ForEach(fk => fk.DeleteBehavior = DeleteBehavior.Restrict));
    }


    private static void EntityLoop(this ModelBuilder builder, Action<IMutableEntityType> action)
    {
        foreach (var entityType in builder.Model.GetEntityTypes())
        {
            action(entityType);
        }
    }
}

 

Mind the interesting way I ensured the loop would be built only once and re-used, parameterizing the loop with Actions/Lambda expressions.

Once we built the extension methods, the OnModelCreating will become like this:

 

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            if (modelBuilder == null)
                throw new ArgumentNullException("modelBuilder");

            modelBuilder.AddRemovePluralizeConvention();
            modelBuilder.AddRemoveOneToManyCascadeConvention();

            modelBuilder.ApplyConventions();

            base.OnModelCreating(modelBuilder);
        }

Optimizing the Extension

This code could still be improved, because in the way it is the same loop is being repeated twice. We can make some changes to our extension to avoid this. The new code will be like this:

 

public static class ContextExtensions
{

    private static List<Action<IMutableEntityType>> Conventions=new List<Action<IMutableEntityType>>();

    public static void AddRemovePluralizeConvention(this ModelBuilder builder)
    {
         Conventions.Add(et => et.SetTableName(et.DisplayName()));
    }

    public static void AddRemoveOneToManyCascadeConvention(this ModelBuilder builder)
    {
        Conventions.Add(et => et.GetForeignKeys()
            .Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade)
            .ToList()
            .ForEach(fk => fk.DeleteBehavior = DeleteBehavior.Restrict));
    }

    public static void ApplyConventions(this ModelBuilder builder)
    {
        foreach (var entityType in builder.Model.GetEntityTypes())
        {
            foreach(Action<IMutableEntityType> action in Conventions)
                action(entityType);
        }

        Conventions.Clear();
    }
}

The new OnModelCreating will be like this:

 

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            if (modelBuilder == null)
                throw new ArgumentNullException("modelBuilder");

            modelBuilder.AddRemovePluralizeConvention();
            modelBuilder.AddRemoveOneToManyCascadeConvention();

            modelBuilder.ApplyConventions();

            base.OnModelCreating(modelBuilder);
        }

Conclusion

.NET core avoid some pre-built classes such as the conventions, but we still can create our own libraries to reuse and simplify the code in our own way.

I made these extension methods available on GitHub, you can use, fork, improve and include in your artifacts