August 2019

Volume 34 Number 8

[Data Points]

Cross-Platform EF6 with .NET Core 3.0!

By Julie Lerman

Julie LermanThough Entity Framework Core has been available for a few years now, there are still myriad production apps out there using EF6. And even with all of the innovation going into EF Core, there’s no reason to port working, stable production code to EF Core if you’re not planning to change the EF6 logic and have no need to take advantage of the new capabilities or improved performance of EF Core. Thanks to its open source nature, EF6 is still being maintained and even tweaked by Microsoft and the community. Yet, many developers and teams who don’t want to mess with their EF6 functionality would like to port their software from .NET to .NET Core to take advantage of the many .NET Core benefits, including its cross-platform capabilities and many innovations.

It has been possible to encapsulate EF6 logic into an ASP.NET Web API and access that from .NET Core applications. But that solution won’t allow you to benefit from the features of ASP.NET Core APIs or the innovations to Windows desktop apps as they move to .NET Core 3.0. Luckily, with the upcoming releases of EF 6.3 and .NET Core 3.0, you’ll be able to “have your cake and eat it, too.” Not only will EF6.3 continue to run on .NET Framework, it will also work with .NET Core 3.0. This is possible because in addition to running on .NET 4.0 and .NET 4.5, EF6.3 is cross-­compiled to target .NET Standard 2.1.

In this article, using the latest preview version of EF6.3 and ASP.NET Core 3.0, I’ll go all in on trying out EF6.3 in a cross-platform scenario—creating a new ASP.NET Core 3.0 API using EF6.3. On my MacBook! In macOS! Using Visual Studio Code! And then deploying it to a Linux-based Docker container! Not one of these tasks was possible with EF6 until now. You can still use EF6.3 in .NET Framework apps in Visual Studio starting with version 2017 v15.3, although for .NET Core 3.0 apps you’ll need preview versions of Visual Studio 2019 that support .NET Core 3.0.

The EF6.3 preview does have a few limitations with .NET Core 3.0 that are expected to be sorted out by the time it’s released. Current limitations include:

  • .NET Core 3.0 can’t be used with the EF Designer in Visual Studio yet.
  • Migration commands don’t yet work with .NET Core 3.0 projects.
  • The only working provider at the time of writing is SQL Server.

You may find the discussion tied to the announcement blog post useful (bit.ly/2F9xtDt), but keep in mind that as the release gets closer, many of these issues should be resolved.

Even with these limitations, my real interest is a proof of concept—witnessing EF6.3 on a non-Windows machine. So, I’ll build a very simple project with the most simplistic model—enough to see it working on my MacBook. And because I’m working on macOS and the only database provider currently available is SQL Server, I get a great excuse to use SQL Server for Linux in a Docker container to persist the data.

Preparing the Base API Project

Installing .NET Core 3.0 and creating an ASP.NET Core API remains as it’s always been. I’ll relay these steps briefly for those of you who haven’t done it yet.

Start by ensuring you have .NET Core 3.0 installed on your machine. As I’m writing this in early June 2019, the latest version is Preview 6, released on June 12, 2019. The installation page for .NET Core 3.0 is bit.ly/2KoSOxh. You want to install the SDK package, which includes not only the SDK but also the .NET Core and ASP.NET Core runtimes. Once it’s installed, the command dotnet  --version will list the latest version, while dotnet --list-sdks will show all of the versions on your system.

Next, create a new folder for your project (I called mine “coreapi”) and be sure you’re in that folder at the command line. Then type the CLI command: dotnet new webapi. This will create a new ASP.NET Core API project in the folder using C# as the default language and, by default, the latest version of .NET Core installed on your machine.

As I’m working on my MacBook, there’s a way I can immediately experience the cross-platform support—by using Visual Studio Code for my development environment. I have a shortcut that allows me to type code . at the command line in my project folder to start VS Code with that folder opened. You can also just start VS Code and open the project folder.

The essential assets of the project were created by the template, including a default ValuesController. As you can see in Figure 1, the project file targets .NET Core 3.0. There’s no longer a need to explicitly reference any of the ASP.NET Core packages in csproj with this version of .NET Core, which is why ItemGroup is empty.

The coreapi Project and Contents of Its csproj File
Figure 1 The coreapi Project and Contents of Its csproj File

Adding EF6.3 to the Project

You can add EF6.3 directly to the csproj file by including the following package reference within the ItemGroup section:

<PackageReference Include="EntityFramework" Version="6.3.0-preview6-19304-03" />

Note that this references the latest preview version available as I’m working on this article. Check the NuGet page for Entity Framework (bit.ly/2RgTo0l) to determine the current version. By the time you read this article, EF6.3 may have already been fully released!

I’ll need some data to store, so I created a tiny class called Human:

public class Human {
  public int HumanId { get; set; }
  public string Name { get; set; }
}

Note that namespaces and using statements are included in the download example, but not in the code listings here.

To persist the data, I created a DbContext class called HumanContext:

