diff --git a/docs/content/dynamic local db.fsx b/docs/content/dynamic local db.fsx index 69a96b6e..1b54c728 100644 --- a/docs/content/dynamic local db.fsx +++ b/docs/content/dynamic local db.fsx @@ -6,89 +6,135 @@ Dynamic creation of offline MDF =============================== -Sometimes you don't want to have to be online just to compile your programs. With FSharp.Data.SqlClient you can use a local -.MDF file as the compile time connection string, and then change your connection string at runtime when you deploy your application. +Sometimes you don't want to have to be online just to compile your programs, or +you might not have access to your production database from your CI systems. With +FSharp.Data.SqlClient you can use a local .MDF file as the compile-time +connection string, and then use a different connection string when you deploy +your application. + +A connection string to a local .MDF file might look like this: *) open FSharp.Data [] -let connectionString = @"Data Source=(LocalDB)\v12.0;AttachDbFilename=C:\git\Project1\Database1.mdf;Integrated Security=True;Connect Timeout=10" +let compileConnectionString = + @"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=C:\git\Project1\Database1.mdf;Integrated Security=True" (** -However, binary files like this are difficult to diff/merge when working with multiple developers. For this reason wouldn't it be nice -to store your schema in a plain text file, and have it dynamically create the MDF file for compile time? +However, binary files like this are difficult to diff/merge when working with +multiple developers, so you might not want to check them in. Wouldn't it be nice +to store your schema in a plain text file, and have it dynamically create the +MDF file for compile time? Well the following scripts can do that for your project. -First create a file called `createdb.ps1`: +First create a file called `createDb.ps1` and place it in an `SQL` subfolder in +your project (you can place it in the project root to, if you want): - # this is the name that Fsharp.Data.SqlClient TypeProvider expects it to be at build time - $new_db_name = "Database1" + param( + [Parameter(Mandatory=$true)][String]$DbName, + [Parameter(Mandatory=$true)][String]$DbScript + ) $detach_db_sql = @" - use master; - GO - EXEC sp_detach_db @dbname = N'$new_db_name'; - GO + IF (SELECT COUNT(*) FROM sys.databases WHERE name = '$DbName') > 0 + EXEC sp_detach_db @dbname = N'$DbName' "@ $detach_db_sql | Out-File "detachdb.sql" - sqlcmd -S "(localdb)\v11.0" -i detachdb.sql - Remove-Item .\detachdb.sql + sqlcmd -S "(LocalDB)\MSSQLLocalDB" -i "detachdb.sql" + Remove-Item "detachdb.sql" - Remove-Item "$new_db_name.mdf" - Remove-Item "$new_db_name.ldf" + if (Test-Path "$PSScriptRoot\$DbName.mdf") { Remove-Item "$PSScriptRoot\$DbName.mdf" } + if (Test-Path "$PSScriptRoot\$DbName.ldf") { Remove-Item "$PSScriptRoot\$DbName.ldf" } $create_db_sql = @" - USE master ; - GO - CREATE DATABASE $new_db_name - ON - ( NAME = Sales_dat, - FILENAME = '$PSScriptRoot\$new_db_name.mdf', - SIZE = 10, - MAXSIZE = 50, - FILEGROWTH = 5 ) - LOG ON - ( NAME = Sales_log, - FILENAME = '$PSScriptRoot\$new_db_name.ldf', - SIZE = 5MB, - MAXSIZE = 25MB, - FILEGROWTH = 5MB ) ; - GO + CREATE DATABASE $DbName + ON ( + NAME = ${DbName}_dat, + FILENAME = '$PSScriptRoot\$DbName.mdf' + ) + LOG ON ( + NAME = ${DbName}_log, + FILENAME = '$PSScriptRoot\$DbName.ldf' + ) "@ $create_db_sql | Out-File "createdb.sql" - sqlcmd -S "(localdb)\v11.0" -i createdb.sql - Remove-Item .\createdb.sql + sqlcmd -S "(LocalDB)\MSSQLLocalDB" -i "createdb.sql" + Remove-Item "createdb.sql" - sqlcmd -S "(localdb)\v11.0" -i schema.sql + sqlcmd -S "(LocalDB)\MSSQLLocalDB" -i "$DbScript" $detach_db_sql | Out-File "detachdb.sql" - sqlcmd -S "(localdb)\v11.0" -i detachdb.sql - Remove-Item .\detachdb.sql + sqlcmd -S "(LocalDB)\MSSQLLocalDB" -i "detachdb.sql" + Remove-Item "detachdb.sql" Then change your connection string to look like this *) [] -let connectionStringForCompileTime = @"Data Source=(LocalDB)\v12.0;AttachDbFilename=" + __SOURCE_DIRECTORY__ + @"\Database1.mdf;Integrated Security=True;Connect Timeout=10" +let compileConnectionString = + @"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=" + __SOURCE_DIRECTORY__ + @"\Database1.mdf;Integrated Security=True;Connect Timeout=10" -type Foo = SqlCommandProvider<"SELECT * FROM Foo", connectionStringForCompileTime> +type Foo = SqlCommandProvider<"SELECT * FROM Foo", compileConnectionString> let myResults = (new Foo("Use your Runtime connectionString here")).Execute() (** -Lastly, edit your `.fsproj` file and add the following to the very end right before `` - - - - +Lastly, edit your `.fsproj` file and add the following to the very end right +before ``: + + + + + + + + + + + + + + + + -Now when you build, it will create a database named `Database1` and then look for a file called `schema.sql` which will be used -to create the database. It will then compile against this dynamically generated MDF file so you'll get full static type checking -without the hassle of having to have an internet connection, or deal with binary .MDF files! + + + + -*) \ No newline at end of file +Now when you build, it will create the databases `SQL\Db1.mdf` and `SQL\Db2.mdf` +using the scripts `SQL\create_myDb1.sql` and `SQL\create_myDb2.sql`. It will +then compile against this dynamically generated MDF file so you'll get full +static type checking without the hassle of having to have an internet +connection, or deal with binary .MDF files! + +Furthermore, the `.fsproj` edits above give the following benefits: +* The DBs are rebuilt if their corresponding SQL scripts have changed, or if the + PowerShell script has changed +* The project is rebuilt if the PowerShell script has changed +* The project is rebuilt if any SQL file has changed (both the database creation + scripts, and any other SQL scripts that SqlClient might use though the + `SqlFile` type provider) +* Incremental build - each database is only built if its corresponding SQL + script or the PowerShell script has changed + +When it comes to actually making the database creation scripts (such as the +`create_myDb1.sql` in the example above), you can do this if you use SQL Server +Management Studio (SSMS): +* Connect to the database you want to copy +* Right-click the database and select Tasks -> Generate scripts +* Select what you need to be exported (for example, everything except Users). +* If SqlClient throws errors when connecting to your local database, you might + be missing important objects from your database. Make sure everything you need + is enabled in SSMS under Tools -> Options -> SQL Server Object Explorer -> + Scripting. For example, if you have indexed views and use the `WITH + (NOEXPAND)` hint in your SQL, you need the indexes too, which are not enabled + by default. In this case, enable "Script indexes" under the "Table and view + options" heading. +*)