Tuesday, 19 July 2016

Run Entity Framework Core DB Scaffold with Gulp

If you're using ASP.NET Core Entity Framework (database first) you may have to rebuild your DB context and models frequently.

Instead of manually running "Scaffold-DbContext" in package manager console and updating the DBContext (as per https://docs.efproject.net/en/latest/platforms/aspnetcore/existing-db.html), you can speed things up with a Gulp task.

You can create a Gulp task and use the "node-powershell" to quickly run the scaffold command in dotnet CLI and automatically remove the hardcoded connection string from the Context class.

The script below creates a Gulp task ("build-DbContext") that will:

  1. Get the database connection string from appsettings.json
  2. Delete any previously created database models
  3. Create new models and DbContext (called ProjectDbContext), by running the "dotnet ef dbcontext scaffold" command
  4. Replace the "OnConfiguring" method with a new constructor in the "ProjectDbContext" class
This can then be quickly executed in Task Runner as you need it.


gulpfile.js (build-DbContext)


var gulp = require('gulp');
var fs = require('fs');
var shell = require('node-powershell');
var del = require('delete');
var change = require('gulp-change');
var savefile = require('gulp-savefile');

var _appSettingsPath = "./appsettings.json";

var _contextProvider = "Microsoft.EntityFrameworkCore.SqlServer"
var _contextClass = "ProjectDbContext";
var _contextOutputDir = "Models";

gulp.task('build-DbContext', function () {
    ScaffoldDbContext();
});

//Get connection string from the appsettings file
function GetConnectionString() {
    var _appsettingJSON = JSON.parse(fs.readFileSync(_appSettingsPath, "utf8").replace(/^\uFEFF/, ''));

    return _appsettingJSON.data.DefaultConnection;
}

function ScaffoldDbContext() {
    _defaultConnection = GetConnectionString();

    //Clear out old models first
    del("models/**/*");

    var _powerShell = new shell({ executionPolicy: 'Bypass', debugMsg: true });

    _powerShell.addCommand('dotnet ef dbcontext scaffold "' + _defaultConnection + '" ' + _contextProvider + ' --context "' + _contextClass + '" --output-dir "' + _contextOutputDir + '"  --data-annotations')
        .then(function () {
            return _powerShell.invoke();
        })
        .then(function (output) {
            console.log(output);
            _powerShell.dispose();
        })
        .then(function () {
            UpdateDbContext();
        })
        .catch(function (err) {
            console.log(err);
            _powerShell.dispose();
            this.emit('end');
        });

    
}

function UpdateDbContext() {
    return gulp.src(_contextOutputDir + "/" + _contextClass + ".cs")
            .pipe(change(RemoveInlineContextConfig))
            .pipe(savefile());
}

function RemoveInlineContextConfig(content, done) {
    var _constructorMethod = "public " + _contextClass + "(DbContextOptions<" + _contextClass + "> options)\n: base(options)\n{ }";

    //Replace the "OnConfiguring" method with new constructor
    var _newContent = content.replace(/protected.*OnConfiguring(((?!}).|[\r\n])*)}/g, _constructorMethod);

    done(null, _newContent);
}


Project

A basic example project looks like:


appsettings.json

Make sure you have a connection string value such as "DefaultConnection":
{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "data": {
    "DefaultConnection": "Server=.\\[INSTANCENAME];Database=[DATABASENAME];user id=[USERNAME];password=[PASSWORD];Trusted_Connection=True;"
  }
}

package.json

Add the required Gulp dependencies:
{
 "version": "1.0.0",
 "name": "avros-data",
 "private": true,
  "devDependencies": {
    "gulp": "3.9.1",
    "node-powershell": "2.0.2",
    "fs": "0.0.2",
    "delete": "0.3.2",
    "gulp-change": "1.0.0",
    "gulp-savefile" : "0.1.1"
  }
}

project.json

    "Microsoft.EntityFrameworkCore.SqlServer.Design": "1.0.0",
    "Microsoft.EntityFrameworkCore": "1.0.0",
    "Microsoft.EntityFrameworkCore.Tools": {
      "version": "1.0.0-preview2-final",
      "imports": [
        "portable-net45+win8+dnxcore50",
        "portable-net45+win8"
      ]
    }

  "tools": {
    "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
  },


Once setup you can run the within Task Runner Eplorer