Conventional Programming with EFCore – Part 1

Photo by Vlad Tchompalov

When I first took a pass at writing a Framework many years ago, I thought I was so clever being able to use reflection to solve common problems. Then I learned about Expression Trees and more fun but difficult to maintain concepts. At one point I sat down and said to myself, will anyone else really be able to add to this or understand this later? For the most part maybe not.

Then after stumbling around Aspect Oriented Programming with Post Sharp and Run-time Weaving with Unity (container), I landed on a simpler solution. Conventional Programming.

Entity Framework use to come with *.edmx that used *.tt (t4) files to generate code. Some edits in the t4 model generator could enable custom interfaces to be applied to entities, and with a lookup those interfaces could be found dynamically. This meant that by simply defining an interface to used as a convention, during database first generation those interfaces could be applied to your entities automatically.

Your data access layer then could work with interfaces to apply common rules to based off of those conventions. It worked but wasn’t great. Mainly because editing a *.tt file was pretty painful and there wasn’t many tools out there to help. Also *.edmx were a nightmare.

Then EFCore came around and removed *.edmx all together and no longer used *.tt files for generation, so I had to start over with this.

Warning

I want to be clear. How I got everything here to work is using parts of EFCore that is not apart of the same API backwards compatibility standards. This means at any time, EFCore could remove the API I used to achieve this.

What it does not mean, is that your code will stop working. Upgrading to the next version might cause an headache.

I’m using public classes in namespaces *.Internals which will provide an EF1001 Compiler Warning. This warning should also be shown to any user attempting to use my libraries.

Entity Scaffolding

I’m introducing a new Open Source library called Entity Scaffolding, because I’m not as creative with naming as others.

GitHub – Source

I’m going to cover a few aspects of how this works in my blog to highlight some of the power of said solution. It is an extremely simple library. Really doesn’t have much code in it, but it is pretty powerful.

Opening the box, using Adventure Works

What’s a .NET POC without Adventure Works? So, I recommend first let’s setup Adventure Works and use the Default Conventions provided by Entity Scaffolding to see what it can do.

Quick Teaser

As a quick teaser before we get started, this is the EF Generated Code for the Address POCO:

using System;
using System.Collections.Generic;
using EntityScaffolding.DefaultConventions;

namespace TestingConventions.Models
{
    public partial class Address: IRowguid, IUpdateTracker, IIdentity<int>
    {
        public Address()
        {
            BusinessEntityAddress = new HashSet<BusinessEntityAddress>();
            SalesOrderHeaderBillToAddress = new HashSet<SalesOrderHeader>();
            SalesOrderHeaderShipToAddress = new HashSet<SalesOrderHeader>();
        }

        
        [PrimaryKey]
        public int AddressId { get; set; }
        public string AddressLine1 { get; set; }
        public string AddressLine2 { get; set; }
        public string City { get; set; }
        public int StateProvinceId { get; set; }
        public string PostalCode { get; set; }
        public Guid Rowguid { get; set; }
        public DateTime ModifiedDate { get; set; }

        public virtual StateProvince StateProvince { get; set; }
        public virtual ICollection<BusinessEntityAddress> BusinessEntityAddress { get; set; }
        public virtual ICollection<SalesOrderHeader> SalesOrderHeaderBillToAddress { get; set; }
        public virtual ICollection<SalesOrderHeader> SalesOrderHeaderShipToAddress { get; set; }
    }
}

Note: Additional interfaces were added, using statements updated, and the Primary Key property tagged with an Attribute.

Adventure Works

If you don’t already have an Adventure Works database installed you can download it from Microsoft: Adventure Works 2017

Project Setup

Create a new Console APP or Class Library using .NET Core 3.0.

In your Package Manager Console run the following commands:

Install-Package Microsoft.EntityFrameworkCore -Version 3.0.1
Install-Package Microsoft.EntityFrameworkCore.Design -Version 3.0.1
Install-Package Microsoft.EntityFrameworkCore.Tools -Version 3.0.1

Install-Package EntityScaffolding -Version 3.0.1
Install-Package EntityScaffolding.DefaultConventions -Version 3.0.1

Assuming you are using Sql Server you can run this command as well. If you are using a different database type you will have to install the respective package.

Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 3.0.1

Configuring Design Time Services

Create a new class in the project called DesignTimeServices with the following code.

using EntityScaffolding;
using EntityScaffolding.DefaultConventions;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Scaffolding.Internal;
using Microsoft.Extensions.DependencyInjection;

namespace <YourNameSpace>
{
    public class DesignTimeServices : IDesignTimeServices
    {
        public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
        {
            serviceCollection.AddSingleton(new UseDefaultConventions());
            serviceCollection.AddSingleton<ICSharpEntityTypeGenerator, ConventionEntityTypeGenerator>();
        }
    }
}

If the Internal namespace or ICSharpEntityTypeGenerator interface is not resolved, you will have to manually add the Microsoft.EntityFrameworkCore.Design.dll to your project.

Right Click “Dependencies” under you project and select “Add Reference”.

Browse to C:\Users\<YourUsername>\.nuget\packages\microsoft.entityframeworkcore.design\3.0.1\lib\netstandard2.1

And select Microsoft.EntityFrameworkCore.Design.dll

Generating Models

Run the following command in the Package Manager Console:

Scaffold-DbContext "Server=.;Database=AdventureWorks;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models

You of course may have to edit the connection string to your specific database.

Done

Now if you browse your models you will see that interfaces and attributes have been added to the models they apply to.

These conventions are defined in EntityScaffolding.DefaultConventions but are not required. By removing that package and the following line from DesignTimeServices, only conventions you defined will be used.

serviceCollection.AddSingleton(new UseDefaultConventions());

Creating your own Custom Convention

Create a new interface as follows:

using EntityScaffolding;

namespace <YourNameSpace>
{
    [EntityConvention]
    public interface IPerson
    {
        string FirstName { get; set; }
        string MiddleName { get; set; }
        string LastName { get; set; }
    }
}

Re-run the Scaffold-DbContext command but with ‘-force’.

If you open the Person Entity, you will now see the IPerson interface was automatically added the the Entity.

Conclusion

With the Entity Scaffolding Framework, you can quickly and easily apply custom conventions to your entities during scaffolding. In future posts I’ll cover some additional aspects of the framework, specifically how to additional code, and cover scenarios how you can use entities with conventions.

Although I only have a version out for 3.0.1, I have tested with 3.1.0 and it still works exactly the same. I plan on releasing nuget packages that align with the EFCore versions to avoid confusion.

Check out Part 2 that covers an updated Find method that is Type Specific: Part 2 Fixing Find

One thought on “Conventional Programming with EFCore – Part 1

Leave a comment

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