Code First Entity Framework Core

Most of the people worked with Entity Framework are familiar with the approach of generating database through code populary known as "Code First" approach. "Code First" workflow begins with classes that describe the conceptual model.  Code first approach offers most control over the final appearance of the application code and the resulting database.  Following are the few advantages of this approach.

Advantages:

  • Database Version Control
    • Versioning databases is hard, but with code first and code first migrations, it’s much more effective
    • Because the database schema is fully based on code models, by version controlling source code you're helping to version your database
  • Speed up Development
    • No dependency between developers
    • Easy to onboard new developer
    • Each developer will have their own Database
    • Much easier and quicker to add a property to a class in the code than it is to add a column to a table
    • Full control over database design without much knowledge of SQL and DBA
    • Out of the box support from Visual Studio and TFS/VSTS to merge code with other developers
  • More support for DevOps
    • Doesn’t require a separate resource to manage DB
    • Easy to generate Create, Update and Delete scripts
    • More support for test data and Unit testing

While we talk about the advantages of Code First approach, there are few disadvantages as well.  This approach requires everything related to database has to be done through coding.  If there are any changes in Database directly, those changes will be overwritten on subsequent migrations or end up with a conflict.

Though there are many blog posts and articles talks about Code First migrations, I didn't see a complete implementation anywhere.  We will talk about the following steps in the remaining article.

  1. NuGet Packages required for EF Core
  2. Creating DbContext and DbContext Factory
  3. Configure DbContext in Startup.cs
  4. Setting up Connection string in appsettings.json
  5. Create Migration and Update database
  6. Seed database

1. Required NuGet Packages:

For the purpose of this blog, i have created an ASP.NET Web API Core project using VS 2017.  We need to install "Microsoft.EntityFrameworkCore.SqlServer" and "Microsoft.EntityFrameworkCore.Tools". 

(Package versions in the image are the latest at the time of writing this blog)

2. Creating DbContext and DbContext Factory:

Before creating the DbContext, we have to create the model which can be used in the context.  I have created a simple model called "Employee" which will be generated as a table later.

Employee.cs

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCodeFirst.Models
{
    public class Employee
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public string Title { get; set; }
    }
}

IMyDbContext.cs


using Microsoft.EntityFrameworkCore;

namespace EFCodeFirst.Models
{
    public interface IMyDbContext
    {
        DbSet Employees { get; set; }
    }

    public class MyDbContext : DbContext, IMyDbContext
    {
        public MyDbContext(DbContextOptions options) : base(options)
        {

        }
        public DbSet Employees { get; set; }
    }
}

MyDbContextFactory.cs


using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.Configuration;

namespace EFCodeFirst.Models
{
    public class MyDbContextFactory : IDbContextFactory
    {
        public MyDbContext Create()
        {
            var environmentName = Environment.GetEnvironmentVariable("Hosting:Environment");
            var basePath = AppContext.BaseDirectory;
            return Create(basePath, environmentName);
        }

        public MyDbContext Create(DbContextFactoryOptions options)
        {
            return Create(options.ContentRootPath, options.EnvironmentName);
        }

        private MyDbContext Create(string basePath, string environmentName)
        {
            var builder = new ConfigurationBuilder()
            .SetBasePath(basePath)
            .AddJsonFile("appsettings.json")
            .AddJsonFile($"appsettings.{environmentName}.json", true)
            .AddEnvironmentVariables();

            var config = builder.Build();
            var connstr = config.GetConnectionString("DefaultConnection");

            if (string.IsNullOrWhiteSpace(connstr) == true)
            {
                throw new InvalidOperationException(
                "Could not find a connection string named '(DefaultConnection)'.");
            }
            else
            {
                return Create(connstr);
            }
        }

        private MyDbContext Create(string connectionString)
        {
            if (string.IsNullOrEmpty(connectionString))
                throw new ArgumentException(
                $"{nameof(connectionString)} is null or empty.",
                nameof(connectionString));

            var optionsBuilder = new DbContextOptionsBuilder();
            optionsBuilder.UseSqlServer(connectionString);
            return new MyDbContext(optionsBuilder.Options);
        }
    }
}

3. Configure DbContext in Startup.cs


public void ConfigureServices(IServiceCollection services)
{
      services.AddTransient<IMyDbContext, MyDbContext>();
      services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
      // Add framework services.
      services.AddMvc();
}

4. Setting up Connection string in appsettings.json

I used SQL Express for this demo.  The connection string can be replaced to work with SQL Authentication.  The approach we are using here enabled the developer to enable migrations without hardcoding connection string.  An environment specific appsettings.json file can also be used

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=.\\sqlexpress;Database=EFCodeFirst;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

5. Create Migration and Update database

Once the steps 1 through 4 are completed, we can create the migration.

Run the following commands in package manager console to create the migration and update the database respectively

  • add-migration "InitialMigration"
      • Creates a folder called "Migrations" in project root and adds a new migration file
      • This command also generates the following class called "InitialMigration" inside the "Migrations" folder
    using Microsoft.EntityFrameworkCore.Migrations;
    using Microsoft.EntityFrameworkCore.Metadata;
    
    namespace EFCodeFirst.Migrations
    {
        public partial class InitialMigration : Migration
        {
            protected override void Up(MigrationBuilder migrationBuilder)
            {
                migrationBuilder.CreateTable(
                    name: "Employees",
                    columns: table => new
                    {
                        Id = table.Column(nullable: false)
                            .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                        Age = table.Column(nullable: false),
                        FirstName = table.Column(nullable: true),
                        LastName = table.Column(nullable: true),
                        Title = table.Column(nullable: true)
                    },
                    constraints: table =>
                    {
                        table.PrimaryKey("PK_Employees", x => x.Id);
                    });
            }
    
            protected override void Down(MigrationBuilder migrationBuilder)
            {
                migrationBuilder.DropTable(
                    name: "Employees");
            }
        }
    }
    
    
  • update-database
    • This command updates the database to a specified migration

Please refer this link for more information on EF commands

6. Seed database

Now we have created the database and tables, lets see how to populate the tables with data.  The approach i am going to use requires an extension method of MyDbContext class and some code in Startup.cs to call the extension method and trigger the seeding.  The way i have written the code in the seeding method will seed the data during the application startup and only when the table is empty.  This method can be refactored in a better way.

MyDbContextExtensions.cs

using System.Linq;

namespace EFCodeFirst.Models
{
    public static class MyDbContextExtensions
    {
        public static void EnsureSeedData(this MyDbContext context)
        {
            SeedEmployees(context);
            context.SaveChanges();
        }

        private static void SeedEmployees(MyDbContext context)
        {
            if (!context.Employees.Any())
            {
                context.Employees.AddRange(
                    new Employee { FirstName = "Jane", LastName = "Doe", Age = 34, Title = "Developer" }
                    );
            }
        }
    }
}

Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
       loggerFactory.AddConsole(Configuration.GetSection("Logging"));
       loggerFactory.AddDebug();

       if (env.IsDevelopment())
       {
           using (var serviceScope = app.ApplicationServices.GetRequiredService().CreateScope())
           {
               // Migrate the database
               serviceScope.ServiceProvider.GetService().Database.Migrate();
               // Seed data
               serviceScope.ServiceProvider.GetService().EnsureSeedData();
           }
       }

       app.UseMvc();
}

Useful Links

EF Core

EF Core with ASP.NET Core

Working with multiple environments

Connection Strings

1 Comment

Comments have been disabled for this content.