65

Can we run sql script using code first migrations?

I am new to code first and if I want to save my changes to a SQL script file before update-database command of migration, is it possible?

If possible then please provide steps to get it done. Also if script is generated then is it possible that I can run that script using migration?

wonea
  • 4,783
  • 17
  • 86
  • 139
Manprit Singh
  • 825
  • 2
  • 8
  • 18

6 Answers6

104

First you need to create a migration.

Add-Migration RunSqlScript

Then in the generated migration file you can write your SQL.

// PLAIN SQL
Sql("UPDATE dbo.Table SET Created = GETDATE()");

// FROM FILE
var sqlFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Custom.sql"); 
Sql(File.ReadAllText(sqlFile));

Then you run

Update-Database
Rikard
  • 3,828
  • 1
  • 24
  • 39
  • 2
    This works like magic for me. I further researched and found that we can add pre-created stored procedure with this using the below code. var sqlFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Custom.sql"); Sql(File.ReadAllText(sqlFile)); – Manprit Singh Aug 20 '15 at 19:59
  • 8
    Where do you place your SQL files in the project so you can grab them at runtime? `Path.Combine(AppDomain.CurrentDomain.BaseDirectory` puts me in the debug directory. – webworm Jan 14 '16 at 14:59
  • @webworm Maybe you could put it into the Migrations folder, and configure its properties on "Copy to Output Directory" as "Copy Always". The script will now be in the `bin` folder. – Reuel Ribeiro Jul 20 '16 at 13:06
  • 2
    Reuel Ramos Ribeiro, bad idea. – Kate Aug 19 '16 at 13:47
  • 1
    @Kate can you elaborate on why this is a bad idea, and, more importantly, how it should be done properly? – fikkatra Aug 25 '16 at 09:51
  • 4
    @fikkatra If you set "Copy to Output Directory" as "Copy Always" for folder with SP you will get .sql scripts on production. I use something like this:var sqlFilesFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory.Substring(0, AppDomain.CurrentDomain.BaseDirectory.IndexOf("bin")), "FolderNameWithMySP"); – Kate Aug 25 '16 at 11:30
  • In my case, it had to be `@"Migrations\Custom.sql"` because EF.Core creates a Migrations folder - and I have named the SQL file like the migration, but with .sql extension. Also I named it like the CS-file and created two - one for Up, the other for Down, i.e. `...Up.sql` and `...Down.sql` - so you can support both directions. – Matt Jul 01 '20 at 14:05
41

What I like to do is to embed the SQL script as a resource in the assembly and use the SqlResource method. I have tested this approach with Visual Studio 2017 15.5.6.

First you need to create a migration file:

  1. In Visual Studio make sure to set as start up project the project where your DbContext is defined
  2. In Visual Studio open the PMC: View -> Other Windows -> Package Manager Console
  3. In PMC Set the default project to the project that holds the DbContext
  4. If you have both EF core and EF 6.x installed:

    EntityFramework\Add-Migration RunSqlScript

  5. If you have only EF 6.x Installed:

    Add-Migration RunSqlScript

Add a Sql Script in the migration folder (I name it with the same prefix as the migration file)

Migration folder in the Solution Explorer

In the File properties window make sure the Build Action is "Embedded Resource" Note that we don't need to copy to the output folder as the sql script will be embedded in the assembly.

Update the Up method in the RunSqlScript migration

public override void Up()
{
    string sqlResName = typeof(RunSqlScript).Namespace  + ".201801310940543_RunSqlScript.sql";
    this.SqlResource(sqlResName );
}

I hope this helps

Yanal-Yves Fargialla
  • 1,257
  • 15
  • 16
  • 2
    I liked this method. Note: if you do this, don't try to nest the `.sql` under the migration file... this breaks the embedded of the resource. leave it un-nested. – Kris Coleman Feb 20 '18 at 18:30
  • 2
    Very nice. Just want to add that if your post-migration sql script is in a different folder of the same assembly, you can retrieve it like this: `var resourceNames = this.GetType().Assembly.GetManifestResourceNames(); var sqlResourceName = resourceNames.SingleOrDefault(r => r.Contains("201801310940543_RunSqlScript.sql"));` – Kon Aug 15 '18 at 15:23
  • @KrisColeman, what do you mean when you say "this breaks the embedded of the resource." I don't see any issue about nesting the `.sql` file. – Yanal-Yves Fargialla Sep 02 '18 at 07:27
  • sorry, should have been "embedding of the resource" When I try to nest the file with the migration file, it fails to find the resource at runtime. – Kris Coleman Sep 05 '18 at 16:50
  • @kriscoleman, sorry to ask, did you make sure the Build Action is "Embedded Resource" in the File properties window ? I am nesting SQL files without issue so I am wondering what this doesn't work for you? – Yanal-Yves Fargialla Sep 14 '18 at 14:44
  • Yes, of course. But once nested EF couldn't find the nested file. UnNested, it would work fine. – Kris Coleman Sep 14 '18 at 16:04
  • hello, i did some changes to my previously working fine sql file now i am getting this error on migrations *The argument 'sql' cannot be null, empty or contain only white space. at System.Data.Entity.SqlServer.Utilities.Check.NotEmpty(String value, String parameterName) at System.Data.Entity.SqlServer.SqlServerMigrationSqlGenerator.Statement(String sql, Boolean suppressTransaction, String batchTerminator) at System.Data.Entity.SqlServer.SqlServerMigrationSqlGenerator.StatementBatch(String sqlBatch, Boolean suppressTransaction) at ....* – Alok Sep 25 '19 at 07:41
  • Yep. Agree with @kriscoleman. When I nest sql file in .cs migration file, occurs an error - could not find sql file. Build Action is "Embedded Resource". – Denis Sep 26 '19 at 04:38
  • 1
    I'd played with the answer from @Rikard and ended up doing pretty much the same as this and it works great. I've got combinations of the generated Up() and Down() migration methods injected with some respective custom SQL `MyMigrationNameUp/Down.sql` scripts using `Sql(File.ReadAllText(sqlFile));` - works awesome!! – jamheadart Sep 26 '19 at 14:01
