How to include created database project created dacpac into a cs project - c#

I have a separate database project that I’d like to build within the same solution and then reference the created dacpac. When I try adding the database project it builds fine and the dll is added to the secondary project file, but the dacpac is not.
Is there a way that I can have the dacpac copied into my main project through the msbuild? I keep on thinking that there should be a way to modify either the sqlproj file or csproj file so that the dacpac is included as one of the project outputs. My knowledge of msbuild is not extensive, I’ve not been able to figure it out.
It seems to me that I need to add the dacpac somehow to say the '#(ReferenceCopyLocalPaths)' item but I have not been able to figure it out. Any tips or suggestions would be appreciated.
I tried doing something a little like what is referenced here MSBuild - ItemGroup of all bin directories within subdirectories by doing:
<Target Name="AfterBuild">
<Message Text="#(MainAssembly)" />
<!--<DacPacs Include="%(ProjectReference.Directory)**" />-->
<ItemGroup>
<DacPacs Include="%(ProjectReference.Directory)**/*bin*/*.dac" />
</ItemGroup>
<Message Text="#(ReferenceCopyLocalPaths)" />
<Message Text="DacPacs: #(DacPacs)" />
<Message Text="Target Database: $(TargetDatabase)" />
</Target>
which gives nothing for DacPacs (when the wildcard is added). Also I tried referencing one of the item groups from the sqlproj file but it comes out empty to:

In the project properties, you can add a pre-build event command line to get a copy of the dacpac file.
Or you can just add it in the csproj :
<PropertyGroup>
<PreBuildEvent>copy "$(SolutionDir)DatabaseProject\bin\$(ConfigurationName)\DatabaseProject.dacpac" "$(ProjectDir)\DatabaseProject.dacpac"</PreBuildEvent>
</PropertyGroup>
This will only work if the database project was built first, so you should add a dependency. Right clic on the solution and select Project Dependencies..., then select the main project and check that it depends on the Database project.

Add this to your csproj:
<ItemGroup>
<Content Include="..\DatabaseProject\bin\Debug\DatabaseProject.dacpac">
<Link>DatabaseProject.dacpac</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

This solution worked for me with a .NET 6 project which references a .sqlproj project using Micrsoft.Sql.Sdk. Assume there are two projects: DacpacDepender (.NET 6 project) and Dacpac (SQL project).
In DacpacDepender.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- some properties redacted for brevity -->
<TargetFramework>net6.0</TargetFramework>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<ItemGroup>
<!-- Ensures build order, the DACPAC is _guaranteed_ to exist when included -->
<ProjectReference Include="..\Dacpac\Dacpac.sqlproj">
<!-- Companion to .sqlproj CopyBuildOutputToOutputDirectory=false -->
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<!-- Suppresses a warning about this project not being compatible with SQL project -->
<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<!-- Use `$(Configuration)` to reference the DACPAC output location regardless of build configuration. -->
<Content Include="$(ProjectDir)..\Dacpac\bin\$(Configuration)\Dacpac.dacpac" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
In Dacpac.sqlproj:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build">
<Sdk Name="Microsoft.Build.Sql" Version="0.1.3-preview" />
<PropertyGroup>
<!-- some properties redacted for brevity -->
<!-- Prevents outputting the DLL (some may need it) -->
<CopyBuildOutputToOutputDirectory>false</CopyBuildOutputToOutputDirectory>
<CopyOutputSymbolsToOutputDirectory>false</CopyOutputSymbolsToOutputDirectory>
<Name>Dacpac</Name>
<NetCoreBuild>True</NetCoreBuild>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
</Project>
Please note that <RestorePackagesWithLockFile /> is not strictly necessary but ensures dotnet restore --locked-mode works, which is a nicety for reproducible builds.

Related

