Change Delete Behavior and more on EF Core

Comments 0

Share to social media

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

Load comments

About the author

Dennes Torres

See Profile

Dennes Torres is a Data Platform MVP and Software Architect living in Malta who loves SQL Server and software development and has more than 20 years of experience. Dennes can improve Data Platform Architectures and transform data in knowledge. He moved to Malta after more than 10 years leading devSQL PASS Chapter in Rio de Janeiro and now is a member of the leadership team of MMDPUG PASS Chapter in Malta organizing meetings, events, and webcasts about SQL Server. He is an MCT, MCSE in Data Platforms and BI, with more titles in software development. You can get in touch on his blog https://dennestorres.com or at his work https://dtowersoftware.com