Because .NET Standard is implemented by later versions of the .NET Framework and .NET Core, these two very different project types can actually play nicely together. (I think of .NET Standard like an interface implemented by .NET Framework and .NET Core.) For example, a .NET Framework 4.7.2 project can use .NET Core 3.1 features, or reference a .NET Core 3.1 libraries.

In this post I’m going to outline the process of getting an ASP.NET WebForms project (on .NET Framework 4.7.2) to use a class library model built in Entity Framework Core 3.1.

Version Compatibility

It is important to become familiar with which versions of the .NET Framework and .NET Core implement which versions of .NET Standard. Here is a chart showing the minimum implementation versions that support each .NET Standard version. There is also a more user-friendly version of the chart, where you can select the different versions of .NET Standard and clearly see which .Net implementation versions support it:

Create the .NET Core Class Library Using .NET Standard

Although the class library project will use .NET Core 3.1 features, it must be created as a .NET Standard project in order to be usable by the .NET Framework project. Create a new project and select the Class Library (.NET Standard) template. Visual Studio 2019 automatically set my new project’s target framework to “.NET Standard 2.0” because that is the latest version of .NET Standard that is implemented by both .NET Framework 4.7.2 and .NET Core 3.1. (.NET Standard 2.0 is apparently the last version that will be implemented by the .NET Framework until things get merged in .NET 5 or whatever they’re doing next.)

Because I’m using Entity Framework Core with SQL Server in the model project, I had to add the following packages to the project:

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools

From there, I created the EF code-first model as usual. (Tons of examples out there.)

Add Package to .NET Framework Project

I already had a working .NET Framework project (ASP.Net Webforms). In order for it to be able to use the EF Core 3.1 model, I had to make a couple of changes:

  • Add a reference to the new .NET Standard project created above
  • Add the package Microsoft.EntityFrameworkCore (yes, this actually works)

Note: Without being able to use Dependency Injection in a .Net Framework Webforms project, I’ve been using a fairly reasonable solution that creates one instance of the model context for every context thread, and makes it available to the rest of the thread through HttpContext. It’s not perfect, but each thread shares a single instance of the context, which is inline with what seems to be the current best practice. I may add another post about that one of these days. Until then, create the model context however you normally do.

Working With Entity Framework Migrations

In order to work with migrations in the model, there is some prep work that must first be done.

Create a Helper Project

Normally you could manage migrations either directly in the model project or by setting the web/console project as a startup project in the migration command. But .NET Standard lacks the necessary runtime to do migrations. Unless you have another true .NET Core project in this solution, Microsoft’s recommendation is to create an empty .Net Core console application using the Console App (.NET Core) template. This allows you to set the empty console project as the startup project in the migration commands, giving the .NET command line tools the runtime it needs to manage migrations.

  1. Create a new project using the Console App (.NET Core) template
  2. Add a reference to the model project
  3. Add the following packages to the project:
    • Microsoft.EntityFrameworkCore.SqlServer
    • Microsoft.EntityFrameworkCore.Tools
    • Microsoft.Extensions.Configuration.Json (optional, see below)

There are many ways to allow the .NET command line tools to determine which db context (and which connection string) to use. Normally in a full .Net Core 3 solution I would point the –startup-project parameter to the web project, where a proper setup using Dependency Injection would let the command retrieve the database details, but that’s not possible in this example.

Another way is to override the OnConfiguring() method and injecting a default connection string in there.

And the way I went about it was by implementing an IDesignTimeDbContextFactory in the new console project, which reads a connection string from config.json and creates a context that the .NET command line tools can use when working with migrations.

Create a factory class named after your model context class. My helper project is called ModelMigrationHelper, and my model context class is called ModelContext, so my factory file (ModelContextFactory.cs) looks like this:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using System.IO;
using MyModelProject;

namespace ModelMigrationHelper {
    public class ModelContextFactory : IDesignTimeDbContextFactory<ModelContext> {
        public ModelContext CreateDbContext(string[] args) {
            IConfigurationRoot configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json") // This is where your connection string will go
                .Build();
            var optionsBuilder = new DbContextOptionsBuilder<ModelContext>();
            var connString = configuration.GetConnectionString("DefaultConnection");
            optionsBuilder.UseSqlServer(connString);
            return new ModelContext(optionsBuilder.Options);
        }
    }
}

This also requires the addition of a json config file called appsettings.json where the connection string is stored:

{
    "ConnectionStrings": {
        "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyTestDB;Trusted_Connection=True;MultipleActiveResultSets=true"
    }
}

Note: The migrations will use the connection string found in the helper project’s appconfig.json file, not the connection string used by your web project. Be conscious of this when making changes so that your migrations are applied to the correct database. (There’s probably a reasonable way to force the web project and helper project to use the same connection strings, but I haven’t needed to do that yet.)

Migration Example

With the helper project properly configured, migrations should now work. My other post about migrations should cover the basics. Here’s an example of a command to create a new migration based on the current state of the model (MyModelProject), using the helper project (ModelMigrationHelper) as the –startup-project:

dotnet ef --project .\MyModelProject\MyModelProject.csproj --startup-project .\ModelMigrationHelper\ModelMigrationHelper.csproj migrations add <SomeName>

Use The Model

Use the EF Core 3.1 model in the .NET Framework project like you would use any other model. Add a reference to the project, create a context, and go.

Possible Missing Reference to NetStandard in Web.config

I received the following error message when my ASP.Net project referenced an ENUM in the model:

BC30652: Reference required to assembly ‘netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51’ containing the type ‘[Object]’. Add one to your project.

To fix this, I had to add a reference to the netstandard library in my Web.config file under <system.web><compilation><assemblies>:

<system.web>
    <compilation debug="true" strict="false" explicit="true" targetFramework="4.7.2" >
        <assemblies>
            <add assembly="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51"/> <!-- Added this line -->
        </assemblies>
    </compilation>
</system.web>