7

This question might have been asked multiple times and maybe I'm just bad at using the search functions here aswell as google but I've not found an answer to this question as of yet.

I've currently got two projects, project X, project Y and project Z (which only holds the mappings)

Project X is a FluentNhibernate project which holds my sessionfactories and things like this. So this is also where the mappings are loaded and things (this is important and i suspect it might be the whole reason for the problem I'm having)

Now in Project X I've referenced an assembly that uses Microsoft.SqlServer.Types.dll.

Project Y is a service which uses project X for it's database connections.

All probjects build find and run perfectly on my development machine, however when deployed to our server they failed to function (runtime errors). The error was pretty obscure since it pointed to a missing FluentNHibernate assembly which was not the case.

Procmon.exe luckily showed the code trying to load the Microsoft.SqlServer.Types.dll which since the server does not have SQL Server installed (my client doesn't either but it has a management studio which most likely installs this .DLL aswell).

So far so good, copied the DLL over and it worked (yay!).

Now I figured i'd add the assembly to project X to make sure that this reference is copied over to other projects using project X. This didn't happen.... So i tried setting the 'Copy Local' setting to true.

Sadly this still does not copy the .dll to the referencing project Y.

Is there a way to make this happen or is this Visual Studio beeing clever and realizing Project Y doesn't need this .dll and thus refusing to copy it over?

(Since Project Z needs the actual reference and Project X loads this assembly @ run time there is not 'hard' reference between Z and X)

Could any Stackoverflow genius shed some light on this since honestly I'd like to know why it's behaving this way (and ideally a way to make it behave like I want it to).

Cœur
  • 32,421
  • 21
  • 173
  • 232
F.B. ten Kate
  • 1,972
  • 2
  • 19
  • 31

3 Answers3

7

This question has been answered already but I figured I'll include a generic fail-safe method for copying all indirect references into your projects' output directories.

  1. Set Copy Local to true for all references you want to be included with your output.
  2. Save the copy indirect dependencies code (see below) into a file with the name CopyIndirectDependencies.targets and place the targets file in your project directory.
  3. Edit your .csproj or .vbproj to include an import to the .targets file you added. See example below.
  4. Build your project and you should have secondary dependencies automatically copied to the build output.

CopyIndirectDependencies.targets File Content

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <CopyIndirectDependencies   
      Condition="'$(CopyIndirectDependencies)'==''">true</CopyIndirectDependencies>
    <CopyIndirectDependenciesPdb
      Condition="'$(CopyIndirectDependenciesPdb)'==''">false</CopyIndirectDependenciesPdb>
    <CopyIndirectDependenciesXml
      Condition="'$(CopyIndirectDependenciesXml)'==''">false</CopyIndirectDependenciesXml>
  </PropertyGroup>


  <!-- BuildXxx part -->

  <Target Name="CopyIndirectDependencies"
          Condition="'$(CopyIndirectDependencies)'=='true'"
          DependsOnTargets="DetectIndirectDependencies">
    <Copy Condition="'%(IndirectDependency.FullPath)'!=''"
          SourceFiles="%(IndirectDependency.FullPath)"
          DestinationFolder="$(OutputPath)"
          SkipUnchangedFiles="true" >
      <Output TaskParameter="CopiedFiles"
              ItemName="IndirectDependencyCopied" />
    </Copy>
    <Message Importance="low"
             Condition="'%(IndirectDependencyCopied.FullPath)'!=''
               and '%(IndirectDependencyCopied.Extension)'!='.pdb'
               and '%(IndirectDependencyCopied.Extension)'!='.xml'"
             Text="Indirect dependency copied: %(IndirectDependencyCopied.FullPath)" />
  </Target>

  <Target Name="DetectIndirectDependencies"
          DependsOnTargets="ResolveAssemblyReferences">

    <Message Importance="low"
             Text="Direct dependency: %(ReferencePath.Filename)%(ReferencePath.Extension)" />
    <Message Importance="low"
             Text="Indirect dependency: %(ReferenceDependencyPaths.Filename)%(ReferenceDependencyPaths.Extension)" />

    <!-- Creating indirect dependency list -->
    <CreateItem Include="%(ReferenceDependencyPaths.FullPath)"
                Condition="'%(ReferenceDependencyPaths.CopyLocal)'=='true'">
      <Output TaskParameter="Include"
              ItemName="_IndirectDependency"/>
    </CreateItem>
    <CreateItem Include="%(ReferenceDependencyPaths.RootDir)%(ReferenceDependencyPaths.Directory)%(ReferenceDependencyPaths.Filename).xml"
                Condition="'%(ReferenceDependencyPaths.CopyLocal)'=='true' and '$(CopyIndirectDependenciesXml)'=='true'">
      <Output TaskParameter="Include"
              ItemName="_IndirectDependency"/>
    </CreateItem>
    <CreateItem Include="%(ReferenceDependencyPaths.RootDir)%(ReferenceDependencyPaths.Directory)%(ReferenceDependencyPaths.Filename).pdb"
                Condition="'%(ReferenceDependencyPaths.CopyLocal)'=='true' and '$(CopyIndirectDependenciesPdb)'=='true'">
      <Output TaskParameter="Include"
              ItemName="_IndirectDependency"/>
    </CreateItem>

    <!-- Filtering indirect dependency list by existence -->
    <CreateItem Include="%(_IndirectDependency.FullPath)"
                Condition="Exists('%(_IndirectDependency.FullPath)')">
      <Output TaskParameter="Include"
              ItemName="IndirectDependency"/>
    </CreateItem>

    <!-- Creating copied indirect dependency list -->
    <CreateItem Include="@(_IndirectDependency->'$(OutputPath)%(Filename)%(Extension)')">
      <Output TaskParameter="Include"
              ItemName="_ExistingIndirectDependency"/>
    </CreateItem>

    <!-- Filtering copied indirect dependency list by existence -->
    <CreateItem Include="%(_ExistingIndirectDependency.FullPath)"
                Condition="Exists('%(_ExistingIndirectDependency.FullPath)')">
      <Output TaskParameter="Include"
              ItemName="ExistingIndirectDependency"/>
    </CreateItem>

  </Target>


  <!-- Build sequence modification -->

  <PropertyGroup>
    <CoreBuildDependsOn>
      $(CoreBuildDependsOn);
      CopyIndirectDependencies
    </CoreBuildDependsOn>
  </PropertyGroup>
</Project>

Sample project with import

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  ...

  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Import Project="CopyIndirectDependencies.targets" /> <!-- ADD THIS LINE!! -->

  ...

</Project>

Source: http://blog.alexyakunin.com/2009/09/making-msbuild-visual-studio-to.html

Alex Essilfie
  • 11,613
  • 9
  • 64
  • 103
  • Does it matter where the line in step 3 is added in the project file? For example, does it have to be under the AfterBuild target or somewhere else (or anywhere)? – longda Feb 11 '14 at 00:32
  • 4
    Hmm, this does not work in Visual Studio 2012. I hope I'm wrong. – longda Feb 11 '14 at 01:20
  • @longda: See the article I sourced this answer for. It has a sample project. – Alex Essilfie Feb 11 '14 at 06:40
  • I got Visual Studio 2012 to work without manual build target changes by changing CoreBuildDependsOn to ResolveReferencesDependsOn. There probably is a better equivalent – WhiteKnight Jun 17 '15 at 09:40
2

Now I figured i'd add the assembly to project X to make sure that this reference is copied over to other projects using project X. This didn't happen.... So i tried setting the 'Copy Local' setting to true.

You are still talking about the Microsoft.SqlServer.Types.dll right?

I had the same issues some time ago, actually sqlserver types should not be copied and redistributed with your installation. The correct way to "install" it is downloading the sql server runtime (which is included in the sql server management tools, that's why it works for you locally).

Because it is some time ago, I'm not 100% sure if the following information is correct and will work, but simply try to install the Microsoft® System CLR Types for Microsoft® SQL Server® 2012 only. On this download page expand Install Instructions and scroll down to the CLR Types download...

Use this link for SQL Server 2008

Install this on the target server. This will only install the stuff needed to run this types dll...

MichaC
  • 12,433
  • 2
  • 39
  • 53
  • I am still talking about Microsoft.SqlServer.Types.dll yes. But why would distributing it with your actual software be bad in this case? Is there some best practise thing at work here? – F.B. ten Kate Oct 02 '13 at 10:14
  • The types dll is just a wrapper around the dlls which are also used by SQL Server itself to do the spatial operations... those dlls are written in C/C++ and are not copied if you just copy over the types dll... you could of cause also copy SQLServerSpatial.dll (which is the native dll) – MichaC Oct 02 '13 at 10:26
  • Well I'm using these types in source code written by a third which i've altered to use fluent nhibernate with geo information and the sql server 2012 Dialect. Installing the CLR types per the information you provided seems to work however. So i'll do a few more tests and if those succeed i'll mark your answer. That said I doubt visual studio refuses to copy DLL's due to them beeing written in C/C++. The underlying issue still stands and i'd love an answer here however others that find their way here because of Microsoft.SqlServer.Types.dll should use the installer. – F.B. ten Kate Oct 02 '13 at 10:39
  • tbh I don't really get your problem with the copying of the dlls, maybe the best would be to ask a new question around this on SO with some clear explanations ;) Anyways, you can set the output bin folder of all your projects to a common place, e.g. ..\Bin instead of Debug\Bin... This would copy all dlls in one place. For distributing stuff, if you have 3rd party assemblies and dependencies, I think the best is to build a setup project, otherwise you might miss other dependencies. You can also manually distribute both sql types dlls by adding it to a setup I guess (never tried it) – MichaC Oct 02 '13 at 10:53
0

May be Microsoft's answer will be usefull: https://connect.microsoft.com/VisualStudio/feedback/details/652785/visual-studio-does-not-copy-referenced-assemblies-through-the-reference-hierarchy

takrl
  • 6,098
  • 3
  • 55
  • 64
Subtle Fox
  • 564
  • 5
  • 7
  • 3
    That link is now dead. This is the very reason it is preferred to at least copy the solution from the link and not just post the link itself. – Krzaku Oct 25 '18 at 17:59