diff --git a/LaaProductionWeb/FluentSQL/FluentSQL.cs b/LaaProductionWeb/FluentSQL/FluentSQL.cs new file mode 100644 index 00000000..419d208e --- /dev/null +++ b/LaaProductionWeb/FluentSQL/FluentSQL.cs @@ -0,0 +1,116 @@ +namespace LaaProductionWeb.Data +{ + using LaaProductionWeb.Data.Interfaces; + + using System; + using System.Collections.Generic; + using System.Data; + using System.Data.SqlClient; + using System.Linq; + + public class FluentSQL : IFluentSQL + { + private readonly string connectionString; + + public FluentSQL(string connectionString) + { + connectionString.ThrowIfNullOrWhiteSpace(nameof(connectionString)); + + var connectionStringBuilder = new SqlConnectionStringBuilder(connectionString); + + this.connectionString = connectionStringBuilder.ToString(); + } + + public FluentSQLCommand CreateCommand(string commandText) + => new FluentSQLCommand(this, commandText); + + internal int ExecuteNonQuery(FluentSQLCommand command) + { + command.ThrowIfNull(nameof(command)); + + var rowsAffected = 0; + + using (var sqlConnection = new SqlConnection(this.connectionString)) + { + sqlConnection.OpenWithErrorHandling(); + + using (var sqlCommand = sqlConnection.CreateCommand()) + { + sqlCommand.CommandText = command.CommandText; + + sqlCommand.Parameters.AddRange(command.Parameters); + + rowsAffected = sqlCommand.ExecuteNonQuery(); + + sqlCommand.Parameters.Clear(); + } + } + + return rowsAffected; + } + + internal IEnumerable ExecuteReader(FluentSQLCommand command, Func expression) + { + command.ThrowIfNull(nameof(command)); + expression.ThrowIfNull(nameof(expression)); + + using (var sqlConnection = new SqlConnection(this.connectionString)) + { + sqlConnection.OpenWithErrorHandling(); + + using (var sqlCommand = sqlConnection.CreateCommand()) + { + sqlCommand.CommandText = command.CommandText; + + sqlCommand.Parameters.AddRange(command.Parameters); + + using (FluentSQLReader sqlReader = sqlCommand.ExecuteReader(CommandBehavior.SequentialAccess)) + { + sqlCommand.Parameters.Clear(); + + while (sqlReader.Read()) + { + yield return expression(sqlReader); + } + } + } + } + } + + internal T ExecuteScalar(FluentSQLCommand command, Func expression) + { + command.ThrowIfNull(nameof(command)); + expression.ThrowIfNull(nameof(expression)); + + var scalarResult = default(T); + + using (var sqlConnection = new SqlConnection(this.connectionString)) + { + sqlConnection.OpenWithErrorHandling(); + + using (var sqlCommand = sqlConnection.CreateCommand()) + { + sqlCommand.CommandText = command.CommandText; + + sqlCommand.Parameters.AddRange(command.Parameters); + + var scalarObject = sqlCommand.ExecuteScalar(); + + sqlCommand.Parameters.Clear(); + + if (scalarObject is T scalarValue) + { + scalarResult = scalarValue; + } + } + } + + return scalarResult; + } + + internal T FirstOrDefault(FluentSQLCommand command, Func expression) + => this + .ExecuteReader(command, expression) + .FirstOrDefault(); + } +} diff --git a/LaaProductionWeb/FluentSQL/FluentSQL.csproj b/LaaProductionWeb/FluentSQL/FluentSQL.csproj new file mode 100644 index 00000000..24dae94f --- /dev/null +++ b/LaaProductionWeb/FluentSQL/FluentSQL.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/LaaProductionWeb/FluentSQL/FluentSQLCommand.cs b/LaaProductionWeb/FluentSQL/FluentSQLCommand.cs new file mode 100644 index 00000000..39c93cdc --- /dev/null +++ b/LaaProductionWeb/FluentSQL/FluentSQLCommand.cs @@ -0,0 +1,65 @@ +namespace LaaProductionWeb.Data +{ + using LaaProductionWeb.Data.Interfaces; + + using System; + using System.Collections.Generic; + using System.Data.SqlClient; + using System.Linq; + + public class FluentSQLCommand : IFluentSQLCommand + { + private readonly FluentSQL fluentSql; + private readonly IDictionary parameters; + + public FluentSQLCommand(FluentSQL fluentSql, string commandText) + { + this.fluentSql = fluentSql.EnsureNotNull(nameof(fluentSql)); + this.CommandText = commandText.EnsureNotNullOrWhiteSpace(nameof(commandText)); + this.parameters = new Dictionary(); + } + + internal string CommandText { get; } + + internal SqlParameter[] Parameters + => this.parameters.Values.ToArray(); + + public int ExecuteNonQuery() + => this.fluentSql.ExecuteNonQuery(this); + + public IEnumerable ExecuteReader(Func expression) + => this.fluentSql.ExecuteReader(this, expression); + + public T ExecuteScalar(Func expression) + => this.fluentSql.ExecuteScalar(this, expression); + + public T FirstOrDefault(Func expression) + => this.fluentSql.FirstOrDefault(this, expression); + + public IFluentSQLCommand SetParameter(string name, object value) + { + name.ThrowIfNullOrWhiteSpace(nameof(name)); + + if (value is null) + { + value = DBNull.Value; + } + + var parameter = new SqlParameter(name, value); + + this.parameters[name] = parameter; + + return this; + } + + public IFluentSQLCommand SetParameters(IEnumerable> parameters) + { + foreach (var kvp in parameters) + { + this.SetParameter(kvp.Key, kvp.Value); + } + + return this; + } + } +} diff --git a/LaaProductionWeb/FluentSQL/FluentSQLExtensions.cs b/LaaProductionWeb/FluentSQL/FluentSQLExtensions.cs new file mode 100644 index 00000000..ad5457c1 --- /dev/null +++ b/LaaProductionWeb/FluentSQL/FluentSQLExtensions.cs @@ -0,0 +1,54 @@ +namespace LaaProductionWeb.Data +{ + using System; + using System.Data.SqlClient; + using System.Runtime.CompilerServices; + + internal static class FluentSQLExtensions + { + public static T EnsureNotNull(this T value, [CallerMemberName] string memberName = "") + { + value.ThrowIfNull(memberName); + + return value; + } + + public static string EnsureNotNullOrWhiteSpace(this string value, [CallerMemberName] string memberName = "") + { + value.ThrowIfNullOrWhiteSpace(memberName); + + return value; + } + + public static void ThrowIfNull(this T value, [CallerMemberName] string memberName = "") + { + if (value == null) + { + throw new ArgumentException($"{memberName} should not be null.", memberName); + } + } + + public static void ThrowIfNullOrWhiteSpace(this string value, [CallerMemberName] string memberName = "") + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException($"{memberName} should not be null or white space.", memberName); + } + } + + internal static void OpenWithErrorHandling(this SqlConnection sqlConnection) + { + sqlConnection.ThrowIfNull(nameof(sqlConnection)); + + sqlConnection.FireInfoMessageEventOnUserErrors = true; + sqlConnection.InfoMessage += SqlConnectionInfoMessage; + + sqlConnection.Open(); + } + + internal static void SqlConnectionInfoMessage(object sender, SqlInfoMessageEventArgs e) + { + // TODO: Log error message to the caller or elsewhere. + } + } +} diff --git a/LaaProductionWeb/FluentSQL/FluentSQLReader.cs b/LaaProductionWeb/FluentSQL/FluentSQLReader.cs new file mode 100644 index 00000000..02b565b1 --- /dev/null +++ b/LaaProductionWeb/FluentSQL/FluentSQLReader.cs @@ -0,0 +1,151 @@ +namespace LaaProductionWeb.Data +{ + using LaaProductionWeb.Data.Interfaces; + + using System; + using System.Data.SqlClient; + + public class FluentSQLReader : IFluentSQLReader, IDisposable + { + private readonly SqlDataReader sqlDataReader; + + private int index; + + private FluentSQLReader(SqlDataReader sqlDataReader) + => this.sqlDataReader = sqlDataReader.EnsureNotNull(nameof(sqlDataReader)); + + public void Dispose() + { + this.sqlDataReader.Close(); + this.sqlDataReader.Dispose(); + } + + public bool GetBool() + { + this.index++; + + if (!this.sqlDataReader.IsDBNull(this.index)) + { + return this.sqlDataReader.GetBoolean(this.index); + } + + return default(bool); + } + + public byte[] GetBytes() + { + this.index++; + + var bytes = default(byte[]); + + if (!this.sqlDataReader.IsDBNull(this.index)) + { + bytes = this.sqlDataReader.GetFieldValue(0); + } + + return bytes ?? Array.Empty(); + } + + public DateTime GetDate() + { + this.index++; + + if (!this.sqlDataReader.IsDBNull(this.index)) + { + return this.sqlDataReader.GetDateTime(this.index); + } + + return default(DateTime); + } + + public int GetInt() + { + this.index++; + + if (!this.sqlDataReader.IsDBNull(this.index)) + { + return this.sqlDataReader.GetInt32(this.index); + } + + return default(int); + } + + public long GetLong() + { + this.index++; + + if (!this.sqlDataReader.IsDBNull(this.index)) + { + return this.sqlDataReader.GetInt64(this.index); + } + + return default(long); + } + + public short GetSmallint() + { + this.index++; + + if (!this.sqlDataReader.IsDBNull(this.index)) + { + return this.sqlDataReader.GetInt16(this.index); + } + + return default(short); + } + + public string GetString() + { + this.index++; + + if (!this.sqlDataReader.IsDBNull(this.index)) + { + return $"{this.sqlDataReader.GetValue(this.index)}"; + } + + return default(string); + } + + public T GetValue() + { + this.index++; + + if (!this.sqlDataReader.IsDBNull(this.index)) + { + if (this.sqlDataReader.GetValue(this.index) is T value) + { + return value; + } + } + + return default(T); + } + + public object GetValue(string column) + { + column.EnsureNotNullOrWhiteSpace(nameof(column)); + + var colIndex = this.sqlDataReader.GetOrdinal(column); + + if (colIndex >= 0 && colIndex < this.sqlDataReader.FieldCount) + { + if (!this.sqlDataReader.IsDBNull(this.index)) + { + return this.sqlDataReader.GetValue(this.index); + } + } + + return default(object); + } + + internal bool Read() + { + this.index = -1; + + return this.sqlDataReader.Read(); + } + + public static implicit operator FluentSQLReader(SqlDataReader sqlDataReader) + => new FluentSQLReader(sqlDataReader); + } +} \ No newline at end of file diff --git a/LaaProductionWeb/FluentSQL/Interfaces/IFluentSQL.cs b/LaaProductionWeb/FluentSQL/Interfaces/IFluentSQL.cs new file mode 100644 index 00000000..4b2de335 --- /dev/null +++ b/LaaProductionWeb/FluentSQL/Interfaces/IFluentSQL.cs @@ -0,0 +1,7 @@ +namespace LaaProductionWeb.Data.Interfaces +{ + public interface IFluentSQL + { + FluentSQLCommand CreateCommand(string commandText); + } +} diff --git a/LaaProductionWeb/FluentSQL/Interfaces/IFluentSQLCommand.cs b/LaaProductionWeb/FluentSQL/Interfaces/IFluentSQLCommand.cs new file mode 100644 index 00000000..f44451fb --- /dev/null +++ b/LaaProductionWeb/FluentSQL/Interfaces/IFluentSQLCommand.cs @@ -0,0 +1,20 @@ +namespace LaaProductionWeb.Data.Interfaces +{ + using System; + using System.Collections.Generic; + + public interface IFluentSQLCommand + { + int ExecuteNonQuery(); + + IEnumerable ExecuteReader(Func expression); + + T ExecuteScalar(Func expression); + + T FirstOrDefault(Func expression); + + IFluentSQLCommand SetParameter(string name, object value); + + IFluentSQLCommand SetParameters(IEnumerable> parameters); + } +} diff --git a/LaaProductionWeb/FluentSQL/Interfaces/IFluentSQLReader.cs b/LaaProductionWeb/FluentSQL/Interfaces/IFluentSQLReader.cs new file mode 100644 index 00000000..be11cea0 --- /dev/null +++ b/LaaProductionWeb/FluentSQL/Interfaces/IFluentSQLReader.cs @@ -0,0 +1,25 @@ +namespace LaaProductionWeb.Data.Interfaces +{ + using System; + + public interface IFluentSQLReader + { + bool GetBool(); + + byte[] GetBytes(); + + DateTime GetDate(); + + int GetInt(); + + long GetLong(); + + short GetSmallint(); + + string GetString(); + + T GetValue(); + + object GetValue(string column); + } +} \ No newline at end of file diff --git a/LaaProductionWeb/LaaProductionWeb.Data/FluentSQL.cs b/LaaProductionWeb/LaaProductionWeb.Data/FluentSQL.cs index 5e78d42b..419d208e 100644 --- a/LaaProductionWeb/LaaProductionWeb.Data/FluentSQL.cs +++ b/LaaProductionWeb/LaaProductionWeb.Data/FluentSQL.cs @@ -14,6 +14,8 @@ public FluentSQL(string connectionString) { + connectionString.ThrowIfNullOrWhiteSpace(nameof(connectionString)); + var connectionStringBuilder = new SqlConnectionStringBuilder(connectionString); this.connectionString = connectionStringBuilder.ToString(); @@ -24,7 +26,7 @@ internal int ExecuteNonQuery(FluentSQLCommand command) { - command.ThrowIfNull(); + command.ThrowIfNull(nameof(command)); var rowsAffected = 0; @@ -49,8 +51,8 @@ internal IEnumerable ExecuteReader(FluentSQLCommand command, Func expression) { - command.ThrowIfNull(); - expression.ThrowIfNull(); + command.ThrowIfNull(nameof(command)); + expression.ThrowIfNull(nameof(expression)); using (var sqlConnection = new SqlConnection(this.connectionString)) { @@ -77,8 +79,8 @@ internal T ExecuteScalar(FluentSQLCommand command, Func expression) { - command.ThrowIfNull(); - expression.ThrowIfNull(); + command.ThrowIfNull(nameof(command)); + expression.ThrowIfNull(nameof(expression)); var scalarResult = default(T); diff --git a/LaaProductionWeb/LaaProductionWeb.Data/FluentSQLCommand.cs b/LaaProductionWeb/LaaProductionWeb.Data/FluentSQLCommand.cs index 66062e03..39c93cdc 100644 --- a/LaaProductionWeb/LaaProductionWeb.Data/FluentSQLCommand.cs +++ b/LaaProductionWeb/LaaProductionWeb.Data/FluentSQLCommand.cs @@ -14,8 +14,8 @@ public FluentSQLCommand(FluentSQL fluentSql, string commandText) { - this.fluentSql = fluentSql.EnsureNotNull(); - this.CommandText = commandText.EnsureNotNullOrWhiteSpace(); + this.fluentSql = fluentSql.EnsureNotNull(nameof(fluentSql)); + this.CommandText = commandText.EnsureNotNullOrWhiteSpace(nameof(commandText)); this.parameters = new Dictionary(); } @@ -38,7 +38,7 @@ public IFluentSQLCommand SetParameter(string name, object value) { - name.ThrowIfNullOrWhiteSpace(); + name.ThrowIfNullOrWhiteSpace(nameof(name)); if (value is null) { diff --git a/LaaProductionWeb/LaaProductionWeb.Data/FluentSQLExtensions.cs b/LaaProductionWeb/LaaProductionWeb.Data/FluentSQLExtensions.cs index 61b59aee..ad5457c1 100644 --- a/LaaProductionWeb/LaaProductionWeb.Data/FluentSQLExtensions.cs +++ b/LaaProductionWeb/LaaProductionWeb.Data/FluentSQLExtensions.cs @@ -38,13 +38,15 @@ internal static void OpenWithErrorHandling(this SqlConnection sqlConnection) { + sqlConnection.ThrowIfNull(nameof(sqlConnection)); + sqlConnection.FireInfoMessageEventOnUserErrors = true; sqlConnection.InfoMessage += SqlConnectionInfoMessage; sqlConnection.Open(); } - private static void SqlConnectionInfoMessage(object sender, SqlInfoMessageEventArgs e) + internal static void SqlConnectionInfoMessage(object sender, SqlInfoMessageEventArgs e) { // TODO: Log error message to the caller or elsewhere. } diff --git a/LaaProductionWeb/LaaProductionWeb.Data/FluentSQLReader.cs b/LaaProductionWeb/LaaProductionWeb.Data/FluentSQLReader.cs index 0610a83a..02b565b1 100644 --- a/LaaProductionWeb/LaaProductionWeb.Data/FluentSQLReader.cs +++ b/LaaProductionWeb/LaaProductionWeb.Data/FluentSQLReader.cs @@ -12,7 +12,7 @@ private int index; private FluentSQLReader(SqlDataReader sqlDataReader) - => this.sqlDataReader = sqlDataReader.EnsureNotNull(); + => this.sqlDataReader = sqlDataReader.EnsureNotNull(nameof(sqlDataReader)); public void Dispose() { @@ -123,7 +123,7 @@ public object GetValue(string column) { - column.EnsureNotNullOrWhiteSpace(); + column.EnsureNotNullOrWhiteSpace(nameof(column)); var colIndex = this.sqlDataReader.GetOrdinal(column); diff --git a/LaaProductionWeb/LaaProductionWeb.UnitTests.Data/FluentSQLTestClass.cs b/LaaProductionWeb/LaaProductionWeb.UnitTests.Data/FluentSQLTestClass.cs new file mode 100644 index 00000000..05db71cf --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb.UnitTests.Data/FluentSQLTestClass.cs @@ -0,0 +1,29 @@ +namespace LaaProductionWeb.UnitTests.Data +{ + using LaaProductionWeb.Data; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using System; + using System.Collections.Generic; + + [TestClass] + public class FluentSQLTestClass + { + [TestMethod] + public void Constructor_ShouldThrow_ConnectionString_IsNull() + => Assert.ThrowsException(() => new FluentSQL(null)); + + [TestMethod] + public void Constructor_ShouldThrow_ConnectionString_IsWhiteSpace() + => Assert.ThrowsException(() => new FluentSQL(string.Empty)); + + [TestMethod] + public void Constructor_ShouldThrow_ConnectionString_KeyNotFound() + => Assert.ThrowsException(() => new FluentSQL("invalid connection string")); + + [TestMethod] + public void Constructor_ShouldThrow_ConnectionString_NotWellFormed() + => Assert.ThrowsException(() => new FluentSQL("Invalid Key=123")); + } +} diff --git a/LaaProductionWeb/LaaProductionWeb.UnitTests.Data/LaaProductionWeb.UnitTests.Data.csproj b/LaaProductionWeb/LaaProductionWeb.UnitTests.Data/LaaProductionWeb.UnitTests.Data.csproj new file mode 100644 index 00000000..c209bcf4 --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb.UnitTests.Data/LaaProductionWeb.UnitTests.Data.csproj @@ -0,0 +1,71 @@ + + + + + + Debug + AnyCPU + {A14581DD-E1AC-4AB0-9329-B723D24A2F3A} + Library + Properties + LaaProductionWeb.UnitTests.Data + LaaProductionWeb.UnitTests.Data + v4.8.1 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + + + + + + + + + {4b5bf112-d3be-4e84-9a8e-74a0e314bba7} + LaaProductionWeb.Data + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/LaaProductionWeb/LaaProductionWeb.UnitTests.Data/Properties/AssemblyInfo.cs b/LaaProductionWeb/LaaProductionWeb.UnitTests.Data/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..fbfc348f --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb.UnitTests.Data/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("LaaProductionWeb.UnitTests.Data")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("LaaProductionWeb.UnitTests.Data")] +[assembly: AssemblyCopyright("Copyright © 2023")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("a14581dd-e1ac-4ab0-9329-b723d24a2f3a")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/LaaProductionWeb/LaaProductionWeb.sln b/LaaProductionWeb/LaaProductionWeb.sln index 95ff4126..f1d21555 100644 --- a/LaaProductionWeb/LaaProductionWeb.sln +++ b/LaaProductionWeb/LaaProductionWeb.sln @@ -14,6 +14,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documents", "Documents", "{ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaaProductionWeb", "LaaProductionWeb\LaaProductionWeb.csproj", "{D1AD077B-00E3-4663-AB9F-23FF65D98D2F}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{C01BB59F-25A6-4AC1-ADB5-C453EA57C35B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaaProductionWeb.UnitTests.Data", "LaaProductionWeb.UnitTests.Data\LaaProductionWeb.UnitTests.Data.csproj", "{A14581DD-E1AC-4AB0-9329-B723D24A2F3A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentSQL", "FluentSQL\FluentSQL.csproj", "{68986A99-0DBA-407C-90BE-D5ED63C41BD4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -32,10 +38,21 @@ Global {D1AD077B-00E3-4663-AB9F-23FF65D98D2F}.Debug|Any CPU.Build.0 = Debug|Any CPU {D1AD077B-00E3-4663-AB9F-23FF65D98D2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {D1AD077B-00E3-4663-AB9F-23FF65D98D2F}.Release|Any CPU.Build.0 = Release|Any CPU + {A14581DD-E1AC-4AB0-9329-B723D24A2F3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A14581DD-E1AC-4AB0-9329-B723D24A2F3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A14581DD-E1AC-4AB0-9329-B723D24A2F3A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A14581DD-E1AC-4AB0-9329-B723D24A2F3A}.Release|Any CPU.Build.0 = Release|Any CPU + {68986A99-0DBA-407C-90BE-D5ED63C41BD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68986A99-0DBA-407C-90BE-D5ED63C41BD4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68986A99-0DBA-407C-90BE-D5ED63C41BD4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68986A99-0DBA-407C-90BE-D5ED63C41BD4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A14581DD-E1AC-4AB0-9329-B723D24A2F3A} = {C01BB59F-25A6-4AC1-ADB5-C453EA57C35B} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9D3C9EBA-FB4B-4BF4-8158-4A9FE47D3E88} EndGlobalSection