11

My problem is i am trying to seed an Entity Framework Core database with data and in my mind the below code show work. I've realised that this should not be called in the ApplicationDbContext constructor and should be called from the startup but im not sure how to do this.

EDIT: Based on the solution provided by Ketrex, my solution is as follows:

Startup.cs:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        ... 

        app.ApplicationServices.GetRequiredService<ApplicationDbContext>().Seed();
    }

Seed extension:

public static class DbContextExtensions
{
    public static void Seed(this ApplicationDbContext context)
    {
        // Perform database delete and create
        context.Database.EnsureDeleted();
        context.Database.EnsureCreated();

        // Perform seed operations
        AddCountries(context);
        AddAreas(context);
        AddGrades(context);
        AddCrags(context);
        AddClimbs(context);

        // Save changes and release resources
        context.SaveChanges();
        context.Dispose();
    }

    private static void AddCountries(ApplicationDbContext context)
    {
        context.AddRange(
            new Country { Name = "England", Code = "En" },
            new Country { Name = "France", Code = "Fr" }
            );
    }

    ...
}

I understand that seeding a database is quite high up on the priority list for Entity Framework but it would be great if there was some documentation on how to achieve this trivial task or at least provide a temporary work around. If someone can provide some guidance on how to do this it would be greatly appreciated. I feel i'm close to a solution but just cant piece it together.

Thanks for any help.

Adam H
  • 561
  • 1
  • 9
  • 20

3 Answers3

11

Assuming you are using the built-in DI container, here is one way you can accomplish this.

Reference your seed method in the Configure method of your startup class, and pass the IApplicationBuilder object as a parameter instead of the DbContext, like this:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    //...
    // Put this at the end of your configure method
    DbContextSeedData.Seed(app);
}

Next, modify your seed method to accept the IApplicationBuilder instance. Then you'll be able to spin up an instance of the DbContext, and perform your seed operation, like this:

public static void Seed(IApplicationBuilder app)
{
    // Get an instance of the DbContext from the DI container
    using (var context = app.ApplicationServices.GetRequiredService<ApplicationDbContext>())
    {
        // perform database delete
        context.Database.EnsureDeleted;
        //... perform other seed operations
    }
}
cygnim
  • 1,985
  • 2
  • 16
  • 22
  • Thank you so much! It was this line `app.ApplicationServices.GetRequiredService()` that i was missing. I modified your solution slightly to use it as an extension method. – Adam H Jun 26 '16 at 17:59
  • 1
    @MohammadAkbari you seem to be picking nits. Notice the //... perform other seed operations line? It clearly implies he may have other operations to perform. The problem addressed here was injecting the context into the seed method. – cygnim Jul 12 '16 at 16:57
  • Beware of the service locator anti-pattern, but at the same time nice workaround. I look forward to EF Core getting more features so it can be used in prod. – Lotok Feb 07 '17 at 23:39
  • I ended up using `public static IApplicationBuilder Seed(this IApplicationBuilder app)` so that my startup could just use `app.seed();` – Brian Webb Oct 16 '17 at 04:12
  • I'm a very newbie and followed this string very carefully, but kept getting a run-time error about scoping. Found and made this change (I'm using core 2.1 RC) .public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() .UseDefaultServiceProvider(options => options.ValidateScopes = false) (https://blogs.msdn.microsoft.com/dotnet/2017/05/12/announcing-ef-core-2-0-preview-1/) – user1489765 May 29 '18 at 23:17
4

You could also use from the Startup.cs ConfigureServices method to make your ApplicationDbContext available (Registering the dbcontext as a service):

public void ConfigureServices(IServiceCollection services)
{
   var connectionString = Startup.Configuration["connectionStrings:DBConnectionString"];//this line is not that relevant, the most important thing is registering the DbContext
            services.AddDbContext<ApplicationDbContext>(o => o.UseSqlServer(connectionString));
}

and then add your ApplicationDbContext as dependency in your Configure method which will call your seed extension method.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, ApplicationDbContext myApplicationDbContext)
{
    //...
    myApplicationDbContext.Seed();
}

Finally the seed method could do a quick check on an important table, because perhaps recreating the db is too heavy:

public void Seed()
{
 //....      
 if(context.Countries.Any())
   return;
 //...
}

I hope it helps you or someone else, at least as another option.

Adrita Sharma
  • 21,581
  • 10
  • 69
  • 79
darmis
  • 2,879
  • 1
  • 19
  • 21
0

If you want to run your EF code from a separate class library and do Seeding in that you can do the following. This is using TSQL...

1) Create a new class library. Add the following dependencies with NuGet...

Microsoft.AspNetCore
Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools

2) Point the Package Manager Console at this project and run...

PM> add-migration Seeder01

then...

PM> update-database

this gives you an empty migration.

3) Script the updates as something like...

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using System.IO;

namespace Test02.Data.Migrations
{
    public partial class Seeder01 : Migration
    {

        protected override void Up(MigrationBuilder migrationBuilder)
        {
            string sql = string.Empty;

            sql = "SET IDENTITY_INSERT State ON;";
            sql += "Insert into State (Id, Name) values ";
            sql += "(2, 'NSW'),";
            sql += "(3, 'VIC'),";
            sql += "(4, 'QLD'),";
            sql += "(5, 'SA')";
            sql += ";";
            sql += "SET IDENTITY_INSERT State OFF;";
            migrationBuilder.Sql(sql);

        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            string sql = string.Empty;
            sql = "delete State;";
            migrationBuilder.Sql(sql);


        }
    }
}

4) Revert to the prior migration with...

PM> add-migration {PriorMigrationName}

Reload the seed migration and update the database...

PM> add-migration Seeder01
PM> update-database
CYoung
  • 307
  • 1
  • 10