public class HumanContext : DbContext {
  public HumanContext (string connectionString) : base (connectionString)
  {
    Database.SetInitializer<HumanContext> (new HumanInitializer ());
  }
  public DbSet<Human> Humans { get; set; }
  protected override void OnModelCreating (DbModelBuilder modelBuilder
  {
    modelBuilder.Entity<Human> ().ToTable ("Humans");
  }
}

The constructor expects a connection string to be passed in.

To prevent EF from completely failing at pluralizing the word Human to Humen (!), I’ve used a mapping to force the table name to be Humans. Additionally, in the constructor, I’m setting the database initializer to a custom initializer, HumanInitializer, which will seed the test database with a few romantic humans if the model changes. In case you’ve forgotten your EF6 lessons, I’ll remind you that this initializer will also create the database if it doesn’t exist yet. Here’s the HumanInitializer class:

public class HumanInitializer : DropCreateDatabaseIfModelChanges<HumanContext>
{
  protected override void Seed (HumanContext context)
  {
    context.Humans.AddRange (new Human[] {
      new Human { Name = "Juliette" },
      new Human { Name = "Romeo" }
    });
  }
}

Wiring Up ASP.NET Core and EF6

When using EF6.3 in ASP.NET Core, there are a few advantages from EF Core that you’ll miss out on. One is the integrated dependency injection. EF Core can tie itself into ASP.NET Core services using its AddDbContext extension method. This allows ASP.NET Core to inject object instances of a DbContext on demand into classes that need them, such as a controller class. But the AddDbContext method isn’t available without EF Core. Moreover, the EF Core database providers, such as Microsoft.EntityFrameworkCore.SqlServer, have extension methods that allow you to add details about the provider to the AddDbContext method. For example, SqlServer provides a UseSqlServer method to which you can supply options such as the connection string.

While these methods won’t be available when using EF6.3, you can still wire up the HumanContext to ASP.NET Core’s dependency injection services and let it know about the provider and the connection string. You just have to use different code, which goes in the same place as the EF Core code—inside the ConfigureServices method of the API’s startup class.

The IServiceCollection Add method (along with variations such as AddScoped) allow you to add any class to the services. Then you can pass in the instructions of what to do when, at runtime, that object is needed.

This code then says to keep an eye out for places where a HumanContext object is required and, in response, instantiate a new HumanContext, passing in a connection string found in appsettings.json:

services.AddScoped<HumanContext>
                (serviceProvider => new HumanContext (Configuration["Connection"]));

This takes care of the dependency injection for HumanContext.

What about making up for the loss of the UseSqlServer method to specify the database provider and connection string? As of Preview 6 of EF6.3, if no provider is specified, EF will use System.Data.SqlClient. So, because I’ll be using SQL Server, there’s no need to add any more code. SQL Server will be the default provider in this case.

The last piece of this wiring is to get the connection string into appsettings.json so the Configuration[“Connection”] method can read it:

"Connection":"Server=localhost,1601;Database=Humans;User Id=sa;Password=P@ssword1"

Recall that earlier I said that because I’m on my MacBook, I’ll be using SQL Server for Linux in a Docker container as the database. When I ran the container, I specified 1601 as the port on my machine from which to expose the database server. Here’s the docker command I used to run the container:

docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=P@ssword1'
  -p 1601:1433 -d --name SQLforEF6  mcr.microsoft.com/mssql/server:2017-latest

Note that this command needs to be on a single line. If you want to learn more about using SQL Server in a container, check out my July 2017 article, “On-the-Fly SQL Servers with Docker,” at msdn.com/magazine/mt784660. One difference from that article is that I’m now pointing to the new home of Microsoft’s Docker containers in the Microsoft Container Registry, the “mcr” in the URL of the image that the docker run command is targeting.

Adding a Controller That Uses EF6

The controller that was created by the template is a good way to test that your API is working, but it only builds and returns strings. To test out the database interaction, add a new HumanController class as shown in Figure 2. For this proof of concept, all you really need is a single HttpGet method to get some data. The Seed method in Human­Initializer will take care of inserting data.

Figure 2 The HumanController Class

[Route ("api/[controller]")]
[ApiController]
public class HumanController : ControllerBase
{
  HumanContext _context;
  public HumanController (HumanContext context)   {
    _context = context;
  }
  [HttpGet]
  public ActionResult<IEnumerable<Human>> Get ()
  {
   return _context.Humans.ToList ();
  }
}

Note that the first time in an application instance that EF tries to perform some interaction with the database is when the initialization I defined in the HumanContext constructor will get triggered. Therefore, the database won’t get created and seeded until I try to run this method. Because this is a simple demo, I’m not worrying about the possibility of having this application on multiple servers, which could cause a conflict if they were trying to run the initialization and seeding code concurrently. But it’s always a side effect to keep in mind when allowing an application to be responsible for database creation and seeding.

Running the API

Finally, with everything in place, you can run or debug the API. I usually start by running it from the command line with dotnet run. Once it’s running, I like to first browse to the values controller at localhost:5000/api/values to verify that the API itself is working. This should output “value1” and “value2” in the browser. Then I hit the database-dependent API at localhost:5000/api/human. If all goes well, the API will take a little time on its very first run to create the Humans database and create and seed the Humans table. Once that’s done, it should output the Ids and names of the two Humans, as shown in Figure 3.

The Results of the coreapi's Get Method
Figure 3 The Results of the coreapi's Get Method

Checking Out the Containerized Database

This is proof enough that the database was created and seeded. However, I always feel better if I double-check the database, especially when it’s in a containerized server.

Calling the docker ps command to list running containers proves that the container is indeed running:

➜  ~ docker ps

CONTAINER ID  IMAGE                                        COMMAND                 
4bfc01930095  mcr.microsoft.com/mssql/server:2017-latest   "/opt/mssql/bin/sqls…"
CREATED             STATUS              PORTS                    NAMES
23 hours ago        Up 13 hours         0.0.0.0:1601->1433/tcp   SQLforEF6

The VS Code Docker extension’s Docker Explorer is another way to see that my SQLforEF6 container is running.

And by using Azure Data Studio, I can connect to that container’s SQL Server instance to explore the database and its data (see Figure 4). For more information on this tool, see my earlier article on Azure Data Studio at msdn.com/magazine/mt832858.

Exploring the New Database in Azure Data Studio
Figure 4 Exploring the New Database in Azure Data Studio

Deploying the EF6.3-Dependent API to Docker for Linux

While it’s possible to put Windows-dependent apps into Windows containers, Linux containers are a lot easier to work with. And because EF6.3 can now run cross-platform, I’ll create a Linux-based image of the API.

In addition to creating the image, I’ll create a docker-compose file to run a container based on this image and enable it to communicate with SQL Server in a container. My three-part series in April, May and June 2019 issues of this magazine dig into how all of this works, so here I’ll just relay the highlights. You can also see all of the code in the sample download.

Again, the VS Code Docker extension helps with most of this effort. By pressing F1 and then typing Docker, you’ll find a number of commands that the extension provides. Choose “Docker: Add Docker Files to Workspace.” Then when prompted, choose ASP.NET Core as the application platform. Follow the rest of the prompts by choosing Linux as the OS and the default port 80.

The template-generated Dockerfile needs an update, though, because I’m targeting Preview 6 of .NET Core 3.0. I had to change the FROM image targets to pull Preview 6 versions directly from the Microsoft container registry (MCR). Change the first four lines of the Dockerfile as follows:

FROM mcr.microsoft.com/dotnet/core/aspnet:3.0.0-preview6 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/core/sdk:3.0.100-preview6 AS build

Next, I added a docker-compose file to orchestrate building this new image, running it in a container and also running another SQL Server container to which the API can talk. If you haven’t read my earlier articles yet, this is important so that the API container knows how to find the database container.

Figure 5 shows the docker-compose.yml file I added to my project.

Figure 5 The docker-compose.yml File to Run Both the API and Database Containers

version: '3.4'
services:
  coreapi:
    image: ${DOCKER_REGISTRY-}api
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 80:80
  db:
    image: mcr.microsoft.com/mssql/server
    environment:
      SA_PASSWORD: "P@ssword1"
      ACCEPT_EULA: "Y"
    ports:
      - "1601:1433"

The last change to make is the connection string in the API’s appsettings.json file. Currently it’s pointing to localhost, 1601. But the ASP.NET API inside the new container doesn’t have the server on its own localhost. However, if you change the server name to match the service name (db) in the docker-compose file, Docker will ensure that the API container can find the database container.

Here’s the new connection listing in appsettings.json:

"Connection":"Server=db;Database=Humans;User Id=sa;Password=P@ssword1"

That’s it. Now you can run the containers using docker-compose. Just right-click on the docker-compose.yml file in the document explorer and, thanks to the Docker extension, one of the options is Compose Up. Choose that and wait for it to complete. It may take another 30 seconds or so before SQL Server finishes setting up its system databases. Then you can browse to the API at localhost:80/api/human. Give it a few extra seconds to create and seed the Humans database. SQL Server is a little slow at doing that, but when it’s done, you can browse to the API and see the same output as in Figure 4! You can then right-click that yml file again and choose Compose Down to remove the new containers.

EF6.3 Without Windows!

While I haven’t taken EF6.3 for a real ride, I am amazed and impressed that I was able to do this entire proof of concept in a non-Windows environment. I wouldn’t begin a new project using EF6.3. All new work should use EF Core, which has so many advantages, especially with its cross-platform reach. But if you have production code happily using EF6 and want dependent apps to benefit from .NET Core features—whether for cross-platform capabilities or other great functionality—you’ll now be able to have your EF6 cake and eat it, too.


Julie Lerman is a Microsoft Regional Director, Microsoft MVP, Docker Captain and software team coach who lives in the hills of Vermont. You can find her presenting on data access and other topics at user groups and conferences around the world. She blogs at thedatafarm.com/blog and is the author of “Programming Entity Framework,” as well as a Code First and a DbContext edition, all from O’Reilly Media. Follow her on Twitter: @julielerman and see her Pluralsight courses at bit.ly/PS-Julie.

Thanks to the following Microsoft technical experts for reviewing this article: Diego Vega (Diego.Vega@microsoft.com), Brice Lambson <(bricelam@microsoft.com)>


Discuss this article in the MSDN Magazine forum