Jesse Bellingham
Jesse's Blog

Follow

Jesse's Blog

Follow

This Week I Learned: How to enable a SQL Server Database project cross-platform

Jesse Bellingham's photo
Jesse Bellingham
·Dec 5, 2021·

4 min read

This Week I Learned

It’s possible to build a SQL Server Database project down to a dacpac on Mac or Linux.

What is a SQL Server Database project?

A database project is similar to the plain old C# based projects you're probably already familiar with. Where the database project differs is that it allows you to develop, build, and publish a database from a source controlled project, using SQL scripts. Before Entity Framework and code-first arrived, it was (seemingly) the preferred approach to database administration, and it still is the preferred approach among those more comfortable with SQL than C# code.

A database project describes the desired state of your database schema. The output of a database project build is a .dacpac file, that can be used with various tooling to deploy or migrate a database instance.

Historically, building these SQL Server Database projects was only possible on Windows, due to the .sqlproj project type being based on an older version of .NET Framework.

Well it turns out now you can build these projects without Windows, it just takes a little finessing. Here are two options I've found:

Option 1: Add a separate, cross-platform build project

Firstly, in order to create .NET Framework projects on Mac or Linux, you will need to download the Mono sdk.

Assuming you already have an existing database project, let’s create a new ".NET Standard Class Library" project called Database.Build. Delete Class1.cs, we won’t be using it.

Open up Database.Build.csproj for editing and you should see something like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

</Project>

Edit the line <Project Sdk="Microsoft.NET.Sdk"> to instead read <Project Sdk="MSBuild.Sdk.SqlProj/1.15.0">

Next, we can add any links to sql scripts in our existing database that we want to include in our new dacpac build:

<ItemGroup>
  <Content Include="..\Database\dbo\**\*.sql" />
</ItemGroup>

The content path assumes an existing co-located database project called Database.

If you run a build for Database.Build you should see output similar to

1>ResolveDatabaseReferences:
         Resolved dacpac file from package NETStandard.Library to /users/jessebellingham/.nuget/packages/netstandard.library/2.0.3/tools/NETStandard.Library.dacpac
         Resolved database package references:
       ValidateEnvironment:
         Using target framework netcoreapp3.1 to run DacpacTool
       CoreGenerateAssemblyInfo:
       Skipping target "CoreGenerateAssemblyInfo" because all output files are up-to-date with respect to the input files.
       CoreCompile:
         dotnet "/Users/jessebellingham/.nuget/packages/msbuild.sdk.sqlproj/1.15.0/Sdk/../tools/netcoreapp3.1/DacpacTool.dll" build -o "obj/Debug/netstandard2.0/Database.Build.dacpac" -n "Database.Build" -v "1.0.0" -sv Sql150 -i "obj/Debug/netstandard2.0/Database.Build.InputFiles.txt"
         Using package name Database.Build and version 1.0.0
         Using SQL Server version Sql150
         Adding /Users/jessebellingham/dev/CrossPlatformDbBuild/Database/dbo/Table1.sql to the model

Writing model to /Users/jessebellingham/dev/CrossPlatformDbBuild/Database.Build/obj/Debug/netstandard2.0/Database.Build.dacpac
       CopyFilesToOutputDirectory:
         Copying file from "/Users/jessebellingham/dev/CrossPlatformDbBuild/Database.Build/obj/Debug/netstandard2.0/Database.Build.dacpac" to "/Users/jessebellingham/dev/CrossPlatformDbBuild/Database.Build/bin/Debug/netstandard2.0/Database.Build.dacpac".
         Database.Build -> /Users/jessebellingham/dev/CrossPlatformDbBuild/Database.Build/bin/Debug/netstandard2.0/Database.Build.dacpac
     1>Done Building Project "/Users/jessebellingham/dev/CrossPlatformDbBuild/Database.Build/Database.Build.csproj" (build target(s)).

And with that, we now have successfully built a .dacpac without needing Windows 🎉

Note

If you need to target a different version of SQL Server than the default Sql150, you can add this line <SqlServerVersion>SqlAzure</SqlServerVersion> to the <PropertyGroup section.

Similarly, if you want to use SqlCmd variables, these can just be copied across from your existing .sqlproj file, e.g.:

<ItemGroup>
  <SqlCmdVariable Include="SomeVariable" />
</ItemGroup>

Finally, there is support for running pre and post-deployment scripts in a dacpac deploy build this way, although there are limitations described here.

<ItemGroup>
  <PostDeploy Include="..\Database\Post-Deployment\Script.PostDeployment.sql" />
</ItemGroup>

Option 2: Use .NET Framework reference assemblies

This option is a bit simpler, and involves adding a package reference directly to your .sqlproj file as can be seen here. Note: this approach also works for regular .csproj projects.

We'll need to add the following to your <PropertyGroup> element:

<TargetFramework>net452</TargetFramework>

We'll also need to add a new ItemGroup:

  <ItemGroup>
    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="All" />
  </ItemGroup>

With the whole thing looking something like this for a database project:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Database</OutputType>
    <TargetFramework>net452</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="All" />
  </ItemGroup>

</Project>

With that done, we need to do a dotnet restore to populate the added reference assemblies, at which point, we should be able to do a dotnet build noting a similar build output to the one shown above.

Note

An issue I ran into with this second approach was this Asset file is missing target error. As the docs suggest, I was able to resolve it by ensuring that I had the TargetFrameworks property, and deleting the obj folder. After doing this, the build should now be working.

 
Share this