From 70e56c901909a90758615169f46d3ae51c555a48 Mon Sep 17 00:00:00 2001 From: Tristan Barcelon Date: Fri, 30 Apr 2021 13:29:44 -0400 Subject: [PATCH 1/4] Remove non-existent sample dacpac projects, change targetplatform to x64, and migrate nuget packages to PackageReference --- src/MergeDacpac.sln | 66 +++++++---------- src/MergeEm/MergeEm.csproj | 142 ++++++++++++++++-------------------- src/MergeEm/packages.config | 6 -- 3 files changed, 87 insertions(+), 127 deletions(-) delete mode 100644 src/MergeEm/packages.config diff --git a/src/MergeDacpac.sln b/src/MergeDacpac.sln index 81600e8..64af3f5 100644 --- a/src/MergeDacpac.sln +++ b/src/MergeDacpac.sln @@ -1,41 +1,25 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26403.7 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "MergeDacpac", "P1\MergeDacpac.sqlproj", "{011776C2-C6CF-45C0-AE4E-12CD7949CCF8}" -EndProject -Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "MergeDacpac2", "P2\MergeDacpac2.sqlproj", "{873AD76B-1A1F-4355-BDF0-310DAA6FD27C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MergeEm", "MergeEm\MergeEm.csproj", "{8CF00550-FA7D-47D3-B62F-0FCA322F57FF}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {011776C2-C6CF-45C0-AE4E-12CD7949CCF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {011776C2-C6CF-45C0-AE4E-12CD7949CCF8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {011776C2-C6CF-45C0-AE4E-12CD7949CCF8}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {011776C2-C6CF-45C0-AE4E-12CD7949CCF8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {011776C2-C6CF-45C0-AE4E-12CD7949CCF8}.Release|Any CPU.Build.0 = Release|Any CPU - {011776C2-C6CF-45C0-AE4E-12CD7949CCF8}.Release|Any CPU.Deploy.0 = Release|Any CPU - {873AD76B-1A1F-4355-BDF0-310DAA6FD27C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {873AD76B-1A1F-4355-BDF0-310DAA6FD27C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {873AD76B-1A1F-4355-BDF0-310DAA6FD27C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {873AD76B-1A1F-4355-BDF0-310DAA6FD27C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {873AD76B-1A1F-4355-BDF0-310DAA6FD27C}.Release|Any CPU.Build.0 = Release|Any CPU - {873AD76B-1A1F-4355-BDF0-310DAA6FD27C}.Release|Any CPU.Deploy.0 = Release|Any CPU - {8CF00550-FA7D-47D3-B62F-0FCA322F57FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8CF00550-FA7D-47D3-B62F-0FCA322F57FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8CF00550-FA7D-47D3-B62F-0FCA322F57FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8CF00550-FA7D-47D3-B62F-0FCA322F57FF}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {AF9A6E26-DAA8-4ADF-93DA-0F77D6DCBB1E} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31205.134 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MergeEm", "MergeEm\MergeEm.csproj", "{8CF00550-FA7D-47D3-B62F-0FCA322F57FF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8CF00550-FA7D-47D3-B62F-0FCA322F57FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CF00550-FA7D-47D3-B62F-0FCA322F57FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CF00550-FA7D-47D3-B62F-0FCA322F57FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CF00550-FA7D-47D3-B62F-0FCA322F57FF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AF9A6E26-DAA8-4ADF-93DA-0F77D6DCBB1E} + EndGlobalSection +EndGlobal diff --git a/src/MergeEm/MergeEm.csproj b/src/MergeEm/MergeEm.csproj index cfe8262..94f7b8d 100644 --- a/src/MergeEm/MergeEm.csproj +++ b/src/MergeEm/MergeEm.csproj @@ -1,81 +1,63 @@ - - - - - Debug - AnyCPU - {8CF00550-FA7D-47D3-B62F-0FCA322F57FF} - Exe - MergeEm - MergeEm - v4.7.2 - 512 - true - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.SqlServer.DacFx.150.4573.2\lib\net46\Microsoft.Data.Tools.Schema.Sql.dll - - - ..\packages\Microsoft.SqlServer.DacFx.150.4573.2\lib\net46\Microsoft.Data.Tools.Utilities.dll - - - ..\packages\Microsoft.SqlServer.DacFx.150.4573.2\lib\net46\Microsoft.SqlServer.Dac.dll - - - ..\packages\Microsoft.SqlServer.DacFx.150.4573.2\lib\net46\Microsoft.SqlServer.Dac.Extensions.dll - - - ..\packages\Microsoft.SqlServer.DacFx.150.4573.2\lib\net46\Microsoft.SqlServer.TransactSql.ScriptDom.dll - - - ..\packages\Microsoft.SqlServer.DacFx.150.4573.2\lib\net46\Microsoft.SqlServer.Types.dll - - - - - ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll - True - - - ..\packages\System.IO.Packaging.4.7.0\lib\net46\System.IO.Packaging.dll - True - - - - - - - - - - - - - - - - - - + + + + + Debug + AnyCPU + {8CF00550-FA7D-47D3-B62F-0FCA322F57FF} + Exe + MergeEm + MergeEm + v4.7.2 + 512 + true + + + + x64 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x64 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + 150.5084.2 + + + 5.0.0 + + + \ No newline at end of file diff --git a/src/MergeEm/packages.config b/src/MergeEm/packages.config deleted file mode 100644 index 5c40d23..0000000 --- a/src/MergeEm/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From ca346c3a05f6bd7423a028de74f6e93ea7d84ee8 Mon Sep 17 00:00:00 2001 From: Tristan Barcelon Date: Fri, 30 Apr 2021 22:23:06 -0400 Subject: [PATCH 2/4] Add console output so we have an idea what it's doing. Return exitcode so we can use it in powershell to detect errors. --- src/MergeEm/Program.cs | 68 ++++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/src/MergeEm/Program.cs b/src/MergeEm/Program.cs index 21e269d..e451384 100644 --- a/src/MergeEm/Program.cs +++ b/src/MergeEm/Program.cs @@ -15,17 +15,35 @@ class Program { static void Main(string[] args) { - if(args.Length < 3 || args.Any(p => p == "/?") || args.Any(p => p == "-?") || args.Any(p => p == "/help") || args.Any(p => p == "--help")) + int returnCode = 0; + + try { - Console.WriteLine("you need at least three args - targetDacPac (which will be created) sourceDacpac sourceDacpac"); - return; - } + if (args.Length < 3 || args.Any(p => p == "/?") || args.Any(p => p == "-?") || args.Any(p => p == "/help") || args.Any(p => p == "--help")) + { + Console.WriteLine("you need at least three args - targetDacPac (which will be created) sourceDacpac sourceDacpac"); + returnCode = 1; + } + + var stopwatch = new System.Diagnostics.Stopwatch(); - var target = args.First(); - var sources = args.Skip(1).ToArray(); + stopwatch.Start(); - var merger = new DacpacMerge(args[0], sources); - merger.Merge(); + var target = args.First(); + var sources = args.Skip(1).ToArray(); + + var merger = new DacpacMerge(args[0], sources); + merger.Merge(); + + stopwatch.Stop(); + + Environment.Exit(returnCode); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + Environment.Exit(e.HResult); + } } } @@ -40,7 +58,7 @@ class DacpacMerge public DacpacMerge(string target, params string[] sources) { _sources = sources; - _first = new TSqlModel(sources.First()); + _first = new TSqlModel(sources.First()); var options = _first.CopyModelOptions(); _target = new TSqlModel(_first.Version, options); @@ -54,20 +72,28 @@ public void Merge() foreach (var source in _sources) { + if (!File.Exists(source)) + { + Console.WriteLine("File {0} does not exist and is being skipped.", source); + continue; + } + + Console.WriteLine("{0} : Processing dacpac {1}", DateTimeOffset.Now.ToString("o", System.Globalization.CultureInfo.InvariantCulture), source); + var model = getModel(source); - foreach(var obj in model.GetObjects(DacQueryScopes.UserDefined)) + foreach (var obj in model.GetObjects(DacQueryScopes.UserDefined)) { TSqlScript ast; - if(obj.TryGetAst(out ast)) + if (obj.TryGetAst(out ast)) { var name = obj.Name.ToString(); var info = obj.GetSourceInformation(); - if(info != null && !string.IsNullOrWhiteSpace(info.SourceName)) + if (info != null && !string.IsNullOrWhiteSpace(info.SourceName)) { name = info.SourceName; } - if(!string.IsNullOrWhiteSpace(name) && !name.EndsWith(".xsd")) + if (!string.IsNullOrWhiteSpace(name) && !name.EndsWith(".xsd")) { _target.AddOrUpdateObjects(ast, name, new TSqlObjectOptions()); //WARNING throwing away ansi nulls and quoted identifiers! } @@ -76,11 +102,11 @@ public void Merge() using (var package = DacPackage.Load(source)) { - if(!(package.PreDeploymentScript is null)) + if (!(package.PreDeploymentScript is null)) { pre += new StreamReader(package.PreDeploymentScript).ReadToEnd(); } - if(!(package.PostDeploymentScript is null)) + if (!(package.PostDeploymentScript is null)) { post += new StreamReader(package.PostDeploymentScript).ReadToEnd(); } @@ -97,6 +123,7 @@ private void WriteFinalDacpac(TSqlModel model, string preScript, string postScri DacPackageExtensions.BuildPackage(_targetPath, model, metadata); AddScripts(preScript, postScript, _targetPath); + model.Dispose(); } TSqlModel getModel(string source) @@ -106,7 +133,16 @@ TSqlModel getModel(string source) return _first; } - return new TSqlModel(source); + try + { + return new TSqlModel(source); + } + catch (DacModelException e) when (e.Message.Contains("Required references are missing.")) + { + throw new DacModelException("Failed to load model from DACPAC. " + + "A reason might be that the \"SuppressMissingDependenciesErrors\" isn't set to 'true' consistently. ", + e); + } } private void AddScripts(string pre, string post, string dacpacPath) From 1ae2cfff922a4fe5b334fc71e75e7559794a5f4e Mon Sep 17 00:00:00 2001 From: Tristan Barcelon Date: Tue, 4 May 2021 10:59:29 -0400 Subject: [PATCH 3/4] minor logging enhancements and nuget package update --- src/MergeEm/MergeEm.csproj | 2 +- src/MergeEm/Program.cs | 353 +++++++++++++++++++------------------ 2 files changed, 182 insertions(+), 173 deletions(-) diff --git a/src/MergeEm/MergeEm.csproj b/src/MergeEm/MergeEm.csproj index 94f7b8d..97f59fd 100644 --- a/src/MergeEm/MergeEm.csproj +++ b/src/MergeEm/MergeEm.csproj @@ -52,7 +52,7 @@ - + 150.5084.2 diff --git a/src/MergeEm/Program.cs b/src/MergeEm/Program.cs index e451384..aadc853 100644 --- a/src/MergeEm/Program.cs +++ b/src/MergeEm/Program.cs @@ -1,175 +1,184 @@ -using Microsoft.SqlServer.Dac; -using Microsoft.SqlServer.Dac.Model; -using Microsoft.SqlServer.TransactSql.ScriptDom; -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Packaging; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MergeEm -{ - class Program - { - static void Main(string[] args) - { - int returnCode = 0; - - try - { - if (args.Length < 3 || args.Any(p => p == "/?") || args.Any(p => p == "-?") || args.Any(p => p == "/help") || args.Any(p => p == "--help")) - { - Console.WriteLine("you need at least three args - targetDacPac (which will be created) sourceDacpac sourceDacpac"); - returnCode = 1; - } - - var stopwatch = new System.Diagnostics.Stopwatch(); - - stopwatch.Start(); - - var target = args.First(); - var sources = args.Skip(1).ToArray(); - - var merger = new DacpacMerge(args[0], sources); - merger.Merge(); - - stopwatch.Stop(); - - Environment.Exit(returnCode); - } - catch (Exception e) +using Microsoft.SqlServer.Dac; +using Microsoft.SqlServer.Dac.Model; +using Microsoft.SqlServer.TransactSql.ScriptDom; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Packaging; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Diagnostics; + +namespace MergeEm +{ + class Program + { + static void Main(string[] args) + { + int returnCode = 0; + + try + { + if (args.Length < 3 || args.Any(p => p == "/?") || args.Any(p => p == "-?") || args.Any(p => p == "/help") || args.Any(p => p == "--help")) + { + Console.WriteLine("you need at least three args - targetDacPac (which will be created) sourceDacpac sourceDacpac"); + returnCode = 1; + } + + var stopwatch = new Stopwatch(); + + stopwatch.Start(); + + var target = args.First(); + var sources = args.Skip(1).ToArray(); + + var merger = new DacpacMerge(args[0], sources); + merger.Merge(); + + stopwatch.Stop(); + + Console.WriteLine("Completed merging {0} dacpacs in {1} seconds.", args.Length - 1, stopwatch.Elapsed.TotalSeconds); + Environment.Exit(returnCode); + } + catch (Exception e) { Console.WriteLine(e.Message); Environment.Exit(e.HResult); - } - } - } - - class DacpacMerge - { - - private string[] _sources; - private TSqlModel _first; - private string _targetPath; - private TSqlModel _target; - - public DacpacMerge(string target, params string[] sources) - { - _sources = sources; - _first = new TSqlModel(sources.First()); - var options = _first.CopyModelOptions(); - - _target = new TSqlModel(_first.Version, options); - _targetPath = target; - } - - public void Merge() - { - var pre = String.Empty; - var post = String.Empty; - - foreach (var source in _sources) - { - if (!File.Exists(source)) - { - Console.WriteLine("File {0} does not exist and is being skipped.", source); - continue; - } - - Console.WriteLine("{0} : Processing dacpac {1}", DateTimeOffset.Now.ToString("o", System.Globalization.CultureInfo.InvariantCulture), source); - - var model = getModel(source); - foreach (var obj in model.GetObjects(DacQueryScopes.UserDefined)) - { - TSqlScript ast; - if (obj.TryGetAst(out ast)) - { - var name = obj.Name.ToString(); - var info = obj.GetSourceInformation(); - if (info != null && !string.IsNullOrWhiteSpace(info.SourceName)) - { - name = info.SourceName; - } - - if (!string.IsNullOrWhiteSpace(name) && !name.EndsWith(".xsd")) - { - _target.AddOrUpdateObjects(ast, name, new TSqlObjectOptions()); //WARNING throwing away ansi nulls and quoted identifiers! - } - } - } - - using (var package = DacPackage.Load(source)) - { - if (!(package.PreDeploymentScript is null)) - { - pre += new StreamReader(package.PreDeploymentScript).ReadToEnd(); - } - if (!(package.PostDeploymentScript is null)) - { - post += new StreamReader(package.PostDeploymentScript).ReadToEnd(); - } - } - } - - WriteFinalDacpac(_target, pre, post); - } - - private void WriteFinalDacpac(TSqlModel model, string preScript, string postScript) - { - var metadata = new PackageMetadata(); - metadata.Name = "dacpac"; - - DacPackageExtensions.BuildPackage(_targetPath, model, metadata); - AddScripts(preScript, postScript, _targetPath); - model.Dispose(); - } - - TSqlModel getModel(string source) - { - if (source == _sources.FirstOrDefault()) - { - return _first; - } - - try - { - return new TSqlModel(source); - } - catch (DacModelException e) when (e.Message.Contains("Required references are missing.")) - { - throw new DacModelException("Failed to load model from DACPAC. " - + "A reason might be that the \"SuppressMissingDependenciesErrors\" isn't set to 'true' consistently. ", - e); - } - } - - private void AddScripts(string pre, string post, string dacpacPath) - { - using (var package = Package.Open(dacpacPath, FileMode.Open, FileAccess.ReadWrite)) - { - if (!string.IsNullOrEmpty(pre)) - { - var part = package.CreatePart(new Uri("/predeploy.sql", UriKind.Relative), "text/plain"); - - using (var stream = part.GetStream()) - { - stream.Write(Encoding.UTF8.GetBytes(pre), 0, pre.Length); - } - } - - if (!string.IsNullOrEmpty(post)) - { - var part = package.CreatePart(new Uri("/postdeploy.sql", UriKind.Relative), "text/plain"); - - using (var stream = part.GetStream()) - { - stream.Write(Encoding.UTF8.GetBytes(post), 0, post.Length); - } - } - package.Close(); - } - } - } -} + } + } + } + + class DacpacMerge + { + + private string[] _sources; + private TSqlModel _first; + private string _targetPath; + private TSqlModel _target; + + public DacpacMerge(string target, params string[] sources) + { + _sources = sources; + _targetPath = target; + } + + public void Merge() + { + var pre = String.Empty; + var post = String.Empty; + + foreach (var source in _sources) + { + + if (!File.Exists(source)) + { + Console.WriteLine("File {0} does not exist and is being skipped.", source); + continue; + } + + Console.WriteLine("{0} : Processing dacpac {1}", DateTimeOffset.Now.ToString("o", System.Globalization.CultureInfo.InvariantCulture), source); + + if (source == _sources.First()) + { + Console.WriteLine("{0}: Copying dacpac options from {1} to {2}", DateTimeOffset.Now.ToString("o", System.Globalization.CultureInfo.InvariantCulture), source, _targetPath); + + _first = new TSqlModel(_sources.First()); + var options = _first.CopyModelOptions(); + + _target = new TSqlModel(_first.Version, options); + } + + var model = getModel(source); + foreach (var obj in model.GetObjects(DacQueryScopes.UserDefined)) + { + TSqlScript ast; + if (obj.TryGetAst(out ast)) + { + var name = obj.Name.ToString(); + var info = obj.GetSourceInformation(); + if (info != null && !string.IsNullOrWhiteSpace(info.SourceName)) + { + name = info.SourceName; + } + + if (!string.IsNullOrWhiteSpace(name) && !name.EndsWith(".xsd")) + { + _target.AddOrUpdateObjects(ast, name, new TSqlObjectOptions()); //WARNING throwing away ansi nulls and quoted identifiers! + } + } + } + + using (var package = DacPackage.Load(source)) + { + if (!(package.PreDeploymentScript is null)) + { + pre += new StreamReader(package.PreDeploymentScript).ReadToEnd(); + } + if (!(package.PostDeploymentScript is null)) + { + post += new StreamReader(package.PostDeploymentScript).ReadToEnd(); + } + } + } + + WriteFinalDacpac(_target, pre, post); + } + + private void WriteFinalDacpac(TSqlModel model, string preScript, string postScript) + { + var metadata = new PackageMetadata(); + metadata.Name = "dacpac"; + + DacPackageExtensions.BuildPackage(_targetPath, model, metadata); + AddScripts(preScript, postScript, _targetPath); + model.Dispose(); + } + + TSqlModel getModel(string source) + { + if (source == _sources.FirstOrDefault()) + { + return _first; + } + + try + { + return new TSqlModel(source); + } + catch (DacModelException e) when (e.Message.Contains("Required references are missing.")) + { + throw new DacModelException("Failed to load model from DACPAC. " + + "A reason might be that the \"SuppressMissingDependenciesErrors\" isn't set to 'true' consistently. ", + e); + } + } + + private void AddScripts(string pre, string post, string dacpacPath) + { + using (var package = Package.Open(dacpacPath, FileMode.Open, FileAccess.ReadWrite)) + { + if (!string.IsNullOrEmpty(pre)) + { + var part = package.CreatePart(new Uri("/predeploy.sql", UriKind.Relative), "text/plain"); + + using (var stream = part.GetStream()) + { + stream.Write(Encoding.UTF8.GetBytes(pre), 0, pre.Length); + } + } + + if (!string.IsNullOrEmpty(post)) + { + var part = package.CreatePart(new Uri("/postdeploy.sql", UriKind.Relative), "text/plain"); + + using (var stream = part.GetStream()) + { + stream.Write(Encoding.UTF8.GetBytes(post), 0, post.Length); + } + } + package.Close(); + } + } + } +} From 119ae85436cf3345025c3d54774214c60a9253ab Mon Sep 17 00:00:00 2001 From: Gabriel Barrios Date: Wed, 5 May 2021 22:11:19 -0400 Subject: [PATCH 4/4] Adding the custom data headers to the resulting dacpac --- src/GOEddie.Dacpac.References/CustomData.cs | 29 +++ .../DacHacFactory.cs | 10 + src/GOEddie.Dacpac.References/DacpacXml.cs | 57 +++++ .../GOEddie.Dacpac.References.csproj | 62 ++++++ .../GOEddie.Dacpac.References.sln | 33 +++ src/GOEddie.Dacpac.References/HeaderParser.cs | 56 +++++ src/GOEddie.Dacpac.References/HeaderWriter.cs | 195 ++++++++++++++++++ src/GOEddie.Dacpac.References/Metadata.cs | 14 ++ .../ModelChecksumWriter.cs | 58 ++++++ .../Properties/AssemblyInfo.cs | 36 ++++ src/GOEddie.Dacpac.References/Reference.cs | 10 + .../ReferenceBuilder.cs | 57 +++++ src/MergeDacpac.sln | 6 + src/MergeEm/MergeEm.csproj | 9 + src/MergeEm/Program.cs | 61 +++++- 15 files changed, 690 insertions(+), 3 deletions(-) create mode 100644 src/GOEddie.Dacpac.References/CustomData.cs create mode 100644 src/GOEddie.Dacpac.References/DacHacFactory.cs create mode 100644 src/GOEddie.Dacpac.References/DacpacXml.cs create mode 100644 src/GOEddie.Dacpac.References/GOEddie.Dacpac.References.csproj create mode 100644 src/GOEddie.Dacpac.References/GOEddie.Dacpac.References.sln create mode 100644 src/GOEddie.Dacpac.References/HeaderParser.cs create mode 100644 src/GOEddie.Dacpac.References/HeaderWriter.cs create mode 100644 src/GOEddie.Dacpac.References/Metadata.cs create mode 100644 src/GOEddie.Dacpac.References/ModelChecksumWriter.cs create mode 100644 src/GOEddie.Dacpac.References/Properties/AssemblyInfo.cs create mode 100644 src/GOEddie.Dacpac.References/Reference.cs create mode 100644 src/GOEddie.Dacpac.References/ReferenceBuilder.cs diff --git a/src/GOEddie.Dacpac.References/CustomData.cs b/src/GOEddie.Dacpac.References/CustomData.cs new file mode 100644 index 0000000..19d1935 --- /dev/null +++ b/src/GOEddie.Dacpac.References/CustomData.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Linq; + +namespace GOEddie.Dacpac.References +{ + public class CustomData + { + private readonly Dictionary _items = new Dictionary(); + public readonly string Category; + public readonly string Type; + public List RequiredSqlCmdVars = new List(); + + public CustomData(string category, string type) + { + Category = category; + Type = type; + } + + public List Items + { + get { return _items.Values.ToList(); } + } + + public void AddMetadata(string name, string value) + { + _items[name] = new Metadata(name, value); + } + } +} \ No newline at end of file diff --git a/src/GOEddie.Dacpac.References/DacHacFactory.cs b/src/GOEddie.Dacpac.References/DacHacFactory.cs new file mode 100644 index 0000000..50a9254 --- /dev/null +++ b/src/GOEddie.Dacpac.References/DacHacFactory.cs @@ -0,0 +1,10 @@ +namespace GOEddie.Dacpac.References +{ + public class DacHacFactory + { + public DacHacXml Build(string path) + { + return new DacHacXml(path); + } + } +} \ No newline at end of file diff --git a/src/GOEddie.Dacpac.References/DacpacXml.cs b/src/GOEddie.Dacpac.References/DacpacXml.cs new file mode 100644 index 0000000..74106c3 --- /dev/null +++ b/src/GOEddie.Dacpac.References/DacpacXml.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; +using System.IO.Packaging; +using System.Text; + +namespace GOEddie.Dacpac.References +{ + public class DacHacXml : IDisposable + { + private readonly string _dacPath; + private Package _package; + + public DacHacXml(string dacPath) + { + _dacPath = dacPath; + _package = Package.Open(dacPath, FileMode.Open, FileAccess.ReadWrite); + } + + public void Dispose() + { + Close(); + } + + public string GetXml(string fileName) + { + var part = _package.GetPart(new Uri(string.Format("/{0}", fileName), UriKind.Relative)); + var stream = part.GetStream(); + + return new StreamReader(stream).ReadToEnd(); + } + + public Stream GetStream(string fileName) + { + var part = _package.GetPart(new Uri(string.Format("/{0}", fileName), UriKind.Relative)); + var stream = part.GetStream(); + return stream; + } + + public void SetXml(string fileName, string xml) + { + var part = _package.GetPart(new Uri(string.Format("/{0}", fileName), UriKind.Relative)); + var stream = part.GetStream(); + + var bytes = Encoding.ASCII.GetBytes(xml); + stream.SetLength(bytes.Length); + stream.Write(bytes, 0, bytes.Length); + + _package.Close(); + _package = Package.Open(_dacPath, FileMode.Open, FileAccess.ReadWrite); + } + + public void Close() + { + _package.Close(); + } + } +} \ No newline at end of file diff --git a/src/GOEddie.Dacpac.References/GOEddie.Dacpac.References.csproj b/src/GOEddie.Dacpac.References/GOEddie.Dacpac.References.csproj new file mode 100644 index 0000000..9f25756 --- /dev/null +++ b/src/GOEddie.Dacpac.References/GOEddie.Dacpac.References.csproj @@ -0,0 +1,62 @@ + + + + + Debug + AnyCPU + {1622EB7A-0902-417C-9673-97BC13DC2DAF} + Library + Properties + GOEddie.Dacpac.References + GOEddie.Dacpac.References + v4.5.1 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GOEddie.Dacpac.References/GOEddie.Dacpac.References.sln b/src/GOEddie.Dacpac.References/GOEddie.Dacpac.References.sln new file mode 100644 index 0000000..0a585b6 --- /dev/null +++ b/src/GOEddie.Dacpac.References/GOEddie.Dacpac.References.sln @@ -0,0 +1,33 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GOEddie.Dacpac.References", "GOEddie.Dacpac.References.csproj", "{1622EB7A-0902-417C-9673-97BC13DC2DAF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{4540CC1D-4176-43D4-98B4-85E1E875307D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DacpacHeaderParser.Tests", "..\DacpacHeaderParser.Tests\DacpacHeaderParser.Tests.csproj", "{8F0270E8-951E-4338-A661-277EAD18E8D4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1622EB7A-0902-417C-9673-97BC13DC2DAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1622EB7A-0902-417C-9673-97BC13DC2DAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1622EB7A-0902-417C-9673-97BC13DC2DAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1622EB7A-0902-417C-9673-97BC13DC2DAF}.Release|Any CPU.Build.0 = Release|Any CPU + {8F0270E8-951E-4338-A661-277EAD18E8D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F0270E8-951E-4338-A661-277EAD18E8D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F0270E8-951E-4338-A661-277EAD18E8D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F0270E8-951E-4338-A661-277EAD18E8D4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {8F0270E8-951E-4338-A661-277EAD18E8D4} = {4540CC1D-4176-43D4-98B4-85E1E875307D} + EndGlobalSection +EndGlobal diff --git a/src/GOEddie.Dacpac.References/HeaderParser.cs b/src/GOEddie.Dacpac.References/HeaderParser.cs new file mode 100644 index 0000000..0348877 --- /dev/null +++ b/src/GOEddie.Dacpac.References/HeaderParser.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.IO; +using System.Xml; + +namespace GOEddie.Dacpac.References +{ + public class HeaderParser + { + private readonly string _dacPacPath; + + public HeaderParser(string dacPacPath) + { + _dacPacPath = dacPacPath; + } + + public List GetCustomData() + { + var dac = new DacHacXml(_dacPacPath); + var xml = dac.GetXml("Model.xml"); + + var reader = XmlReader.Create(new StringReader(xml)); + reader.MoveToContent(); + + var data = new List(); + CustomData currentCustomData = null; + + while (reader.Read()) + { + if (reader.Name == "CustomData" && reader.NodeType == XmlNodeType.Element) + { + var cat = reader.GetAttribute("Category"); + var type = reader.GetAttribute("Type"); + + currentCustomData = new CustomData(cat, type); + data.Add(currentCustomData); + } + + if (reader.Name == "Metadata" && reader.NodeType == XmlNodeType.Element) + { + var name = reader.GetAttribute("Name"); + var value = reader.GetAttribute("Value"); + + currentCustomData.AddMetadata(name, value); + } + + if (reader.Name == "Header" && reader.NodeType == XmlNodeType.EndElement) + { + break; //gone too far + } + } + dac.Close(); + + return data; + } + } +} \ No newline at end of file diff --git a/src/GOEddie.Dacpac.References/HeaderWriter.cs b/src/GOEddie.Dacpac.References/HeaderWriter.cs new file mode 100644 index 0000000..4b4da87 --- /dev/null +++ b/src/GOEddie.Dacpac.References/HeaderWriter.cs @@ -0,0 +1,195 @@ +using System.Collections.Generic; +using System.IO; +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; + +namespace GOEddie.Dacpac.References +{ + public class HeaderWriter + { + private readonly bool _autoCommitEveryOperation; + private readonly DacHacXml _dac; + private readonly string _dacpacPath; + private string _xml; + + public HeaderWriter(string dacpacPath, DacHacFactory factory, bool autoCommitEveryOperation = true) + { + _dacpacPath = dacpacPath; + _autoCommitEveryOperation = autoCommitEveryOperation; + _dac = factory.Build(_dacpacPath); + _xml = _dac.GetXml("Model.xml"); + } + + public void AddCustomData(CustomData data) + { + AddCustomData(data.Category, data.Type, data.Items); + EnsureSqlVarsWritten(data); + } + + private void EnsureSqlVarsWritten(CustomData data) + { + if (data.RequiredSqlCmdVars.Count == 0) + return; + + var haveChanges = false; + XElement rootNode; + var customData = GetOrAddCustomDataNode("SqlCmdVariables", "SqlCmdVariable", out rootNode); + var reader = XmlReader.Create(new StringReader(_xml)); + var namespaceManager = new XmlNamespaceManager(reader.NameTable); + var ns = XNamespace.Get("http://schemas.microsoft.com/sqlserver/dac/Serialization/2012/02"); + namespaceManager.AddNamespace("p", "http://schemas.microsoft.com/sqlserver/dac/Serialization/2012/02"); + + + foreach (var cmdVar in data.RequiredSqlCmdVars) + { + if ( + customData.XPathSelectElement(string.Format("/p:MetaData[@Name='{0}']", cmdVar), namespaceManager) != + null) + { + continue; + } + + var newMetaNode = new XElement(ns + "Metadata"); + newMetaNode.SetAttributeValue("Name", cmdVar); + newMetaNode.SetAttributeValue("Value", ""); + customData.Add(newMetaNode); + haveChanges = true; + } + + + if (haveChanges) + { + _xml = rootNode.ToString(SaveOptions.None); + + if (_autoCommitEveryOperation) + CommitChanges(); + } + } + + private XElement GetOrAddCustomDataNode(string categoryName, string type, out XElement root + /*root doc must be saved*/) + { + var reader = XmlReader.Create(new StringReader(_xml)); + var element = XElement.Load(reader); + var namespaceManager = new XmlNamespaceManager(reader.NameTable); + namespaceManager.AddNamespace("p", "http://schemas.microsoft.com/sqlserver/dac/Serialization/2012/02"); + + var ns = XNamespace.Get("http://schemas.microsoft.com/sqlserver/dac/Serialization/2012/02"); + + var node = element.XPathSelectElement("//p:Header/p:CustomData[@Category='SqlCmdVariables']", namespaceManager); + if (node != null) + { + root = element; + return node; + } + var header = element.XPathSelectElement("//p:Header", namespaceManager); + + var newNode = new XElement(ns + "CustomData"); + newNode.SetAttributeValue("Category", categoryName); + + if (!string.IsNullOrEmpty(type)) + newNode.SetAttributeValue("Type", type); + + header.Add(newNode); + root = element; + return newNode; + } + + private void AddCustomData(string categoryName, string type, IEnumerable metadatas) + { + var reader = XmlReader.Create(new StringReader(_xml)); + var element = XElement.Load(reader); + var namespaceManager = new XmlNamespaceManager(reader.NameTable); + namespaceManager.AddNamespace("p", "http://schemas.microsoft.com/sqlserver/dac/Serialization/2012/02"); + + var ns = XNamespace.Get("http://schemas.microsoft.com/sqlserver/dac/Serialization/2012/02"); + + var header = element.XPathSelectElement("//p:Header", namespaceManager); + if (header == null) + { + header = new XElement(ns + "Header"); + element.AddFirst(header); + } + + var newNode = new XElement(ns + "CustomData"); + newNode.SetAttributeValue("Category", categoryName); + if (!string.IsNullOrEmpty(type)) + newNode.SetAttributeValue("Type", type); + + header.Add(newNode); + + foreach (var meta in metadatas) + { + var newMetaNode = new XElement(ns + "Metadata"); + newMetaNode.SetAttributeValue("Name", meta.Name); + newMetaNode.SetAttributeValue("Value", meta.Value); + newNode.Add(newMetaNode); + } + + _xml = element.ToString(SaveOptions.None); + + + if (_autoCommitEveryOperation) + CommitChanges(); + } + + public void DeleteCustomData(CustomData data) + { + var reader = XmlReader.Create(new StringReader(_xml)); + var element = XElement.Load(reader); + var namespaceManager = new XmlNamespaceManager(reader.NameTable); + namespaceManager.AddNamespace("p", "http://schemas.microsoft.com/sqlserver/dac/Serialization/2012/02"); + + var nodes = + element.XPathSelectElements( + string.Format("//p:Header/p:CustomData[@Category='{0}' and @Type='{1}']", data.Category, data.Type), + namespaceManager); + XElement nodeToRemove = null; + foreach (var node in nodes) + { + var isMatch = true; + + foreach (var item in data.Items) + { + var child = + node.XPathSelectElement( + string.Format("//p:Metadata[@Name='{0}' and @Value='{1}']", item.Name, item.Value), + namespaceManager); + if (null == child) + { + isMatch = false; + break; + } + } + + if (isMatch) + { + nodeToRemove = node; + break; + } + } + + if (nodeToRemove != null) + nodeToRemove.Remove(); + + _xml = element.ToString(SaveOptions.OmitDuplicateNamespaces); + + if (_autoCommitEveryOperation) + CommitChanges(); + } + + public void CommitChanges() + { + _dac.SetXml("Model.xml", _xml); + } + + public void Close() + { + _dac.Close(); + + var checksumWriter = new ModelChecksumWriter(_dacpacPath); + checksumWriter.FixChecksum(); + } + } +} \ No newline at end of file diff --git a/src/GOEddie.Dacpac.References/Metadata.cs b/src/GOEddie.Dacpac.References/Metadata.cs new file mode 100644 index 0000000..288ace3 --- /dev/null +++ b/src/GOEddie.Dacpac.References/Metadata.cs @@ -0,0 +1,14 @@ +namespace GOEddie.Dacpac.References +{ + public class Metadata + { + public string Name; + public string Value; + + public Metadata(string name, string value) + { + Name = name; + Value = value; + } + } +} \ No newline at end of file diff --git a/src/GOEddie.Dacpac.References/ModelChecksumWriter.cs b/src/GOEddie.Dacpac.References/ModelChecksumWriter.cs new file mode 100644 index 0000000..05bb4f4 --- /dev/null +++ b/src/GOEddie.Dacpac.References/ModelChecksumWriter.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Xml; + +namespace GOEddie.Dacpac.References +{ + public class ModelChecksumWriter + { + private readonly string _checksumProvider; + private readonly string _path; + + public ModelChecksumWriter(string path, + string checksumProvider = "System.Security.Cryptography.SHA256CryptoServiceProvider") + { + _path = path; + _checksumProvider = checksumProvider; + } + + public void FixChecksum(string filename = "model.xml") + { + using (var dac = new DacHacXml(_path)) + { + var originXml = dac.GetXml("Origin.xml"); + + var sourceXml = dac.GetStream(filename); + + var calculatedChecksum = + BitConverter.ToString( + (HashAlgorithm.Create(_checksumProvider) + .ComputeHash(sourceXml))).Replace("-", ""); + ; + + var reader = XmlReader.Create(new StringReader(originXml)); + reader.MoveToContent(); + + + while (reader.Read()) + { + if (reader.Name == "Checksum" && reader.GetAttribute("Uri") == string.Format("/{0}", filename)) + { + var oldChecksum = reader.ReadInnerXml(); + + if (oldChecksum == calculatedChecksum) + return; + + originXml = originXml.Replace(oldChecksum, calculatedChecksum); + + dac.SetXml("Origin.xml", originXml); + + + return; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/GOEddie.Dacpac.References/Properties/AssemblyInfo.cs b/src/GOEddie.Dacpac.References/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5f14d2e --- /dev/null +++ b/src/GOEddie.Dacpac.References/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GOEddie.Dacpac.References")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GOEddie.Dacpac.References")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d3c3bc3d-9bce-49da-bbc0-af3cf0b24270")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/GOEddie.Dacpac.References/Reference.cs b/src/GOEddie.Dacpac.References/Reference.cs new file mode 100644 index 0000000..d3ec405 --- /dev/null +++ b/src/GOEddie.Dacpac.References/Reference.cs @@ -0,0 +1,10 @@ +namespace GOEddie.Dacpac.References +{ + public class Reference + { + public string ExternalParts; + public string LogicalName; + public string Path; + public bool SupresExternalMissingDependencyErrors; + } +} \ No newline at end of file diff --git a/src/GOEddie.Dacpac.References/ReferenceBuilder.cs b/src/GOEddie.Dacpac.References/ReferenceBuilder.cs new file mode 100644 index 0000000..633c488 --- /dev/null +++ b/src/GOEddie.Dacpac.References/ReferenceBuilder.cs @@ -0,0 +1,57 @@ +namespace GOEddie.Dacpac.References +{ + public class ReferenceBuilder + { + public CustomData BuildThisDatabaseReference(string fileName, string logicalName) + { + var customData = GetCustomData(); + customData.AddMetadata("FileName", fileName); + customData.AddMetadata("LogicalName", logicalName); + return customData; + } + + public CustomData BuildSystemDatabaseReference(string dbName, string fileName, string logicalName) + { + var customData = GetCustomData(); + customData.AddMetadata("FileName", fileName); + customData.AddMetadata("LogicalName", logicalName); + customData.AddMetadata("ExternalParts", string.Format("[{0}]", dbName)); + customData.AddMetadata("SuppressMissingDependenciesErrors", "False"); + + return customData; + } + + public CustomData BuildOtherDatabaseReference(string dbName, string fileName, string logicalName) + { + var customData = GetCustomData(); + customData.AddMetadata("FileName", fileName); + customData.AddMetadata("LogicalName", logicalName); + customData.AddMetadata("ExternalParts", string.Format("[$({0})]", dbName)); + customData.AddMetadata("SuppressMissingDependenciesErrors", "False"); + + customData.RequiredSqlCmdVars.Add(dbName); + return customData; + } + + public CustomData BuildOtherServerReference(string dbName, string fileName, string logicalName, string server) + { + var customData = GetCustomData(); + customData.AddMetadata("FileName", fileName); + customData.AddMetadata("LogicalName", logicalName); + customData.AddMetadata("ExternalParts", string.Format("[$({0})].[$({1})]", server, dbName)); + customData.AddMetadata("SuppressMissingDependenciesErrors", "False"); + + customData.RequiredSqlCmdVars.Add(server); + customData.RequiredSqlCmdVars.Add(dbName); + + return customData; + } + + private CustomData GetCustomData() + { + var customData = new CustomData("Reference", "SqlSchema"); + + return customData; + } + } +} \ No newline at end of file diff --git a/src/MergeDacpac.sln b/src/MergeDacpac.sln index 64af3f5..ab4c243 100644 --- a/src/MergeDacpac.sln +++ b/src/MergeDacpac.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.31205.134 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MergeEm", "MergeEm\MergeEm.csproj", "{8CF00550-FA7D-47D3-B62F-0FCA322F57FF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GOEddie.Dacpac.References", "GOEddie.Dacpac.References\GOEddie.Dacpac.References.csproj", "{1622EB7A-0902-417C-9673-97BC13DC2DAF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {8CF00550-FA7D-47D3-B62F-0FCA322F57FF}.Debug|Any CPU.Build.0 = Debug|Any CPU {8CF00550-FA7D-47D3-B62F-0FCA322F57FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {8CF00550-FA7D-47D3-B62F-0FCA322F57FF}.Release|Any CPU.Build.0 = Release|Any CPU + {1622EB7A-0902-417C-9673-97BC13DC2DAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1622EB7A-0902-417C-9673-97BC13DC2DAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1622EB7A-0902-417C-9673-97BC13DC2DAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1622EB7A-0902-417C-9673-97BC13DC2DAF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/MergeEm/MergeEm.csproj b/src/MergeEm/MergeEm.csproj index 97f59fd..021399d 100644 --- a/src/MergeEm/MergeEm.csproj +++ b/src/MergeEm/MergeEm.csproj @@ -55,9 +55,18 @@ 150.5084.2 + + 1.4.1 + 5.0.0 + + + {1622eb7a-0902-417c-9673-97bc13dc2daf} + GOEddie.Dacpac.References + + \ No newline at end of file diff --git a/src/MergeEm/Program.cs b/src/MergeEm/Program.cs index aadc853..4a67d77 100644 --- a/src/MergeEm/Program.cs +++ b/src/MergeEm/Program.cs @@ -1,17 +1,20 @@ using Microsoft.SqlServer.Dac; using Microsoft.SqlServer.Dac.Model; using Microsoft.SqlServer.TransactSql.ScriptDom; +using ObjectsComparer; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.IO.Packaging; using System.Linq; using System.Text; -using System.Threading.Tasks; -using System.Diagnostics; + namespace MergeEm { + using GOEddie.Dacpac.References; + class Program { static void Main(string[] args) @@ -39,11 +42,18 @@ static void Main(string[] args) stopwatch.Stop(); Console.WriteLine("Completed merging {0} dacpacs in {1} seconds.", args.Length - 1, stopwatch.Elapsed.TotalSeconds); +#if DEBUG + Console.ReadLine(); +#endif Environment.Exit(returnCode); } catch (Exception e) { Console.WriteLine(e.Message); + +#if DEBUG + Console.ReadLine(); +#endif Environment.Exit(e.HResult); } } @@ -56,6 +66,7 @@ class DacpacMerge private TSqlModel _first; private string _targetPath; private TSqlModel _target; + private List _globalHeaders = new List(); public DacpacMerge(string target, params string[] sources) { @@ -79,17 +90,18 @@ public void Merge() Console.WriteLine("{0} : Processing dacpac {1}", DateTimeOffset.Now.ToString("o", System.Globalization.CultureInfo.InvariantCulture), source); + if (source == _sources.First()) { Console.WriteLine("{0}: Copying dacpac options from {1} to {2}", DateTimeOffset.Now.ToString("o", System.Globalization.CultureInfo.InvariantCulture), source, _targetPath); _first = new TSqlModel(_sources.First()); var options = _first.CopyModelOptions(); - _target = new TSqlModel(_first.Version, options); } var model = getModel(source); + foreach (var obj in model.GetObjects(DacQueryScopes.UserDefined)) { TSqlScript ast; @@ -109,6 +121,9 @@ public void Merge() } } + AddGlobalCustomData(new HeaderParser(source).GetCustomData() + .Where(x => x.Type != "Assembly").ToList()); + using (var package = DacPackage.Load(source)) { if (!(package.PreDeploymentScript is null)) @@ -131,6 +146,14 @@ private void WriteFinalDacpac(TSqlModel model, string preScript, string postScri metadata.Name = "dacpac"; DacPackageExtensions.BuildPackage(_targetPath, model, metadata); + + var writer = new HeaderWriter(_targetPath, new DacHacFactory()); + foreach (var customData in _globalHeaders) + { + writer.AddCustomData(customData); + } + writer.Close(); + AddScripts(preScript, postScript, _targetPath); model.Dispose(); } @@ -180,5 +203,37 @@ private void AddScripts(string pre, string post, string dacpacPath) package.Close(); } } + + private void AddGlobalCustomData(List newCustomData) + { + + if (_globalHeaders.Count == 0) + { + _globalHeaders.AddRange(newCustomData); + return; + } + + foreach (var customData in newCustomData) + { + var exists = false; + + foreach (var header in _globalHeaders) + { + var comparer = new ObjectsComparer.Comparer(); + var isEqual = comparer.Compare(header, customData, out IEnumerable differences); + + if (isEqual) + { + exists = true; + break; + } + } + + if (!exists) + { + _globalHeaders.Add(customData); + } + } + } } }