Conventional Programming with EFCore – Part 2 – Fixing Find

Photo by Jeffrey Eisen

If you didn’t already setup Entity Scaffolding, please checkout Part 1 before continuing.

DbSet.Find(Object[]) Although a powerful method, it can cause some problems. Primarily, the lack of compile time checking of the arguments. Usually, there is a bit of double checking during initial use, and then god forbids someone changes the keys. You can have some hidden bugs on your hands.

Worst yet if you have two int primary keys, it wasn’t always straight forward the order of the keys. Sometimes it is even possible for EF to shift the keys and not inform you.

I always despised the Find method, it is the rare Object[] in a modern framework. It always seemed that there was a better way. As crazy as it sounds, it kept me up at night.

Then one night a few years ago I did figure it out…

IIdentity<T1,…,Tx>

The first thing is the definition of a few generic Identity interfaces. (Already done in EntityScaffolding.DefaultConventions)

namespace EntityScaffolding.DefaultConventions
{
    public interface IIdentity<T> { }
    public interface IIdentity<T1, T2> { }
    public interface IIdentity<T1, T2, T3> { }
    public interface IIdentity<T1, T2, T3, T4> { }
    public interface IIdentity<T1, T2, T3, T4, T5> { }
}

Note: I have in the past had the Identity interfaces define a method returning a tuple composed of the keys, but haven’t found much use for such a method.

Then during scaffolding, we can apply these interfaces to our entities automatically.

public partial class EmployeeDepartmentHistory: IIdentity<int, DateTime, short, byte> { /***/ }

public partial class EmployeePayHistory: IIdentity<int, DateTime> { /***/ }

public partial class Address: IIdentity<int> { /***/ }

FindEntity Extension Method

Depending on your data layer you can add FindEntity Extension Methods to either DbSet or your IRepository. EntityScaffolding.DefaultConventions define these extensions methods off of DbSet for you.

public static class DbSetExtensions
{
    public static TEntity FindEntity<TEntity, T1>(this DbSet<TEntity> set, T1 key1)
        where TEntity : class, IIdentity<T1>
    {
        return set.Find(key1);
    }

    public static TEntity FindEntity<TEntity, T1, T2>(this DbSet<TEntity> set, T1 key1, T2 key2)
        where TEntity : class, IIdentity<T1, T2>
    {
        return set.Find(key1, key2);
    }

    //And so forth
}

Using the FindEntity Method will cause compile time exceptions to occur when the improper types are used. This should help avoid some bugs.

Unfortunately at the time of writing this, there is an intelli-sense bug in the .NET Core compiler for these methods. It is just creating a less than ideal usage of the methods. Ideally the constants should limit the intelli-sense options, but currently just a compile error is shown. Hopefully, this is resolved shortly.

PrimaryKeyAttribute

Another aspect is the PrimaryKeyAttribute. With the Default Conventions in Entity Scaffolding, the PrimaryKeyAttribute will be added to the properties along with their index.

public partial class BusinessEntityAddress : IIdentity<int, int, int>
{
        
    [PrimaryKey(1)]
    public int BusinessEntityId { get; set; }
        
    [PrimaryKey(2)]
    public int AddressId { get; set; }
        
    [PrimaryKey(0)]
    public int AddressTypeId { get; set; }

    /***/
}

First, this helps developers know which value corresponds with which argument index.

Second during writing this post, I reevaluated the shifting index problem. Although not released just yet, I’m adding support to inform the developer that indexes have shifted during scaffolding.

This should help the developers know when indexes were shifted. My initial pass at these warnings, only display the messages if a compile error won’t be shown. IE: The types are the same. I need to update it to cover implicit casts, such as Int32 and Int16 as so forth.

The way I am accomplishing these warnings is by parsing the existing assembly during scaffolding, and looking for the PrimaryKey attributes. From that information I can determine if a change has occurred.

Conclusion

Still a little bit of development to do, and a little bit of waiting for Roslyn to fix a bug.

But it works as is today.

One thought on “Conventional Programming with EFCore – Part 2 – Fixing Find

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.