MSBuild in Visual Studio - Moving files before including them as content (C#)

I have a C# project which has two C++ .dll versions with the same name but differ based on their architecture (32-bit or 64-bit), they are stored in separate folders and must have the same name, I want to include the right file with that name.
So the plan is to first check the current platform when building, copy the right file from the right folder (based on the platform) into the project directory and then include that file as content to the project so it can be used.
<ItemGroup>
<Source32Bit Include="File_32bit\File.dll" />
<Source64Bit Include="File_64bit\File.dll" />
</ItemGroup>
<Target Name="CopyFiles" BeforeTargets="Build" >
<Copy SourceFiles="#(Source32Bit)" DestinationFolder="$(ProjectDir)" Condition=" '$(Platform)' == 'x86' " />
<Copy SourceFiles="#(Source64Bit)" DestinationFolder="$(ProjectDir)" Condition=" '$(Platform)' == 'x64'" />
</Target>
<ItemGroup>
<Content Include="File.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
But if I run this then it tries to perform Content Include before the Target "CopyFiles" has run and so it cannot find the File.dll in that directory. If I make a target for this content include and try to do AfterTarget="CopyFiles" it complains about the CopyToOutputDirectory.
How should I handle this? Any ideas? Thanks!
But if I run this then it tries to perform Content Include before the
Target "CopyFiles" has run and so it cannot find the File.dll in that
directory. If I make a target for this content include and try to do
AfterTarget="CopyFiles" it complains about the CopyToOutputDirectory.
The main reason is that Build target executes later than MSBuild to read the Item elements, so when you perform the copy operation, it is already later than reading the Item elements, so the project cannot find the copied file. Therefore, you only need to execute the CopyFiles target before reading the Item elements.
So I found a system target called FindInvalidProjectReferences which executes earlier than reading Item elements.
Solution
Try these:
<ItemGroup>
<Source32Bit Include="File_32bit\File.dll" />
<Source64Bit Include="File_64bit\File.dll" />
</ItemGroup>
<Target Name="CopyFiles" BeforeTargets="FindInvalidProjectReferences">
<Copy SourceFiles="#(Source32Bit)" DestinationFolder="$(ProjectDir)" Condition=" '$(Platform)' == 'x86' " />
<Copy SourceFiles="#(Source64Bit)" DestinationFolder="$(ProjectDir)" Condition=" '$(Platform)' == 'x64'" />
</Target>
<ItemGroup>
<Content Include="File.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
Note: when you first load the project, the test.dll has a yellow triangle which is a normal behavior because you did not build your project. And it needs the build process first.
You should build your project first and it will not turn out any errors and when the test.dll in Solution Explorer will not have the warning symbol.
This answer works with Visual Studio 2022.
If you want to use globs (**/*) to pick up generated files as <Content>, then define the fileset and <Content> twice - once at the project level, and once again inside the target that generates the files.
The first time the project builds, the project-level <Content> picks up zero files, but the target-level <Content> picks up produced files.
For later builds, both <Content> elements pick up the files, and that's harmless.
<ItemGroup>
<MyOutputFiles Include="where\output\was\generated\**"/>
<Content Include="#(MyOutputFiles)" CopyToOutputDirectory="PreserveNewest" Link="specific\out\dir\%(RecursiveDir)%(Filename)%(Extension)"/>
</ItemGroup>
<Target Name="GenerateMyFilesPlz" BeforeTargets="BeforeBuild">
<Exec Command=""$(ProjectDir)..\TheTool\TheTool.exe" some args" />
<ItemGroup>
<MyOutputFiles Include="where\output\was\generated\**"/>
<Content Include="#(MyOutputFiles)" CopyToOutputDirectory="PreserveNewest" Link="specific\out\dir\%(RecursiveDir)%(Filename)%(Extension)"/>
</ItemGroup>
</Target>
This is particularly more desirable than FindInvalidProjectReferences because
it runs only when you build. (FindInvalidProjectReferences tends to be invoked by Visual Studio whenever it feels like it)
it makes reasonable error messages when it fails. (when BeforeTargets="FindInvalidProjectReferences" fails, you get a thousand errors like "I can't find Newtonsoft.Json!" because no project references were resolved.)
it works the same whether building via Visual Studio or via dotnet build on the command line. (I've had mixed results with FindInvalidProjectReferences)
it allows the use of globbing (**/*) to define the fileset
Can you rename the path in which the DLL files reside?
If instead of naming those folders File_32bit and File_64bit, you can name them File_x86 and File_x64.
Then you could have ItemGroup:
<ItemGroup>
<Source32Bit Include="File_$(Platform)\File.dll" />
<Source64Bit Include="File_$(Platform)\File.dll" />
</ItemGroup>
Note: I don't really know if this works.

Target .NET 4.x and .NET Standard 2.0

Last year I added .NET Standard 2.0 support to the Network library. I did achieve this by creating a second (.NET Standard) project, and basically copy + paste the sourcecode. With some adjustments it was ready to go.
But since I add features on demand, it is really bothersome to change the same thing in both projects. It would be great to just create one code-base and simply change the compile target.
Pre-Compile statements aren't an option, because the .NET 4.x version does additionally include some NuGet packages, which aren't available for .NET Standard.
The solution I can currently think of is, to create a shared library, including all the cross-project classes. Or is there a much smoother solution?
Solved the Problem with the suggested solution. The .csproj Looks like following
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net46;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<ItemGroup>
<Compile Remove="packages\**" />
<EmbeddedResource Remove="packages\**" />
<None Remove="packages\**" />
</ItemGroup>
<ItemGroup>
<!-- PackageReferences for all TargetFrameworks -->
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net46' ">
<!-- PackageReferences for net46 TargetFramework -->
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' ">
<!-- PackageReferences for standard2.0 TargetFramework -->
</ItemGroup>
</Project>
The only issue currently: I can't use the NuGet Package-Manager. I have to add every entry manually into the correct ItemGroup.
EDIT: The manual edit is only required if the packages are not supported by both TargetFrameworks. Simply Change in the Settings -> NuGet-Paket-Manager -> Default Format -> PackageReference
You can add source files from an existing project to another project as a link.
project a
somefile.cs
project b
Right click on project b, add existing item...navigate to somefile.cs in project a, and then add
You can edit the file from either project...so be careful.

Build configuration for dynamically referenced projects

I have an ASP.NET project that is included in multiple solutions. In each solution I'd like a different unreferenced project to be included in the ASP.NET project's build output. The solutions look like this:
Foo.sln
WebApp.csproj
Foo.csproj
Bar.sln
WebApp.csproj
Bar.csproj
Ideally, this would work even when debugging with F5. I tried doing this with build configurations, but deviating from the typical 'Debug' and 'Release' seems brittle when working within Visual Studio. Is there a typical way of doing this?
Disclaimer: I don't think this is a very good idea to do but it seems like it can be done.
To test this solution I created two projects. ConsoleApplication1 and ClassLibrary1. ConsoleApplication1 does not have a reference (that is visible in Visual Studio) to ClassLibary1 but when building ConsoleApplication1 from Visual Studio it will build then copy the ClassLibary1.dll to the bin folder of ConsoleApplication1.
To import the target file you will go ahead and add this line to the project that you want to build the unreferenced project. This path will be relative to the current project so in my case the target file was at the root of my solution. Make sure you add this after the line <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> because unreferenced.target relies on targets that are setup in Microsoft.CSharp.targets.
<Import Project="..\unreferenced.target" />
Then you will go ahead and create a file name unreferenced.target and add the contents below to the file.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Start another msbuild task to build your unreferenced project -->
<Target Name="BuildBeforeResolveReferences" BeforeTargets="BeforeResolveReferences">
<MSBuild
BuildInParallel="False"
Projects="$(SolutionDir)ClassLibrary1\ClassLibrary1.csproj"
RunEachTargetSeparately="True"
StopOnFirstFailure="False"
UnloadProjectsOnCompletion="False">
</MSBuild>
</Target>
<Target Name="CopyUnreferencedProjectOutput" AfterTargets="Build">
<!-- This item group is here because we do not want it evaluated by msbuild until the ClassLibrary1.csproj has been compiled and its output is in its output directory -->
<ItemGroup>
<!-- Gets a list of all files at the OutputPath that end in .dll if you need the pdbs remove the .dll -->
<!-- To maintain folder structure in the bin folder use <SourceFiles Include="..\ClassLibary1\#(OutputPath)**\*.dll" /> the double ** is a recursive wild card and will look through all directorys -->
<SourceFiles Include="$(MSBuildProjectDirectory)\..\ClassLibrary1\$(OutputPath)*.dll" />
</ItemGroup>
<!-- To make sure the copy maintains folder structure switch it to this copy -->
<!-- <Copy SourceFiles="#(SourceFiles)" DestinationFiles="#(SourceFiles -> '$(MSBuildProjectDirectory)$(OutputPath)%(RecursiveDir)%(Filename)%(Extension)')" /> -->
<Copy SourceFiles="#(SourceFiles)" DestinationFolder="$(MSBuildProjectDirectory)\$(OutputPath)" />
</Target>
<!-- Cleans up all the files when clean is called -->
<Target Name="CleanUnreferenceProjectOutput" BeforeTargets="Clean">
<ItemGroup>
<!-- Removed the .dll from the end of this to clean up the pdbs as well -->
<SourceFiles Include="$(SolutionDir)\ClassLibrary1\$(OutputPath)*" />
<SourceFiles Include="$(SolutionDir)\ConsoleApplication1\$(OutputPath)*.dll" />
</ItemGroup>
<Delete Files="#(SourceFiles)" />
</Target>
</Project>
I think this is the best that can be done. You could extend this to have a list of projects that are not referenced but you want to build but for this example I just left it at one.
EDIT 2: Before getting to the current solution I did extensive research into injecting the reference into the ProjectReference itemgroup before assemblies were resolved. It can be done but you have to set the property BuildInVisualStudio to false because otherwise when the msbuild conditions are evaluted in the ResolveProjectReferences target in Microsoft.Common.Current.targets you will select a MSBuild task that only runs the GetManifest target. I was able to get the solution to build but given my lack of knowledge on what setting BuildInVisualStudio to false entails I opted for the solution above. Also I added a task for cleaning up the files that were moved to the bin folders because clean will only cleanup what {ProjectName}{ProjectExtension}FileListAbsoluteText.txt in the obj folder of your project.
EDIT: After doing some more research into the solution below it will only work from the command line. I am currently looking into why this is occuring.
I don't know if there is a typical way of doing what you are asking for (from IDE), but you have an options to accomplish this manually by editing the *.*proj files.
Each project will emit output (*.dll, *.exe, app.config, etc), and it will be copied to the folder specified in the $(OutputPath) property (internally it will use OutDir property). If you will build a solution, you will have the $(SolutionDir) property, as well as $(SolutionName). So, you can define new msbuild project, which will be referenced by the other ones, and you can set the property $(OutputPath) so that every output will go into one folder (let call it Common.props):
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SolutionDir Condition=" '$(SolutionDir)' == '' ">$(MSBuildThisFileDirectory)<SolutionDir>
<SolutionName Condition=" '$(SolutionName)' == '' " >DefaultSlnName</SolutionName>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<OutputPath>$(SolutionDir)$(SolutionName)\bin\$(Configuration)</OutputPath>
</PropertyGroup>
</Project>
After that, you should import that project by your other projects - *.*proj (you should specify correct path to the project):
<Import Project="..\Common.props" Condition="Exists('..\Common.props')" />
Using common $(OutputPath) property will place all of your binaries to the one folder - this should help to resolve your task.

MSbuild and multiple projects with same class library references

I have a solution file with a class library and a couple of windows services. All the services have a project reference to the class library.
In my build file I'm building each service in release mode, zipping the files and copying the zip files to a webserver where our production servers can download the zip files from.
The problem is that only the first services is built and deployed. All others fail with a CSC : error CS0006: Metadata file "classlibrary.dll" could not be found.
I've made a test solution that reproduces the problem. One empty class library, and two empty console applications, both with references to the class library.
Is it because the temp directory is the same? It works fine with web projects and the same temp directory.
And then this build file.
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildCommunityTasksPath>$(MSBuildProjectDirectory)\Tools\MSBuildCommunityTasks</MSBuildCommunityTasksPath>
</PropertyGroup>
<Import Project="Tools\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
<PropertyGroup>
<ExtensionTasksPath>$(MSBuildProjectDirectory)\Tools\MSBuild.ExtensionPack\</ExtensionTasksPath>
</PropertyGroup>
<Import Project="Tools\MSBuild.ExtensionPack\MSBuild.ExtensionPack.tasks" />
<PropertyGroup>
<ProjectName>MSbuild test</ProjectName>
</PropertyGroup>
<Target Name="DeployAll" >
<Message Text="DeploymentPackage $(ProjectName)" />
<PropertyGroup>
<TempDeploymentDirectory>$(MSBuildProjectDirectory)\DeploymentPackage\</TempDeploymentDirectory>
<ProjectFile1>ConsoleApplication1\ConsoleApplication1.csproj</ProjectFile1>
<ProjectFile2>ConsoleApplication2\ConsoleApplication2.csproj</ProjectFile2>
</PropertyGroup>
<RemoveDir Directories="$(TempDeploymentDirectory)" />
<MSBuild Projects="$(ProjectFile1)" Properties="Configuration=Release;OutDir=$(TempDeploymentDirectory)" Targets="Clean;Build"/>
<!-- Zipping copying files removed for readability -->
<RemoveDir Directories="$(TempDeploymentDirectory)" />
<MSBuild Projects="$(ProjectFile2)" Properties="Configuration=Release;OutDir=$(TempDeploymentDirectory)" Targets="Clean;Build"/>
</Target>
</Project>
Building the classLibrary before each application worked for the demo project, but not for the real project.
The solution in my project, was to use a different output directory for each service.

MSBuild task does not cause generated c# files to compile

I've been trying to write an MSBuild task to "compile" CoCo/R .ATG files into C# files which will then get compiled into the executable, this is to replace the pre-build event.
I've managed to get the .ATG -> .cs process working, however something is not right since the generates .cs files do not get compiled.
If I then modify the .ATG file again, the "old" .cs files seems to get compiled then new ones generated.
I'm quite sure I'm missing something that will inform the rest of the build process that these files have changed.
Here is the target definition that I have included in my Visual Studio 2010 project.
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<AvailableItemName Include="BuildATG" />
</ItemGroup>
<PropertyGroup>
<CoreBuildDependsOn>
BuildATGTarget;
$(CoreBuildDependsOn)
</CoreBuildDependsOn>
</PropertyGroup>
<Target Name="BuildATGTarget" Inputs="#(BuildATG)" Outputs="#(BuildATG -> '%(RelativeDir)Parser.cs')">
<Exec Command="Coco.exe %(BuildATG.Identity)" Outputs="%(BuildATG.RelativeDir)Parser.cs" />
</Target>
</Project>
I am completly new to MSBuild, so any advice / pointers would be appreciated.
One possible solution I have found is to do the following changes and add an ItemGroup inside the target.
<Target Name="BuildATGTarget" Inputs="#(BuildATG)" Outputs="#(BuildATG -> '%(RelativeDir)Parser.cs')">
<Exec Command="Coco.exe %(BuildATG.Identity)" Outputs="%(BuildATG.RelativeDir)Parser.cs" />
<ItemGroup>
<Compile Include="%(BuildATG.RelativeDir)Parser.cs" />
<Compile Include="%(BuildATG.RelativeDir)Scanner.cs" />
</ItemGroup>
</Target>
To avoid duplicate files in your build, you also need to mark the files (Parser.cs and Scanner.cs) in the Visual Studio 2010 project with Build Action: None

Categories

Resources