9

For .NET Core and EF Core you can do something like this in migrations

protected override void Up(MigrationBuilder migrationBuilder)
{
   var schema = "starter_core";
   migrationBuilder.Sql($"INSERT INTO [{schema}].[Roles] ([Name]) VALUES ('transporter')");
}
Monomachus
  • 1,448
  • 2
  • 13
  • 22
7

Like SQL, we have another method SqlFile. you can directly use that.

mongesh madhavan
  • 583
  • 4
  • 16
2

A bit late, but could be helpful.

You can create a static helper class as:

public static class SqlFileTrigger {    
  public static string GetRawSql(string fileName) {
    var baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Migrations"); // your default migrations folder
    var filePath = Path.Combine(baseDir, fileName);
    return File.ReadAllText(filePath);
  }
}

Then create your SQL files with similar naming convention followed by "{migrationName}_Up.sql" and "{migrationName}_Down.sql" like:

For example, assuming your created migration is named as "20230120230041_AddAuditingProperties.cs"

This way they will be grouped under your migration .cs file properly. (You can also create a separate folder for this but I usually prefer to keep them stacked so I can easily check if needed)

Then you can add "20230120230041_AddAuditingProperties_Up.sql" as well as the "20230120230041_AddAuditingProperties_Down.sql" (if down sql is needed)

Then for .NET EF:

  public partial class AddAuditingProperties : Migration
        {            
            protected override void Up(MigrationBuilder migrationBuilder)
            {
                Sql(Utils.SqlFileTrigger.GetRawSql("20230120230041_AddAuditingProperties_Up.sql"));
            }
    
            
            protected override void Down(MigrationBuilder migrationBuilder)
            {
                Sql(
                    Utils.SqlFileTrigger.GetRawSql("20230120230041_AddAuditingProperties_Down.sql"));
            }
        }

For .NET Core EF-Core

public partial class AddAuditingProperties : Migration
    {            
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql(Utils.SqlFileTrigger.GetRawSql("20230120230041_AddAuditingProperties_Up.sql"));
        }

        
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql(
                Utils.SqlFileTrigger.GetRawSql("20230120230041_AddAuditingProperties_Down.sql"));
        }
    }

PS: As a side note, if you are planing them to be deployed and run on server automatically, make sure you right click those created .sql files, and set "Build Action" as "Content" and "Copy to Output Directory" as "Copy always" - I had problems before when they were not set.

curiousBoy
  • 6,334
  • 5
  • 48
  • 56
0
  1. You can add a new migration using Package Manager Console like this:
    add-migration add_defaultValues 
    
  2. And then in the up method add the following command that contains the command you to execute:
    protected override void Up(MigrationBuilder migrationBuilder)
    {
    
        string Sql = @"GO
                INSERT [dbo].[TableName] ([Layer], [Environment], [Group], [Section], [Value], [Description], [LastChangeDate], [LoadType]) VALUES (N'ADMIN', N'TEST', N'PERFORMANCE', N'ALLOW_DB', N'true', NULL, CAST(N'2022-07-18T00:00:00.0000000' AS DateTime2), N'STARTUP')
                GO
                INSERT [dbo].[TableName] ([Layer], [Environment], [Group], [Section], [Value], [Description], [LastChangeDate], [LoadType]) VALUES (N'ADMIN', N'TEST', N'PERFORMANCE', N'ALLOW_FILE', N'false', NULL, CAST(N'2022-07-18T00:00:00.0000000' AS DateTime2), N'STARTUP')
                GO
                INSERT [dbo].[TableName] ([Layer], [Environment], [Group], [Section], [Value], [Description], [LastChangeDate], [LoadType]) VALUES (N'ADMIN', N'TEST', N'PERFORMANCE', N'ALLOW_MAIL', N'false', NULL, CAST(N'2022-07-18T00:00:00.0000000' AS DateTime2), N'STARTUP')
                GO";
    
        migrationBuilder.Sql(Sql);
    
    }
    
Saeed Zhiany
  • 2,051
  • 9
  • 30
  • 41
  • 1
    As [this answer](https://stackoverflow.com/a/56325669/861716) already said. Please don't repeat answers. It's not helpful. Vote for them instead. – Gert Arnold Jul 18 '22 at 18:39