From b765d311c15e4166c7d17c91810ec1c5bd38a433 Mon Sep 17 00:00:00 2001 From: David Shiflet Date: Tue, 30 Apr 2019 23:33:10 -0400 Subject: [PATCH 1/2] Smosamples (#2) * initial SMO sample skeleton project * add linux test runner * change namepsace * add Urn tests * Complete the collection sample * fix a comment --- samples/features/readme.md | 7 + .../features/sql-management-objects/README.md | 62 +++++ .../sql-management-objects/prep/dockerfile | 8 + .../sql-management-objects/prep/entrypoint.sh | 2 + .../sql-management-objects/prep/restore.sh | 4 + .../sql-management-objects/prep/restore.sql | 5 + .../sql-management-objects/runtests.cmd | 17 ++ .../sql-management-objects/runtests.sh | 14 ++ .../src/CollectionSamples.cs | 64 +++++ .../src/ConnectionHelpers.cs | 135 +++++++++++ .../src/ConnectionMetrics.cs | 93 ++++++++ .../src/GenericSqlProxy.cs | 220 ++++++++++++++++++ .../src/SmoSamples.csproj | 36 +++ .../sql-management-objects/src/Urn.cs | 51 ++++ .../src/localhost.runsettings | 8 + 15 files changed, 726 insertions(+) create mode 100644 samples/features/sql-management-objects/README.md create mode 100644 samples/features/sql-management-objects/prep/dockerfile create mode 100644 samples/features/sql-management-objects/prep/entrypoint.sh create mode 100644 samples/features/sql-management-objects/prep/restore.sh create mode 100644 samples/features/sql-management-objects/prep/restore.sql create mode 100644 samples/features/sql-management-objects/runtests.cmd create mode 100644 samples/features/sql-management-objects/runtests.sh create mode 100644 samples/features/sql-management-objects/src/CollectionSamples.cs create mode 100644 samples/features/sql-management-objects/src/ConnectionHelpers.cs create mode 100644 samples/features/sql-management-objects/src/ConnectionMetrics.cs create mode 100644 samples/features/sql-management-objects/src/GenericSqlProxy.cs create mode 100644 samples/features/sql-management-objects/src/SmoSamples.csproj create mode 100644 samples/features/sql-management-objects/src/Urn.cs create mode 100644 samples/features/sql-management-objects/src/localhost.runsettings diff --git a/samples/features/readme.md b/samples/features/readme.md index 70ceb668db..079341dcd5 100644 --- a/samples/features/readme.md +++ b/samples/features/readme.md @@ -28,8 +28,15 @@ Built-in temporal functions enable you to easily track history of changes in a t Graph tables enable you to add a non-relational capability to your database. +[SQL Management Objects (SMO)](sql-management-objects) + +The SQL Server Management Objects (SMO) Framework is a set of objects designed for programmatic management of Microsoft SQL Server and Microsoft Azure SQL Database. These code snippets demonstrate features of SMO and illustrate how to use SMO properties and collections without sacrificing performance. + ## Samples for Business Intelligence features within SQL Server [Reporting Services (SSRS)](reporting-services) Reporting Services provides reporting capabilities for your organziation. Reporting Services can be integrated with SharePoint Server or used as a standalone service. + + + diff --git a/samples/features/sql-management-objects/README.md b/samples/features/sql-management-objects/README.md new file mode 100644 index 0000000000..dea0dd75fb --- /dev/null +++ b/samples/features/sql-management-objects/README.md @@ -0,0 +1,62 @@ +# SmoSamples + +This unit test project is meant to demonstrate features of the Sql Management Objects framework and to help developers optimize performance of their SMO-based applications. + + +### Contents + +[About this sample](#about-this-sample)
+[Before you begin](#before-you-begin)
+[Run this sample](#run-this-sample)
+[Sample details](#sample-details)
+[Disclaimers](#disclaimers)
+[Related links](#related-links)
+ + + + +## About this sample + + +- **Applies to:** SQL Server 2016 (or higher), Azure SQL Database, Azure SQL Data Warehouse +- **Key features:** +- Unit tests and a docker file that demonstrate proper use of SMO features against a working SQL Server instance. +- **Programming Language:** +- C# + + + +## Before you begin + +To run this sample, you need the following prerequisites. + +**Software prerequisites:** + +1. SQL Server 2016 (or higher) or an Azure SQL Database with the full WideWorldImporters sample database, or +2. Docker +3. At minimum the dotnet 2.2 SDK, or Visual Studio 2017 + + + +## Run this sample + + + + +## Sample details + +Each unit test demonstrates a specific aspect of SMO-based application development, either in isolation or in conjunction with other SMO components.
+Feature areas tested include: +1. Efficient use of collections +2. Sql query capture +3. Events +4. URNs +5. Script generation + + + + +## Related Links +The SMO NuGet package is at https://www.nuget.org/packages/Microsoft.SqlServer.SqlManagementObjects/
+Documentation for the APIs is at https://docs.microsoft.com/en-us/sql/relational-databases/server-management-objects-smo/overview-smo
+The WideWorldImporters sample database can be found at https://github.com/Microsoft/sql-server-samples/releases/download/wide-world-importers-v1.0/WideWorldImporters-Full.bak
\ No newline at end of file diff --git a/samples/features/sql-management-objects/prep/dockerfile b/samples/features/sql-management-objects/prep/dockerfile new file mode 100644 index 0000000000..af28e1f2d1 --- /dev/null +++ b/samples/features/sql-management-objects/prep/dockerfile @@ -0,0 +1,8 @@ +FROM mcr.microsoft.com/mssql/server:2017-latest +WORKDIR /tmp/backup +RUN wget -q https://github.com/Microsoft/sql-server-samples/releases/download/wide-world-importers-v1.0/WideWorldImporters-Full.bak +COPY restore.sql . +COPY restore.sh . +COPY entrypoint.sh . +CMD ["/bin/bash", "/tmp/backup/entrypoint.sh"] + diff --git a/samples/features/sql-management-objects/prep/entrypoint.sh b/samples/features/sql-management-objects/prep/entrypoint.sh new file mode 100644 index 0000000000..c7f19da118 --- /dev/null +++ b/samples/features/sql-management-objects/prep/entrypoint.sh @@ -0,0 +1,2 @@ +/opt/mssql/bin/sqlservr & /tmp/backup/restore.sh +tail -f /dev/null diff --git a/samples/features/sql-management-objects/prep/restore.sh b/samples/features/sql-management-objects/prep/restore.sh new file mode 100644 index 0000000000..52532d1732 --- /dev/null +++ b/samples/features/sql-management-objects/prep/restore.sh @@ -0,0 +1,4 @@ +sleep 35s +echo sa_password is $SA_PASSWORD +/opt/mssql-tools/bin/sqlcmd -S . -U sa -P $SA_PASSWORD -i /tmp/backup/restore.sql + \ No newline at end of file diff --git a/samples/features/sql-management-objects/prep/restore.sql b/samples/features/sql-management-objects/prep/restore.sql new file mode 100644 index 0000000000..b4ec6e8c3f --- /dev/null +++ b/samples/features/sql-management-objects/prep/restore.sql @@ -0,0 +1,5 @@ +RESTORE DATABASE WideWorldImporters FROM DISK = "/tmp/backup/WideWorldImporters-Full.bak" +WITH MOVE "WWI_Primary" TO "/var/opt/mssql/data/WideWorldImporters.mdf", +MOVE "WWI_Userdata" TO "/var/opt/mssql/data/WideWorldImporters_UserData.ndf", +MOVE "WWI_Log" TO "/var/opt/mssql/data/WideWorldImporters.ldf", MOVE "WWI_InMemory_Data_1" +TO "/var/opt/mssql/data/WideWorldImporters_InMemory_Data_1" \ No newline at end of file diff --git a/samples/features/sql-management-objects/runtests.cmd b/samples/features/sql-management-objects/runtests.cmd new file mode 100644 index 0000000000..7cd30c1451 --- /dev/null +++ b/samples/features/sql-management-objects/runtests.cmd @@ -0,0 +1,17 @@ +@echo off + set pwd=Passwd__%random% +echo Building the SQL Linux Docker container +docker pull mcr.microsoft.com/mssql/server:2017-latest +docker build -t sqllinux prep +echo Running the SQL linux docker image +start cmd /k docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=%pwd%" -e "MSSQL_SA_PASSWORD=%pwd%" -h sqlserver --name sqlserver -p:1433:1433 --rm sqllinux +echo Waiting 90 seconds for SQL server to restore WideWorldImporters +timeout /t 90 +setlocal +echo running tests against SQL 2017 database WideWorldImporters +set TEST_PASSWORD=%pwd% +dotnet publish src -o out +dotnet vstest src\out\SmoSamples.dll /logger:console /settings:src\localhost.runsettings +endlocal +echo Terminating docker container +docker kill sqlserver diff --git a/samples/features/sql-management-objects/runtests.sh b/samples/features/sql-management-objects/runtests.sh new file mode 100644 index 0000000000..2712c7f033 --- /dev/null +++ b/samples/features/sql-management-objects/runtests.sh @@ -0,0 +1,14 @@ +pwd=Pwd$RANDOM +echo Building the SQL Linux Docker container +docker pull mcr.microsoft.com/mssql/server:2017-latest +docker build -t sqllinux prep +echo Running the SQL linux docker image +docker run -e ACCEPT_EULA=Y -e SA_PASSWORD=$pwd -e MSSQL_SA_PASSWORD=$pwd -h sqlserver --name sqlserver -p:1433:1433 -d --rm sqllinux +echo Waiting 2 minutes for SQL server to restore WideWorldImporters +sleep 120 +echo running tests against SQL 2017 database WideWorldImporters +export TEST_PASSWORD=$pwd +dotnet publish src +dotnet vstest src/bin/Debug/netcoreapp2.1/SmoSamples.dll --logger:console --Settings:src/localhost.runsettings +echo Terminating docker container +docker kill sqlserver diff --git a/samples/features/sql-management-objects/src/CollectionSamples.cs b/samples/features/sql-management-objects/src/CollectionSamples.cs new file mode 100644 index 0000000000..c9ac297d35 --- /dev/null +++ b/samples/features/sql-management-objects/src/CollectionSamples.cs @@ -0,0 +1,64 @@ + +using System.Diagnostics; +using Microsoft.SqlServer.Management.Smo; + +namespace Microsoft.SqlServer.SmoSamples +{ + using System; + using System.Collections.Generic; + using System.Text; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using NUnit.Framework; + using Assert = NUnit.Framework.Assert; + + [TestClass] + public class CollectionSamples + { + public VisualStudio.TestTools.UnitTesting.TestContext TestContext { get; set; } + + [TestMethod] + public void Collection_iteration_is_faster_with_SetDefaultInitFields() + { + using (var connectionMetrics = ConnectionMetrics.SetupMeasuredConnection(TestContext, 50)) + { + var server = new Management.Smo.Server(connectionMetrics.ServerConnection); + var database = server.Databases[TestContext.GetTestDatabaseName()]; + connectionMetrics.Reset(); + foreach (Table table in database.Tables) + { + Trace.TraceInformation( + $"Unoptimized table Name: {table.Name}\tSchema:{table.Schema}\tFileGroup:{table.FileGroup}"); + } + + var unoptimizedMetrics = (connectionMetrics.QueryCount, connectionMetrics.BytesSent, connectionMetrics.BytesRead, connectionMetrics.ConnectionCount); + Trace.TraceInformation(string.Join($"{Environment.NewLine}\t", new[] + { + "Unoptimized metrics:", + $"QueryCount:{unoptimizedMetrics.QueryCount}", $"ConnectionCount:{unoptimizedMetrics.ConnectionCount}", + $"BytesSent:{unoptimizedMetrics.BytesSent}", $"BytesRead:{unoptimizedMetrics.BytesRead}" + })); + + connectionMetrics.Reset(); + server.SetDefaultInitFields(typeof(Table), "Name", "Schema", "FileGroup"); + database.Tables.Refresh(); + foreach (Table table in database.Tables) + { + Trace.TraceInformation( + $"Optimized table Name: {table.Name}\tSchema:{table.Schema}\tFileGroup:{table.FileGroup}"); + } + + var optimizedMetrics = (connectionMetrics.QueryCount, connectionMetrics.BytesSent, connectionMetrics.BytesRead, connectionMetrics.ConnectionCount); + Trace.TraceInformation(string.Join($"{Environment.NewLine}\t", new[] + { + "Optimized Metrics:", + $"QueryCount:{optimizedMetrics.QueryCount}", $"ConnectionCount:{optimizedMetrics.ConnectionCount}", + $"BytesSent:{optimizedMetrics.BytesSent}", $"BytesRead:{optimizedMetrics.BytesRead}" + })); + Assert.That(optimizedMetrics.BytesRead, Is.LessThan(unoptimizedMetrics.BytesRead), "BytesRead"); + Assert.That(optimizedMetrics.BytesSent, Is.LessThan(unoptimizedMetrics.BytesSent), "BytesSent"); + Assert.That(optimizedMetrics.ConnectionCount, Is.AtMost(unoptimizedMetrics.ConnectionCount), "ConnectionCount"); + Assert.That(optimizedMetrics.QueryCount, Is.LessThan(unoptimizedMetrics.QueryCount), "QueryCount"); + } + } + } +} diff --git a/samples/features/sql-management-objects/src/ConnectionHelpers.cs b/samples/features/sql-management-objects/src/ConnectionHelpers.cs new file mode 100644 index 0000000000..b738d16347 --- /dev/null +++ b/samples/features/sql-management-objects/src/ConnectionHelpers.cs @@ -0,0 +1,135 @@ +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Diagnostics; +using System.Reflection; +using System.Text; +using Assert = NUnit.Framework.Assert; +namespace Microsoft.SqlServer.SmoSamples +{ + // Used by test classes to initialize and retrieve a ServerConnection for use in the tests themselves + static class ConnectionHelpers + { + + public static ServerConnection GetTestConnection(this VisualStudio.TestTools.UnitTesting.TestContext context, ConnectionType connectionType = ConnectionType.Default) + { + var connectionString = context.GetConnectionString(); + var connectionStrBuilder = new SqlConnectionStringBuilder(connectionString); + var instanceName = connectionStrBuilder.DataSource; + var sqlServerLogin = connectionStrBuilder.UserID; + var password = connectionStrBuilder.Password; + if (connectionType == ConnectionType.SqlConnection) + { + return new ServerConnection(new SqlConnection(connectionString)); + } + if (connectionType == ConnectionType.Integrated) + { + return new ServerConnection(instanceName); + } + if (connectionType == ConnectionType.SqlAuth ) + { + if (string.IsNullOrWhiteSpace(sqlServerLogin) || string.IsNullOrWhiteSpace(password)) + { + throw new ArgumentException("username and password values are missing from test connection string"); + } + return new ServerConnection(instanceName, sqlServerLogin, password); + } + if (string.IsNullOrEmpty(sqlServerLogin)) + { + return new ServerConnection(instanceName); + } + return new ServerConnection(instanceName, sqlServerLogin, password); + } + + public static string GetConnectionString(this VisualStudio.TestTools.UnitTesting.TestContext context) + { + var connectionString = context.Properties["connectionString"].ToString(); + Assert.That(connectionString, Is.Not.Empty, "connectionString must be set"); + connectionString = connectionString.Replace("[hostname]", Environment.GetEnvironmentVariable("TEST_HOSTNAME")). + Replace("[username]", Environment.GetEnvironmentVariable("TEST_USERNAME")). + Replace("[password]", Environment.GetEnvironmentVariable("TEST_PASSWORD")). + Replace("[database]", Environment.GetEnvironmentVariable("TEST_DATABASE")); + Console.WriteLine("Connection string: {0}", connectionString); + return connectionString; + } + + /// + /// Returns the name of the database to use for the tests + /// + /// + public static string GetTestDatabaseName(this VisualStudio.TestTools.UnitTesting.TestContext context) + { + var databaseName = Environment.GetEnvironmentVariable("TEST_DATABASE"); + if (string.IsNullOrEmpty(databaseName)) + { + databaseName = context.Properties["testDatabase"].ToString(); + } + Assert.That(databaseName, Is.Not.Empty, "testDatabase must be set"); + Console.WriteLine("Test database: {0}", databaseName); + return databaseName; + } + + /// + /// Returns the folder where result files should be written + /// + /// + /// + public static string GetResultsFolder(this VisualStudio.TestTools.UnitTesting.TestContext context) + { + var path = Environment.GetEnvironmentVariable("RESULTS_FOLDER"); + if (string.IsNullOrEmpty(path)) + { + path = context.Properties.ContainsKey("resultsFolder") ? context.Properties["resultsFolder"].ToString() : null; + } + if (string.IsNullOrEmpty(path)) + { + path = PathWrapper.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + path = PathWrapper.Combine(path, "results"); + } + return path; + } + + /// + /// creates a new database with a random name, runs the action, and drops the database + /// + /// + /// + /// + public static void ExecuteWithDbDrop(this VisualStudio.TestTools.UnitTesting.TestContext context, Action action, Action preCreateAction = null) + { + var dbName = string.Format("{0}{1}", context.TestName, new Random().Next()); + var serverConnection = context.GetTestConnection(); + var server = new Management.Smo.Server(serverConnection); + var database = new Database(server, dbName); + preCreateAction?.Invoke(database); + database.Create(); + try + { + action(database); + } + finally + { + try + { + database.Drop(); + } + catch (Exception e) + { + Trace.TraceError("Unable to drop database {0}: {1}", dbName, e); + } + } + } + } + + enum ConnectionType + { + Default, // whatever is specified in the config + Integrated, // integrated auth + SqlAuth, // SQL auth + SqlConnection // Create a SqlConnection first from the connection string + } +} diff --git a/samples/features/sql-management-objects/src/ConnectionMetrics.cs b/samples/features/sql-management-objects/src/ConnectionMetrics.cs new file mode 100644 index 0000000000..1811d6838d --- /dev/null +++ b/samples/features/sql-management-objects/src/ConnectionMetrics.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Diagnostics; +using System.Text; +using System.Threading; +using Microsoft.SqlServer.Management.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.SqlServer.SmoSamples +{ + class ConnectionMetrics : IDisposable + { + public int ConnectionCount; + public long BytesRead; + public long BytesSent; + public int QueryCount; + public readonly ServerConnection ServerConnection; + private readonly GenericSqlProxy proxy; + + public ConnectionMetrics(ServerConnection serverConnection, GenericSqlProxy proxy) + { + this.proxy = proxy; + ServerConnection = serverConnection; + proxy.OnConnect += Proxy_OnConnect; + proxy.OnWriteHost += Proxy_OnWriteHost; + proxy.OnWriteClient += Proxy_OnWriteClient; + serverConnection.StatementExecuted += ServerConnection_StatementExecuted; + } + + public void Reset() + { + ConnectionCount = 0; + BytesRead = BytesSent = 0; + QueryCount = 0; + } + + private void ServerConnection_StatementExecuted(object sender, StatementEventArgs e) + { + QueryCount++; + } + + private void Proxy_OnWriteClient(object sender, StreamWriteEventArgs e) + { + BytesRead += e.BytesWritten; + } + + private void Proxy_OnWriteHost(object sender, StreamWriteEventArgs e) + { + BytesSent += e.BytesWritten; + } + + private void Proxy_OnConnect(object sender, ProxyConnectionEventArgs e) + { + ConnectionCount++; + } + + public void Dispose() + { + proxy.OnConnect -= Proxy_OnConnect; + proxy.OnWriteHost -= Proxy_OnWriteHost; + proxy.OnWriteClient -= Proxy_OnWriteClient; + ServerConnection.StatementExecuted -= ServerConnection_StatementExecuted; + ServerConnection.SqlConnectionObject.Dispose(); + proxy.Dispose(); + } + + public static ConnectionMetrics SetupMeasuredConnection(TestContext testContext, int latencyPaddingMs = 0) + { + var connectionString = testContext.GetConnectionString(); + var proxy = new GenericSqlProxy(connectionString); + if (latencyPaddingMs > 0) + { + proxy.OnWriteClient += (o,e) => DelayWrite(latencyPaddingMs, e); + } + // If running these tests in a container you may need to set a specific port + // and expose that port in the dockerfile + var port = testContext.Properties.ContainsKey("proxyPort") + ? Convert.ToInt32(testContext.Properties["proxyPort"]) + : 0; + var sqlConnection = new SqlConnection(proxy.Initialize(port)); + var serverConnection = new ServerConnection(sqlConnection); + return new ConnectionMetrics(serverConnection, proxy); + } + + static void DelayWrite(long delay, StreamWriteEventArgs args) + { + Thread.Sleep(Convert.ToInt32(delay)); + } + } + + +} diff --git a/samples/features/sql-management-objects/src/GenericSqlProxy.cs b/samples/features/sql-management-objects/src/GenericSqlProxy.cs new file mode 100644 index 0000000000..dce9231de2 --- /dev/null +++ b/samples/features/sql-management-objects/src/GenericSqlProxy.cs @@ -0,0 +1,220 @@ +using System; +using System.Data.SqlClient; +using System.Net.Sockets; +using System.Net; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.SqlServer.SmoSamples +{ + /// + /// Provides an in-memory proxy with callbacks that allow tests to run code before transmission and after receipt of + /// data on the wire + /// + [DebuggerDisplay("{connectionString}:[{Port}]")] + class GenericSqlProxy : IDisposable + { + // We pick a buffer size that's large enough to hold most single replies so we don't over-inject latency + private const int BufferSizeBytes = 128 * 1024; + readonly string connectionString; + volatile bool disposed; + private TcpListener listener = null; + private readonly CancellationTokenSource tokenSource = new CancellationTokenSource(); + + /// + /// Constructs a GenericSqlProxy for the local default sql instance + /// + public GenericSqlProxy() : this(".") + { + + } + + /// + /// Construct a new GenericSqlProxy for the given connection string + /// + /// + public GenericSqlProxy(string connectionString) + { + this.connectionString = connectionString; + } + + public int Port { get; private set; } + + /// + /// Initializes the proxy by opening the TCP listener and copying data between client and server + /// + /// local port number to use. 0 will use a random port + /// The connection string to use for the SqlConnection + public string Initialize(int localPort = 0) + { + var builder = new SqlConnectionStringBuilder(connectionString); + GetTcpInfoFromDataSource(builder.DataSource, out string hostName, out int port); + listener = new TcpListener(IPAddress.Loopback, localPort); + listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); + listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + listener.Start(); + Port = ((IPEndPoint) listener.LocalEndpoint).Port; + Trace.TraceInformation($"Starting TcpListener on port {Port}"); + Task.Factory.StartNew(() => { AsyncInit(listener, hostName, port); }); + return new SqlConnectionStringBuilder(builder.ConnectionString) + { + DataSource = $"tcp:127.0.0.1,{Port}" + }.ConnectionString; + } + + private void AsyncInit(TcpListener tcpListener, string hostName, int port) + { + + while (!disposed) + { + var accept = tcpListener.AcceptTcpClientAsync(); + if (accept.Wait(1000, tokenSource.Token) && !tokenSource.IsCancellationRequested) + { + var localClient = accept.GetAwaiter().GetResult(); + OnConnect?.Invoke(this, new ProxyConnectionEventArgs(localClient)); + var remoteClient = new TcpClient() {NoDelay = true}; + tokenSource.Token.Register(() => + { + localClient.Dispose(); + remoteClient.Dispose(); + }); + remoteClient.ConnectAsync(hostName, port).Wait(tokenSource.Token); + if (!tokenSource.IsCancellationRequested) + { + + + Task.Factory.StartNew(() => { ForwardToSql(localClient, remoteClient); }); + Task.Factory.StartNew(() => { ForwardToClient(localClient, remoteClient); }); + } + else + { + Trace.TraceInformation("AsyncInit aborted due to cancellation token set"); + } + } + } + } + + /// + /// Fires before the proxy writes a buffer to the host + /// + public event EventHandler OnWriteHost; + + /// + /// Fires before the proxy writes a buffer to the client + /// + public event EventHandler OnWriteClient; + + /// + /// Fires when a new connection to the proxy's port is accepted + /// + public event EventHandler OnConnect; + + private void ForwardToSql(TcpClient ourClient, TcpClient sqlClient) + { + long index = 0; + try + { + while (!disposed) + { + byte[] buffer = new byte[BufferSizeBytes]; + int bytesRead = ourClient.GetStream().ReadAsync(buffer, 0, buffer.Length, tokenSource.Token).Result; + if (!tokenSource.Token.IsCancellationRequested) + { + OnWriteHost?.Invoke(this, new StreamWriteEventArgs(index++, buffer, bytesRead)); + sqlClient.GetStream().Write(buffer, 0, bytesRead); + } + } + } + catch (Exception) + { + if (!disposed) + { + throw; + } + } + finally + { + Trace.TraceInformation("ForwardToSql exiting"); + } + } + + private void ForwardToClient(TcpClient ourClient, TcpClient sqlClient) + { + long index = 0; + try + { + while (!disposed) + { + byte[] buffer = new byte[BufferSizeBytes]; + int bytesRead = sqlClient.GetStream().ReadAsync(buffer, 0, buffer.Length, tokenSource.Token).Result; + if (!tokenSource.Token.IsCancellationRequested) + { + OnWriteClient?.Invoke(this, new StreamWriteEventArgs(index++, buffer, bytesRead)); + ourClient.GetStream().Write(buffer, 0, bytesRead); + } + } + } + catch (Exception) + { + if (!disposed) + { + throw; + } + } + finally + { + Trace.TraceInformation("ForwardToClient exiting"); + } + } + + private static void GetTcpInfoFromDataSource(string dataSource, out string hostName, out int port) + { + string[] dataSourceParts = dataSource.Split(','); + if (dataSourceParts.Length == 1) + { + hostName = dataSourceParts[0].Replace("tcp:", ""); + port = 1433; + } + else if (dataSourceParts.Length == 2) + { + hostName = dataSourceParts[0].Replace("tcp:", ""); + port = int.Parse(dataSourceParts[1]); + } + else + { + throw new InvalidOperationException("TCP Connection String not in correct format!"); + } + } + + public void Dispose() + { + disposed = true; + tokenSource.Cancel(); + Trace.TraceInformation("Disposing TcpListener on port {0}", Port); + listener?.Stop(); + } + } + + public class StreamWriteEventArgs : EventArgs + { + public StreamWriteEventArgs(long index, byte[]buffer, int bytesWritten) + { + Index = index; + Buffer = buffer; + BytesWritten = bytesWritten; + } + public long Index; + public byte[] Buffer; + public int BytesWritten; + } + + public class ProxyConnectionEventArgs : EventArgs + { + public ProxyConnectionEventArgs(TcpClient client) + { + Client = client; + } + public TcpClient Client; + } +} diff --git a/samples/features/sql-management-objects/src/SmoSamples.csproj b/samples/features/sql-management-objects/src/SmoSamples.csproj new file mode 100644 index 0000000000..55ff8d136e --- /dev/null +++ b/samples/features/sql-management-objects/src/SmoSamples.csproj @@ -0,0 +1,36 @@ + + + Library + netcoreapp2.1 + false + false + + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Microsoft.SqlSerer.SmoSamples + + + Microsoft.SqlServer.SmoSamples + + + + + + + + + + + + + + + + + diff --git a/samples/features/sql-management-objects/src/Urn.cs b/samples/features/sql-management-objects/src/Urn.cs new file mode 100644 index 0000000000..118206db7d --- /dev/null +++ b/samples/features/sql-management-objects/src/Urn.cs @@ -0,0 +1,51 @@ +using Microsoft.SqlServer.Management.Smo; + +namespace Microsoft.SqlServer.SmoSamples +{ + +using VisualStudio.TestTools.UnitTesting; +using Management.Sdk.Sfc; +using NUnit.Framework; +using Assert=NUnit.Framework.Assert; + + [TestClass] + public class UrnSamples + { + public VisualStudio.TestTools.UnitTesting.TestContext TestContext {get;set;} + + [TestMethod] + public virtual void Urn_attribute_values_require_escaping() + { + var connection = TestContext.GetTestConnection(); + var server = new Management.Smo.Server(connection); + TestContext.ExecuteWithDbDrop((database) => + { + var table = new Table(database, "Name'With'Quotes"); + table.Columns.Add(new Column(table, "col1", DataType.Int)); + table.Create(); + Assert.That(table.Urn.GetNameForType(Table.UrnSuffix), Is.EqualTo("Name'With'Quotes"), "Urn Value"); + Assert.Throws(() => + table = (Table) server.GetSmoObject( + $"Server/Database[@Name='{database.Name}']/Table[@Name='Name'With'Quotes']")); + table = (Table)server.GetSmoObject( + $"Server/Database[@Name='{database.Name}']/Table[@Name='{Urn.EscapeString("Name'With'Quotes")}']"); + Assert.That(table.Name, Is.EqualTo("Name'With'Quotes"), "Table with escaped name"); + }); + } + + [TestMethod] + public virtual void Server_Urn_has_Name_matching_InstanceName() + { + var connection = TestContext.GetTestConnection(); + var server = new Management.Smo.Server(connection); + Assert.That(server.Urn.Value, Is.EqualTo($"Server[@Name='{Urn.EscapeString(connection.TrueName)}']"), "Server URN"); + } + + [TestMethod] + public virtual void Urn_Type_is_the_last_item() + { + var urn = new Urn("Server[@Name='server']/Database[@Name='database']/Table[@Name='table']"); + Assert.That(urn.Type, Is.EqualTo(Table.UrnSuffix), "Urn Type"); + } + } +} \ No newline at end of file diff --git a/samples/features/sql-management-objects/src/localhost.runsettings b/samples/features/sql-management-objects/src/localhost.runsettings new file mode 100644 index 0000000000..5f086f9ae7 --- /dev/null +++ b/samples/features/sql-management-objects/src/localhost.runsettings @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From 26e9c390441c5a74f8f9b55adc184ae68a72d82f Mon Sep 17 00:00:00 2001 From: shueybubbles Date: Mon, 6 May 2019 11:44:25 -0400 Subject: [PATCH 2/2] code review feedback --- .gitignore | 876 +++++++++--------- .../features/sql-management-objects/README.md | 122 +-- .../sql-management-objects/prep/dockerfile | 16 +- .../sql-management-objects/prep/restore.sh | 1 + .../sql-management-objects/prep/restore.sql | 8 +- .../src/CollectionSamples.cs | 24 +- .../src/ConnectionHelpers.cs | 285 +++--- .../src/ConnectionMetrics.cs | 186 ++-- .../src/GenericSqlProxy.cs | 440 ++++----- .../src/SmoSamples.csproj | 65 +- .../sql-management-objects/src/SmoSamples.sln | 25 + .../src/localhost.runsettings | 14 +- 12 files changed, 1054 insertions(+), 1008 deletions(-) create mode 100644 samples/features/sql-management-objects/src/SmoSamples.sln diff --git a/.gitignore b/.gitignore index 9d7f140743..72e4ebacd7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,437 +1,439 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -build/ -bld/ -[Bb]in/ -[Oo]bj/ - -# Visual Studio 2015 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ -**/Properties/launchSettings.json -# DNX -project.lock.json -artifacts/ - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -# publish/ - -# Publish Web Output -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config - -# Windows Azure Build Output -csx/ -*.build.csdef - -# Windows Store app package directory -AppPackages/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -[Ss]tyle[Cc]op.* -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -*.pfx -*.publishsettings -node_modules/ -orleans.codegen.cs - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Typescript v1 declaration files -typings/ -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -/build -/Build -/deploy -/package -bin/ -obj/ -sql/ -*/obj/debug -**/Debug -#ignore thumbnails created by windows -Thumbs.db -#Ignore files build by Visual Studio -*.obj -*.pdb -*.user -*.aps -*.pch -*.docstates -*.vspscc -*_i.c -*_p.c -*.ncb -*.suo -*.tlb -*.dbmdl -*.schemaview -*.tlh -*.cache -*.ilk -*.log -[Bb]in -[Dd]ebug*/ -*.lib -*.sbr -obj/ -[Rr]elease*/ -_ReSharper*/ -[Tt]est[Rr]esult* -*.docstates -*.swp -*.*~ -*.gpState -*.ReSharper* -*.preflight -*.nocommit -#Ignore Recovery Files made by Excel -~$*.xlsx -*.rdl.data - -*.jfm - -#====================================================================================== -# The below section could possibly be removed as these should be ignored by the above. -#====================================================================================== - -samples/in-memory/ticket-reservations/DemoWorkload/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs -*.nupkg -samples/in-memory/ticket-reservations/packages/CircularGauge.1.0.0/CreatePackageFile.bat -*.suo -samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/bin/Debug/PopulateAlwaysEncryptedData.exe -*.pdb -samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/bin/Debug/PopulateAlwaysEncryptedData.vshost.exe -*.Cache -samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/bin/Debug/PopulateAlwaysEncryptedData.exe.config -samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs -samples/databases/wide-world-importers/workload-drivers/vehicle-location-insert/MultithreadedInMemoryTableInsert/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs -*.exe -samples/features/in-memory/iot-smart-grid/ConsoleClient/bin/Release/DataGenerator.dll -samples/features/in-memory/iot-smart-grid/ConsoleClient/bin/Release/Reports/PowerDashboard.pbix -samples/databases/wide-world-importers/wwi-integration-etl/Daily ETL/bin/Development/Daily ETL.ispac -samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Release/PopulateAlwaysEncryptedData.csproj.FileListAbsolute.txt -samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/bin/Release/PopulateAlwaysEncryptedData.vshost.exe.config -samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Release/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs -*.dll -samples/features/in-memory/ticket-reservations/DemoWorkload/obj/Release/DemoWorkload.FrmConfig.resources -samples/features/in-memory/ticket-reservations/DemoWorkload/bin/Release/DemoWorkload.vshost.exe.config -samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Debug/PopulateAlwaysEncryptedData.csproj.FileListAbsolute.txt -samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/bin/Debug/PopulateAlwaysEncryptedData.vshost.exe.config -*.zip -samples/features/in-memory/ticket-reservations/packages/CircularGauge.1.0.0/CreatePackageFile.bat -samples/features/in-memory/ticket-reservations/TicketReservations/TicketReservations.dbmdl -samples/databases/wide-world-importers/workload-drivers/vehicle-location-insert/MultithreadedInMemoryTableInsert/obj/Release/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs -samples/databases/wide-world-importers/workload-drivers/vehicle-location-insert/MultithreadedInMemoryTableInsert/obj/Debug/MultithreadedInMemoryTableInsert.csproj.FileListAbsolute.txt -samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Debug/PopulateAlwaysEncryptedData.Properties.Resources.resources -samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Release/PopulateAlwaysEncryptedData.PopulateAlwaysEncryptedDataMain.resources -samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/bin/Debug/PopulateAlwaysEncryptedData.vshost.exe.manifest -samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Debug/PopulateAlwaysEncryptedData.PopulateAlwaysEncryptedDataMain.resources -samples/databases/wide-world-importers/workload-drivers/vehicle-location-insert/MultithreadedInMemoryTableInsert/obj/Release/MultithreadedInMemoryTableInsert.Properties.Resources.resources -samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Release/PopulateAlwaysEncryptedData.Properties.Resources.resources -samples/features/in-memory/ticket-reservations/packages/CircularGauge.1.0.0/ReadMe.txt -*.dacpac -samples/features/in-memory/ticket-reservations/TicketReservations/obj/Release/Model.xml -samples/features/in-memory/ticket-reservations/DemoWorkload/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs -*.dat - -*.user -samples/features/in-memory/ticket-reservations/DemoWorkload/obj/Release/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs -samples/databases/wide-world-importers/workload-drivers/vehicle-location-insert/MultithreadedInMemoryTableInsert/obj/Debug/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs -samples/features/in-memory/ticket-reservations/DemoWorkload/bin/Debug/DemoWorkload.vshost.exe.config -samples/features/in-memory/ticket-reservations/DemoWorkload/obj/Release/DemoWorkload.Properties.Resources.resources -samples/databases/wide-world-importers/wwi-integration-etl/Daily ETL/obj/Development/Project.params -samples/features/in-memory/ticket-reservations/DemoWorkload/obj/Debug/DemoWorkload.csproj.FileListAbsolute.txt -samples/features/in-memory/iot-smart-grid/Db/obj/Release/Model.xml -samples/applications/iot-smart-grid/DataGenerator/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs -samples/databases/wide-world-importers/workload-drivers/order-insert/MultithreadedOrderInsert/bin/Debug/MultithreadedOrderInsert.exe.config -samples/features/in-memory/iot-smart-grid/ConsoleClient/bin/Release/ConsoleClient.exe.config -*.jfm -samples/features/in-memory/ticket-reservations/TicketReservations/bin/Release/TicketReservations.publish.sql -samples/applications/iot-smart-grid/ConsoleClient/bin/Release/ConsoleClient.exe.config -samples/applications/iot-smart-grid/Db/Db.dbmdl -samples/databases/wide-world-importers/workload-drivers/order-insert/MultithreadedOrderInsert/obj/Release/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs -samples/databases/wide-world-importers/wwi-dw-ssdt/wwi-dw-ssdt/obj/Debug/Model.xml -samples/databases/wide-world-importers/wwi-dw-ssdt/wwi-dw-ssdt/WideWorldImportersDW.dbmdl -samples/features/in-memory/iot-smart-grid/WinFormsClient/bin/Release/Reports/PowerDashboard.pbix -samples/databases/wide-world-importers/wwi-ssdt/wwi-ssdt/WideWorldImporters.dbmdl -samples/databases/wide-world-importers/workload-drivers/order-insert/MultithreadedOrderInsert/obj/Debug/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs -samples/databases/wide-world-importers/wwi-ssasmd/wwi-ssasmd/bin/WWI-SSASMD.asdatabase -samples/applications/iot-smart-grid/Db/obj/Release/Db.sqlproj.FileListAbsolute.txt -*.manifest -samples/applications/iot-smart-grid/WinFormsClient/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs -samples/databases/wide-world-importers/wwi-ssasmd/wwi-ssasmd/obj/Development/IncrementalShapshot.xml -samples/applications/iot-smart-grid/ConsoleClient/bin/Release/Reports/PowerDashboard.pbix -samples/applications/iot-smart-grid/Db/obj/Release/Model.xml -samples/databases/wide-world-importers/workload-drivers/order-insert/MultithreadedOrderInsert/obj/Release/MultithreadedOrderInsert.csproj.FileListAbsolute.txt -samples/applications/iot-smart-grid/ConsoleClient/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs -samples/applications/iot-smart-grid/WinFormsClient/bin/Release/Client.exe.config -samples/databases/wide-world-importers/workload-drivers/order-insert/MultithreadedOrderInsert/bin/Release/MultithreadedOrderInsert.exe.config -samples/databases/wide-world-importers/workload-drivers/order-insert/MultithreadedOrderInsert/obj/Debug/MultithreadedInMemoryTableInsert.MultithreadedOrderInsertMain.resources -/samples/features/epm-framework/5.0/2Reporting/PolicyReports/PolicyEvaluationErrors.rdl.data -/samples/features/epm-framework/5.0/2Reporting/PolicyReports/PolicyEvaluationErrorDetails.rdl.data -/samples/features/epm-framework/5.0/2Reporting/PolicyReports/PolicyEvaluationDetails.rdl.data -/samples/features/epm-framework/5.0/2Reporting/PolicyReports/PolicyDetails.rdl.data -/samples/features/epm-framework/5.0/2Reporting/PolicyReports/PolicyDashboard.rdl.data -/samples/features/epm-framework/5.0/2Reporting/PolicyReports/bin/Debug -/samples/features/epm-framework/5.0/2Reporting/PolicyReports/PolicyDashboard - Backup.rdl -/samples/features/epm-framework/5.0/2Reporting/PolicyReports/PolicyDashboardFiltered.rdl.data +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ +out/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +# publish/ + +# Publish Web Output +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Typescript v1 declaration files +typings/ +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +/build +/Build +/deploy +/package +bin/ +obj/ +sql/ +*/obj/debug +**/Debug +#ignore thumbnails created by windows +Thumbs.db +#Ignore files build by Visual Studio +*.obj +*.pdb +*.user +*.aps +*.pch +*.docstates +*.vspscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.dbmdl +*.schemaview +*.tlh +*.cache +*.ilk +*.log +[Bb]in +[Dd]ebug*/ +*.lib +*.sbr +obj/ +[Rr]elease*/ +_ReSharper*/ +[Tt]est[Rr]esult* +*.docstates +*.swp +*.*~ +*.gpState +*.ReSharper* +*.preflight +*.nocommit +#Ignore Recovery Files made by Excel +~$*.xlsx +*.rdl.data + +*.jfm + +#====================================================================================== +# The below section could possibly be removed as these should be ignored by the above. +#====================================================================================== + +samples/in-memory/ticket-reservations/DemoWorkload/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs +*.nupkg +samples/in-memory/ticket-reservations/packages/CircularGauge.1.0.0/CreatePackageFile.bat +*.suo +samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/bin/Debug/PopulateAlwaysEncryptedData.exe +*.pdb +samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/bin/Debug/PopulateAlwaysEncryptedData.vshost.exe +*.Cache +samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/bin/Debug/PopulateAlwaysEncryptedData.exe.config +samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs +samples/databases/wide-world-importers/workload-drivers/vehicle-location-insert/MultithreadedInMemoryTableInsert/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs +*.exe +samples/features/in-memory/iot-smart-grid/ConsoleClient/bin/Release/DataGenerator.dll +samples/features/in-memory/iot-smart-grid/ConsoleClient/bin/Release/Reports/PowerDashboard.pbix +samples/databases/wide-world-importers/wwi-integration-etl/Daily ETL/bin/Development/Daily ETL.ispac +samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Release/PopulateAlwaysEncryptedData.csproj.FileListAbsolute.txt +samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/bin/Release/PopulateAlwaysEncryptedData.vshost.exe.config +samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Release/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs +*.dll +samples/features/in-memory/ticket-reservations/DemoWorkload/obj/Release/DemoWorkload.FrmConfig.resources +samples/features/in-memory/ticket-reservations/DemoWorkload/bin/Release/DemoWorkload.vshost.exe.config +samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Debug/PopulateAlwaysEncryptedData.csproj.FileListAbsolute.txt +samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/bin/Debug/PopulateAlwaysEncryptedData.vshost.exe.config +*.zip +samples/features/in-memory/ticket-reservations/packages/CircularGauge.1.0.0/CreatePackageFile.bat +samples/features/in-memory/ticket-reservations/TicketReservations/TicketReservations.dbmdl +samples/databases/wide-world-importers/workload-drivers/vehicle-location-insert/MultithreadedInMemoryTableInsert/obj/Release/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs +samples/databases/wide-world-importers/workload-drivers/vehicle-location-insert/MultithreadedInMemoryTableInsert/obj/Debug/MultithreadedInMemoryTableInsert.csproj.FileListAbsolute.txt +samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Debug/PopulateAlwaysEncryptedData.Properties.Resources.resources +samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Release/PopulateAlwaysEncryptedData.PopulateAlwaysEncryptedDataMain.resources +samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/bin/Debug/PopulateAlwaysEncryptedData.vshost.exe.manifest +samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Debug/PopulateAlwaysEncryptedData.PopulateAlwaysEncryptedDataMain.resources +samples/databases/wide-world-importers/workload-drivers/vehicle-location-insert/MultithreadedInMemoryTableInsert/obj/Release/MultithreadedInMemoryTableInsert.Properties.Resources.resources +samples/databases/wide-world-importers/sample-scripts/always-encrypted/PopulateAlwaysEncryptedData/obj/Release/PopulateAlwaysEncryptedData.Properties.Resources.resources +samples/features/in-memory/ticket-reservations/packages/CircularGauge.1.0.0/ReadMe.txt +*.dacpac +samples/features/in-memory/ticket-reservations/TicketReservations/obj/Release/Model.xml +samples/features/in-memory/ticket-reservations/DemoWorkload/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs +*.dat + +*.user +samples/features/in-memory/ticket-reservations/DemoWorkload/obj/Release/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs +samples/databases/wide-world-importers/workload-drivers/vehicle-location-insert/MultithreadedInMemoryTableInsert/obj/Debug/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs +samples/features/in-memory/ticket-reservations/DemoWorkload/bin/Debug/DemoWorkload.vshost.exe.config +samples/features/in-memory/ticket-reservations/DemoWorkload/obj/Release/DemoWorkload.Properties.Resources.resources +samples/databases/wide-world-importers/wwi-integration-etl/Daily ETL/obj/Development/Project.params +samples/features/in-memory/ticket-reservations/DemoWorkload/obj/Debug/DemoWorkload.csproj.FileListAbsolute.txt +samples/features/in-memory/iot-smart-grid/Db/obj/Release/Model.xml +samples/applications/iot-smart-grid/DataGenerator/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs +samples/databases/wide-world-importers/workload-drivers/order-insert/MultithreadedOrderInsert/bin/Debug/MultithreadedOrderInsert.exe.config +samples/features/in-memory/iot-smart-grid/ConsoleClient/bin/Release/ConsoleClient.exe.config +*.jfm +samples/features/in-memory/ticket-reservations/TicketReservations/bin/Release/TicketReservations.publish.sql +samples/applications/iot-smart-grid/ConsoleClient/bin/Release/ConsoleClient.exe.config +samples/applications/iot-smart-grid/Db/Db.dbmdl +samples/databases/wide-world-importers/workload-drivers/order-insert/MultithreadedOrderInsert/obj/Release/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs +samples/databases/wide-world-importers/wwi-dw-ssdt/wwi-dw-ssdt/obj/Debug/Model.xml +samples/databases/wide-world-importers/wwi-dw-ssdt/wwi-dw-ssdt/WideWorldImportersDW.dbmdl +samples/features/in-memory/iot-smart-grid/WinFormsClient/bin/Release/Reports/PowerDashboard.pbix +samples/databases/wide-world-importers/wwi-ssdt/wwi-ssdt/WideWorldImporters.dbmdl +samples/databases/wide-world-importers/workload-drivers/order-insert/MultithreadedOrderInsert/obj/Debug/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs +samples/databases/wide-world-importers/wwi-ssasmd/wwi-ssasmd/bin/WWI-SSASMD.asdatabase +samples/applications/iot-smart-grid/Db/obj/Release/Db.sqlproj.FileListAbsolute.txt +*.manifest +samples/applications/iot-smart-grid/WinFormsClient/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs +samples/databases/wide-world-importers/wwi-ssasmd/wwi-ssasmd/obj/Development/IncrementalShapshot.xml +samples/applications/iot-smart-grid/ConsoleClient/bin/Release/Reports/PowerDashboard.pbix +samples/applications/iot-smart-grid/Db/obj/Release/Model.xml +samples/databases/wide-world-importers/workload-drivers/order-insert/MultithreadedOrderInsert/obj/Release/MultithreadedOrderInsert.csproj.FileListAbsolute.txt +samples/applications/iot-smart-grid/ConsoleClient/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs +samples/applications/iot-smart-grid/WinFormsClient/bin/Release/Client.exe.config +samples/databases/wide-world-importers/workload-drivers/order-insert/MultithreadedOrderInsert/bin/Release/MultithreadedOrderInsert.exe.config +samples/databases/wide-world-importers/workload-drivers/order-insert/MultithreadedOrderInsert/obj/Debug/MultithreadedInMemoryTableInsert.MultithreadedOrderInsertMain.resources +/samples/features/epm-framework/5.0/2Reporting/PolicyReports/PolicyEvaluationErrors.rdl.data +/samples/features/epm-framework/5.0/2Reporting/PolicyReports/PolicyEvaluationErrorDetails.rdl.data +/samples/features/epm-framework/5.0/2Reporting/PolicyReports/PolicyEvaluationDetails.rdl.data +/samples/features/epm-framework/5.0/2Reporting/PolicyReports/PolicyDetails.rdl.data +/samples/features/epm-framework/5.0/2Reporting/PolicyReports/PolicyDashboard.rdl.data +/samples/features/epm-framework/5.0/2Reporting/PolicyReports/bin/Debug +/samples/features/epm-framework/5.0/2Reporting/PolicyReports/PolicyDashboard - Backup.rdl +/samples/features/epm-framework/5.0/2Reporting/PolicyReports/PolicyDashboardFiltered.rdl.data +samples/features/sql-management-objects/src/out/CodeCoverage/CodeCoverage.config diff --git a/samples/features/sql-management-objects/README.md b/samples/features/sql-management-objects/README.md index dea0dd75fb..e057e5e734 100644 --- a/samples/features/sql-management-objects/README.md +++ b/samples/features/sql-management-objects/README.md @@ -1,62 +1,62 @@ -# SmoSamples - -This unit test project is meant to demonstrate features of the Sql Management Objects framework and to help developers optimize performance of their SMO-based applications. - - -### Contents - -[About this sample](#about-this-sample)
-[Before you begin](#before-you-begin)
-[Run this sample](#run-this-sample)
-[Sample details](#sample-details)
-[Disclaimers](#disclaimers)
-[Related links](#related-links)
- - - - -## About this sample - - -- **Applies to:** SQL Server 2016 (or higher), Azure SQL Database, Azure SQL Data Warehouse -- **Key features:** -- Unit tests and a docker file that demonstrate proper use of SMO features against a working SQL Server instance. -- **Programming Language:** -- C# - - - -## Before you begin - -To run this sample, you need the following prerequisites. - -**Software prerequisites:** - -1. SQL Server 2016 (or higher) or an Azure SQL Database with the full WideWorldImporters sample database, or -2. Docker -3. At minimum the dotnet 2.2 SDK, or Visual Studio 2017 - - - -## Run this sample - - - - -## Sample details - -Each unit test demonstrates a specific aspect of SMO-based application development, either in isolation or in conjunction with other SMO components.
-Feature areas tested include: -1. Efficient use of collections -2. Sql query capture -3. Events -4. URNs -5. Script generation - - - - -## Related Links -The SMO NuGet package is at https://www.nuget.org/packages/Microsoft.SqlServer.SqlManagementObjects/
-Documentation for the APIs is at https://docs.microsoft.com/en-us/sql/relational-databases/server-management-objects-smo/overview-smo
+# SmoSamples + +This unit test project is meant to demonstrate features of the Sql Management Objects framework and to help developers optimize performance of their SMO-based applications. + + +### Contents + +[About this sample](#about-this-sample)
+[Before you begin](#before-you-begin)
+[Run this sample](#run-this-sample)
+[Sample details](#sample-details)
+[Disclaimers](#disclaimers)
+[Related links](#related-links)
+ + + + +## About this sample + + +- **Applies to:** SQL Server 2016 (or higher), Azure SQL Database, Azure SQL Data Warehouse +- **Key features:** +- Unit tests and a docker file that demonstrate proper use of SMO features against a working SQL Server instance. +- **Programming Language:** +- C# + + + +## Before you begin + +To run this sample, you need the following prerequisites. + +**Software prerequisites:** + +1. SQL Server 2016 (or higher) or an Azure SQL Database with the full WideWorldImporters sample database, or +2. Docker +3. At minimum the dotnet 2.2 SDK, or Visual Studio 2017 + + + +## Run this sample +If using Docker, use runtests.sh or runtests.cmd as appropriate. If using a separate instance of SQL Server or Azure SQL Database, create a .runsettings file and run the unit tests using Visual Studio or "dotnet vstest". + + + +## Sample details + +Each unit test demonstrates a specific aspect of SMO-based application development, either in isolation or in conjunction with other SMO components.
+Feature areas tested include: +1. Efficient use of collections +2. Sql query capture +3. Events +4. URNs +5. Script generation + + + + +## Related Links +The SMO NuGet package is at https://www.nuget.org/packages/Microsoft.SqlServer.SqlManagementObjects/
+Documentation for the APIs is at https://docs.microsoft.com/sql/relational-databases/server-management-objects-smo/overview-smo
The WideWorldImporters sample database can be found at https://github.com/Microsoft/sql-server-samples/releases/download/wide-world-importers-v1.0/WideWorldImporters-Full.bak
\ No newline at end of file diff --git a/samples/features/sql-management-objects/prep/dockerfile b/samples/features/sql-management-objects/prep/dockerfile index af28e1f2d1..70dcbbd587 100644 --- a/samples/features/sql-management-objects/prep/dockerfile +++ b/samples/features/sql-management-objects/prep/dockerfile @@ -1,8 +1,8 @@ -FROM mcr.microsoft.com/mssql/server:2017-latest -WORKDIR /tmp/backup -RUN wget -q https://github.com/Microsoft/sql-server-samples/releases/download/wide-world-importers-v1.0/WideWorldImporters-Full.bak -COPY restore.sql . -COPY restore.sh . -COPY entrypoint.sh . -CMD ["/bin/bash", "/tmp/backup/entrypoint.sh"] - +FROM mcr.microsoft.com/mssql/server:2017-latest +WORKDIR /tmp/backup +RUN wget -q https://github.com/Microsoft/sql-server-samples/releases/download/wide-world-importers-v1.0/WideWorldImporters-Full.bak +COPY restore.sql . +COPY restore.sh . +COPY entrypoint.sh . +CMD ["/bin/bash", "/tmp/backup/entrypoint.sh"] + diff --git a/samples/features/sql-management-objects/prep/restore.sh b/samples/features/sql-management-objects/prep/restore.sh index 52532d1732..d6d347029d 100644 --- a/samples/features/sql-management-objects/prep/restore.sh +++ b/samples/features/sql-management-objects/prep/restore.sh @@ -1,3 +1,4 @@ +# Wait for SQL Server to start and be ready to accept connections sleep 35s echo sa_password is $SA_PASSWORD /opt/mssql-tools/bin/sqlcmd -S . -U sa -P $SA_PASSWORD -i /tmp/backup/restore.sql diff --git a/samples/features/sql-management-objects/prep/restore.sql b/samples/features/sql-management-objects/prep/restore.sql index b4ec6e8c3f..4e1fddd1de 100644 --- a/samples/features/sql-management-objects/prep/restore.sql +++ b/samples/features/sql-management-objects/prep/restore.sql @@ -1,5 +1,5 @@ -RESTORE DATABASE WideWorldImporters FROM DISK = "/tmp/backup/WideWorldImporters-Full.bak" -WITH MOVE "WWI_Primary" TO "/var/opt/mssql/data/WideWorldImporters.mdf", -MOVE "WWI_Userdata" TO "/var/opt/mssql/data/WideWorldImporters_UserData.ndf", -MOVE "WWI_Log" TO "/var/opt/mssql/data/WideWorldImporters.ldf", MOVE "WWI_InMemory_Data_1" +RESTORE DATABASE WideWorldImporters FROM DISK = "/tmp/backup/WideWorldImporters-Full.bak" +WITH MOVE "WWI_Primary" TO "/var/opt/mssql/data/WideWorldImporters.mdf", +MOVE "WWI_Userdata" TO "/var/opt/mssql/data/WideWorldImporters_UserData.ndf", +MOVE "WWI_Log" TO "/var/opt/mssql/data/WideWorldImporters.ldf", MOVE "WWI_InMemory_Data_1" TO "/var/opt/mssql/data/WideWorldImporters_InMemory_Data_1" \ No newline at end of file diff --git a/samples/features/sql-management-objects/src/CollectionSamples.cs b/samples/features/sql-management-objects/src/CollectionSamples.cs index c9ac297d35..e8cc35ce05 100644 --- a/samples/features/sql-management-objects/src/CollectionSamples.cs +++ b/samples/features/sql-management-objects/src/CollectionSamples.cs @@ -1,12 +1,10 @@ - -using System.Diagnostics; -using Microsoft.SqlServer.Management.Smo; - -namespace Microsoft.SqlServer.SmoSamples +namespace Microsoft.SqlServer.SmoSamples { using System; using System.Collections.Generic; + using System.Diagnostics; using System.Text; + using Microsoft.SqlServer.Management.Smo; using Microsoft.VisualStudio.TestTools.UnitTesting; using NUnit.Framework; using Assert = NUnit.Framework.Assert; @@ -16,6 +14,12 @@ public class CollectionSamples { public VisualStudio.TestTools.UnitTesting.TestContext TestContext { get; set; } + /// + /// SetDefaultInitFields tells the Server object which properties to include in the initial query + /// to populate of a given object type when initialized a collection of that type. + /// The test demonstrates the effect of using this call to enumerate Tables when accessing the FileGroup + /// property of each Table object + /// [TestMethod] public void Collection_iteration_is_faster_with_SetDefaultInitFields() { @@ -26,11 +30,14 @@ public void Collection_iteration_is_faster_with_SetDefaultInitFields() connectionMetrics.Reset(); foreach (Table table in database.Tables) { + // Accessing FileGroup triggers a query to fetch it Trace.TraceInformation( $"Unoptimized table Name: {table.Name}\tSchema:{table.Schema}\tFileGroup:{table.FileGroup}"); } - var unoptimizedMetrics = (connectionMetrics.QueryCount, connectionMetrics.BytesSent, connectionMetrics.BytesRead, connectionMetrics.ConnectionCount); + var unoptimizedMetrics = (QueryCount: connectionMetrics.QueryCount, + BytesSent: connectionMetrics.BytesSent, BytesRead: connectionMetrics.BytesRead, + ConnectionCount: connectionMetrics.ConnectionCount); Trace.TraceInformation(string.Join($"{Environment.NewLine}\t", new[] { "Unoptimized metrics:", @@ -43,11 +50,14 @@ public void Collection_iteration_is_faster_with_SetDefaultInitFields() database.Tables.Refresh(); foreach (Table table in database.Tables) { + // The FileGroup property is already populated, so no extra query is needed Trace.TraceInformation( $"Optimized table Name: {table.Name}\tSchema:{table.Schema}\tFileGroup:{table.FileGroup}"); } - var optimizedMetrics = (connectionMetrics.QueryCount, connectionMetrics.BytesSent, connectionMetrics.BytesRead, connectionMetrics.ConnectionCount); + var optimizedMetrics = (QueryCount: connectionMetrics.QueryCount, + BytesSent: connectionMetrics.BytesSent, BytesRead: connectionMetrics.BytesRead, + ConnectionCount: connectionMetrics.ConnectionCount); Trace.TraceInformation(string.Join($"{Environment.NewLine}\t", new[] { "Optimized Metrics:", diff --git a/samples/features/sql-management-objects/src/ConnectionHelpers.cs b/samples/features/sql-management-objects/src/ConnectionHelpers.cs index b738d16347..f19887407e 100644 --- a/samples/features/sql-management-objects/src/ConnectionHelpers.cs +++ b/samples/features/sql-management-objects/src/ConnectionHelpers.cs @@ -1,135 +1,150 @@ -using Microsoft.SqlServer.Management.Common; -using Microsoft.SqlServer.Management.Smo; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Diagnostics; -using System.Reflection; -using System.Text; -using Assert = NUnit.Framework.Assert; -namespace Microsoft.SqlServer.SmoSamples -{ - // Used by test classes to initialize and retrieve a ServerConnection for use in the tests themselves - static class ConnectionHelpers - { - - public static ServerConnection GetTestConnection(this VisualStudio.TestTools.UnitTesting.TestContext context, ConnectionType connectionType = ConnectionType.Default) - { - var connectionString = context.GetConnectionString(); - var connectionStrBuilder = new SqlConnectionStringBuilder(connectionString); - var instanceName = connectionStrBuilder.DataSource; - var sqlServerLogin = connectionStrBuilder.UserID; - var password = connectionStrBuilder.Password; - if (connectionType == ConnectionType.SqlConnection) - { - return new ServerConnection(new SqlConnection(connectionString)); - } - if (connectionType == ConnectionType.Integrated) - { - return new ServerConnection(instanceName); - } - if (connectionType == ConnectionType.SqlAuth ) - { - if (string.IsNullOrWhiteSpace(sqlServerLogin) || string.IsNullOrWhiteSpace(password)) - { - throw new ArgumentException("username and password values are missing from test connection string"); - } - return new ServerConnection(instanceName, sqlServerLogin, password); - } - if (string.IsNullOrEmpty(sqlServerLogin)) - { - return new ServerConnection(instanceName); - } - return new ServerConnection(instanceName, sqlServerLogin, password); - } - - public static string GetConnectionString(this VisualStudio.TestTools.UnitTesting.TestContext context) - { - var connectionString = context.Properties["connectionString"].ToString(); - Assert.That(connectionString, Is.Not.Empty, "connectionString must be set"); - connectionString = connectionString.Replace("[hostname]", Environment.GetEnvironmentVariable("TEST_HOSTNAME")). - Replace("[username]", Environment.GetEnvironmentVariable("TEST_USERNAME")). - Replace("[password]", Environment.GetEnvironmentVariable("TEST_PASSWORD")). - Replace("[database]", Environment.GetEnvironmentVariable("TEST_DATABASE")); - Console.WriteLine("Connection string: {0}", connectionString); - return connectionString; - } - - /// - /// Returns the name of the database to use for the tests - /// - /// - public static string GetTestDatabaseName(this VisualStudio.TestTools.UnitTesting.TestContext context) - { - var databaseName = Environment.GetEnvironmentVariable("TEST_DATABASE"); - if (string.IsNullOrEmpty(databaseName)) - { - databaseName = context.Properties["testDatabase"].ToString(); - } - Assert.That(databaseName, Is.Not.Empty, "testDatabase must be set"); - Console.WriteLine("Test database: {0}", databaseName); - return databaseName; - } - - /// - /// Returns the folder where result files should be written - /// - /// - /// - public static string GetResultsFolder(this VisualStudio.TestTools.UnitTesting.TestContext context) - { - var path = Environment.GetEnvironmentVariable("RESULTS_FOLDER"); - if (string.IsNullOrEmpty(path)) - { - path = context.Properties.ContainsKey("resultsFolder") ? context.Properties["resultsFolder"].ToString() : null; - } - if (string.IsNullOrEmpty(path)) - { - path = PathWrapper.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - path = PathWrapper.Combine(path, "results"); - } - return path; - } - - /// - /// creates a new database with a random name, runs the action, and drops the database - /// - /// - /// - /// - public static void ExecuteWithDbDrop(this VisualStudio.TestTools.UnitTesting.TestContext context, Action action, Action preCreateAction = null) - { - var dbName = string.Format("{0}{1}", context.TestName, new Random().Next()); - var serverConnection = context.GetTestConnection(); - var server = new Management.Smo.Server(serverConnection); - var database = new Database(server, dbName); - preCreateAction?.Invoke(database); - database.Create(); - try - { - action(database); - } - finally - { - try - { - database.Drop(); - } - catch (Exception e) - { - Trace.TraceError("Unable to drop database {0}: {1}", dbName, e); - } - } - } - } - - enum ConnectionType - { - Default, // whatever is specified in the config - Integrated, // integrated auth - SqlAuth, // SQL auth - SqlConnection // Create a SqlConnection first from the connection string - } -} +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Diagnostics; +using System.Reflection; +using System.Text; +using Assert = NUnit.Framework.Assert; +namespace Microsoft.SqlServer.SmoSamples +{ + /// + /// Used by test classes to initialize and retrieve a ServerConnection for use in the tests themselves + /// + static class ConnectionHelpers + { + + /// + /// Returns a ServerConnection based on the connectionString parameter defined in the runsettings file + /// + public static ServerConnection GetTestConnection(this VisualStudio.TestTools.UnitTesting.TestContext context, ConnectionType connectionType = ConnectionType.Default) + { + var connectionString = context.GetConnectionString(); + var connectionStrBuilder = new SqlConnectionStringBuilder(connectionString); + var instanceName = connectionStrBuilder.DataSource; + var sqlServerLogin = connectionStrBuilder.UserID; + var password = connectionStrBuilder.Password; + if (connectionType == ConnectionType.SqlConnection) + { + return new ServerConnection(new SqlConnection(connectionString)); + } + if (connectionType == ConnectionType.Integrated) + { + return new ServerConnection(instanceName); + } + if (connectionType == ConnectionType.SqlAuth ) + { + if (string.IsNullOrWhiteSpace(sqlServerLogin) || string.IsNullOrWhiteSpace(password)) + { + throw new ArgumentException("username and password values are missing from test connection string"); + } + return new ServerConnection(instanceName, sqlServerLogin, password); + } + if (string.IsNullOrEmpty(sqlServerLogin)) + { + return new ServerConnection(instanceName); + } + return new ServerConnection(instanceName, sqlServerLogin, password); + } + + /// + /// Returns a connection string based on the connectionString parameter defined in the runsettings file + /// Placeholders may be included in the connection string if the caller has set corresponding environment variables. + /// [hostname] -> TEST_HOSTNAME environment variable + /// [username] -> TEST_USERNAME + /// [password] -> TEST_PASSWORD + /// [database] -> TEST_DATABASE + /// + public static string GetConnectionString(this VisualStudio.TestTools.UnitTesting.TestContext context) + { + var connectionString = context.Properties["connectionString"].ToString(); + Assert.That(connectionString, Is.Not.Empty, "connectionString must be set"); + connectionString = connectionString.Replace("[hostname]", Environment.GetEnvironmentVariable("TEST_HOSTNAME")). + Replace("[username]", Environment.GetEnvironmentVariable("TEST_USERNAME")). + Replace("[password]", Environment.GetEnvironmentVariable("TEST_PASSWORD")). + Replace("[database]", Environment.GetEnvironmentVariable("TEST_DATABASE")); + Console.WriteLine("Connection string: {0}", connectionString); + return connectionString; + } + + /// + /// Returns the name of the database to use for the tests + /// If TEST_DATABASE environment variable is set, that value is used, otherwise the + /// testDatabase parameter from runsettings is used. + /// + /// + public static string GetTestDatabaseName(this VisualStudio.TestTools.UnitTesting.TestContext context) + { + var databaseName = Environment.GetEnvironmentVariable("TEST_DATABASE"); + if (string.IsNullOrEmpty(databaseName)) + { + databaseName = context.Properties["testDatabase"].ToString(); + } + Assert.That(databaseName, Is.Not.Empty, "testDatabase must be set"); + Console.WriteLine("Test database: {0}", databaseName); + return databaseName; + } + + /// + /// Returns the folder where result files should be written + /// + /// + /// + public static string GetResultsFolder(this VisualStudio.TestTools.UnitTesting.TestContext context) + { + var path = Environment.GetEnvironmentVariable("RESULTS_FOLDER"); + if (string.IsNullOrEmpty(path)) + { + path = context.Properties.ContainsKey("resultsFolder") ? context.Properties["resultsFolder"].ToString() : null; + } + if (string.IsNullOrEmpty(path)) + { + path = PathWrapper.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + path = PathWrapper.Combine(path, "results"); + } + return path; + } + + /// + /// creates a new database with a random name, runs the action, and drops the database + /// + /// + /// + /// + public static void ExecuteWithDbDrop(this VisualStudio.TestTools.UnitTesting.TestContext context, Action action, Action preCreateAction = null) + { + var dbName = string.Format("{0}{1}", context.TestName, new Random().Next()); + var serverConnection = context.GetTestConnection(); + var server = new Management.Smo.Server(serverConnection); + var database = new Database(server, dbName); + preCreateAction?.Invoke(database); + database.Create(); + try + { + action(database); + } + finally + { + try + { + database.Drop(); + } + catch (Exception e) + { + Trace.TraceError("Unable to drop database {0}: {1}", dbName, e); + } + } + } + } + + enum ConnectionType + { + Default, // whatever is specified in the config + Integrated, // integrated auth + SqlAuth, // SQL auth + SqlConnection // Create a SqlConnection first from the connection string + } +} diff --git a/samples/features/sql-management-objects/src/ConnectionMetrics.cs b/samples/features/sql-management-objects/src/ConnectionMetrics.cs index 1811d6838d..0216484403 100644 --- a/samples/features/sql-management-objects/src/ConnectionMetrics.cs +++ b/samples/features/sql-management-objects/src/ConnectionMetrics.cs @@ -1,93 +1,93 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Diagnostics; -using System.Text; -using System.Threading; -using Microsoft.SqlServer.Management.Common; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Microsoft.SqlServer.SmoSamples -{ - class ConnectionMetrics : IDisposable - { - public int ConnectionCount; - public long BytesRead; - public long BytesSent; - public int QueryCount; - public readonly ServerConnection ServerConnection; - private readonly GenericSqlProxy proxy; - - public ConnectionMetrics(ServerConnection serverConnection, GenericSqlProxy proxy) - { - this.proxy = proxy; - ServerConnection = serverConnection; - proxy.OnConnect += Proxy_OnConnect; - proxy.OnWriteHost += Proxy_OnWriteHost; - proxy.OnWriteClient += Proxy_OnWriteClient; - serverConnection.StatementExecuted += ServerConnection_StatementExecuted; - } - - public void Reset() - { - ConnectionCount = 0; - BytesRead = BytesSent = 0; - QueryCount = 0; - } - - private void ServerConnection_StatementExecuted(object sender, StatementEventArgs e) - { - QueryCount++; - } - - private void Proxy_OnWriteClient(object sender, StreamWriteEventArgs e) - { - BytesRead += e.BytesWritten; - } - - private void Proxy_OnWriteHost(object sender, StreamWriteEventArgs e) - { - BytesSent += e.BytesWritten; - } - - private void Proxy_OnConnect(object sender, ProxyConnectionEventArgs e) - { - ConnectionCount++; - } - - public void Dispose() - { - proxy.OnConnect -= Proxy_OnConnect; - proxy.OnWriteHost -= Proxy_OnWriteHost; - proxy.OnWriteClient -= Proxy_OnWriteClient; - ServerConnection.StatementExecuted -= ServerConnection_StatementExecuted; - ServerConnection.SqlConnectionObject.Dispose(); - proxy.Dispose(); - } - - public static ConnectionMetrics SetupMeasuredConnection(TestContext testContext, int latencyPaddingMs = 0) - { - var connectionString = testContext.GetConnectionString(); - var proxy = new GenericSqlProxy(connectionString); - if (latencyPaddingMs > 0) - { - proxy.OnWriteClient += (o,e) => DelayWrite(latencyPaddingMs, e); - } - // If running these tests in a container you may need to set a specific port - // and expose that port in the dockerfile - var port = testContext.Properties.ContainsKey("proxyPort") - ? Convert.ToInt32(testContext.Properties["proxyPort"]) - : 0; - var sqlConnection = new SqlConnection(proxy.Initialize(port)); - var serverConnection = new ServerConnection(sqlConnection); - return new ConnectionMetrics(serverConnection, proxy); - } - - static void DelayWrite(long delay, StreamWriteEventArgs args) - { - Thread.Sleep(Convert.ToInt32(delay)); - } - } - - -} +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Diagnostics; +using System.Text; +using System.Threading; +using Microsoft.SqlServer.Management.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.SqlServer.SmoSamples +{ + class ConnectionMetrics : IDisposable + { + public int ConnectionCount; + public long BytesRead; + public long BytesSent; + public int QueryCount; + public readonly ServerConnection ServerConnection; + private readonly GenericSqlProxy proxy; + + public ConnectionMetrics(ServerConnection serverConnection, GenericSqlProxy proxy) + { + this.proxy = proxy; + ServerConnection = serverConnection; + proxy.OnConnect += Proxy_OnConnect; + proxy.OnWriteHost += Proxy_OnWriteHost; + proxy.OnWriteClient += Proxy_OnWriteClient; + serverConnection.StatementExecuted += ServerConnection_StatementExecuted; + } + + public void Reset() + { + ConnectionCount = 0; + BytesRead = BytesSent = 0; + QueryCount = 0; + } + + private void ServerConnection_StatementExecuted(object sender, StatementEventArgs e) + { + QueryCount++; + } + + private void Proxy_OnWriteClient(object sender, StreamWriteEventArgs e) + { + BytesRead += e.BytesWritten; + } + + private void Proxy_OnWriteHost(object sender, StreamWriteEventArgs e) + { + BytesSent += e.BytesWritten; + } + + private void Proxy_OnConnect(object sender, ProxyConnectionEventArgs e) + { + ConnectionCount++; + } + + public void Dispose() + { + proxy.OnConnect -= Proxy_OnConnect; + proxy.OnWriteHost -= Proxy_OnWriteHost; + proxy.OnWriteClient -= Proxy_OnWriteClient; + ServerConnection.StatementExecuted -= ServerConnection_StatementExecuted; + ServerConnection.SqlConnectionObject.Dispose(); + proxy.Dispose(); + } + + public static ConnectionMetrics SetupMeasuredConnection(TestContext testContext, int latencyPaddingMs = 0) + { + var connectionString = testContext.GetConnectionString(); + var proxy = new GenericSqlProxy(connectionString); + if (latencyPaddingMs > 0) + { + proxy.OnWriteClient += (o,e) => DelayWrite(latencyPaddingMs, e); + } + // If running these tests in a container you may need to set a specific port + // and expose that port in the dockerfile + var port = testContext.Properties.ContainsKey("proxyPort") + ? Convert.ToInt32(testContext.Properties["proxyPort"]) + : 0; + var sqlConnection = new SqlConnection(proxy.Initialize(port)); + var serverConnection = new ServerConnection(sqlConnection); + return new ConnectionMetrics(serverConnection, proxy); + } + + static void DelayWrite(long delay, StreamWriteEventArgs args) + { + Thread.Sleep(Convert.ToInt32(delay)); + } + } + + +} diff --git a/samples/features/sql-management-objects/src/GenericSqlProxy.cs b/samples/features/sql-management-objects/src/GenericSqlProxy.cs index dce9231de2..eac8626688 100644 --- a/samples/features/sql-management-objects/src/GenericSqlProxy.cs +++ b/samples/features/sql-management-objects/src/GenericSqlProxy.cs @@ -1,220 +1,220 @@ -using System; -using System.Data.SqlClient; -using System.Net.Sockets; -using System.Net; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.SqlServer.SmoSamples -{ - /// - /// Provides an in-memory proxy with callbacks that allow tests to run code before transmission and after receipt of - /// data on the wire - /// - [DebuggerDisplay("{connectionString}:[{Port}]")] - class GenericSqlProxy : IDisposable - { - // We pick a buffer size that's large enough to hold most single replies so we don't over-inject latency - private const int BufferSizeBytes = 128 * 1024; - readonly string connectionString; - volatile bool disposed; - private TcpListener listener = null; - private readonly CancellationTokenSource tokenSource = new CancellationTokenSource(); - - /// - /// Constructs a GenericSqlProxy for the local default sql instance - /// - public GenericSqlProxy() : this(".") - { - - } - - /// - /// Construct a new GenericSqlProxy for the given connection string - /// - /// - public GenericSqlProxy(string connectionString) - { - this.connectionString = connectionString; - } - - public int Port { get; private set; } - - /// - /// Initializes the proxy by opening the TCP listener and copying data between client and server - /// - /// local port number to use. 0 will use a random port - /// The connection string to use for the SqlConnection - public string Initialize(int localPort = 0) - { - var builder = new SqlConnectionStringBuilder(connectionString); - GetTcpInfoFromDataSource(builder.DataSource, out string hostName, out int port); - listener = new TcpListener(IPAddress.Loopback, localPort); - listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - listener.Start(); - Port = ((IPEndPoint) listener.LocalEndpoint).Port; - Trace.TraceInformation($"Starting TcpListener on port {Port}"); - Task.Factory.StartNew(() => { AsyncInit(listener, hostName, port); }); - return new SqlConnectionStringBuilder(builder.ConnectionString) - { - DataSource = $"tcp:127.0.0.1,{Port}" - }.ConnectionString; - } - - private void AsyncInit(TcpListener tcpListener, string hostName, int port) - { - - while (!disposed) - { - var accept = tcpListener.AcceptTcpClientAsync(); - if (accept.Wait(1000, tokenSource.Token) && !tokenSource.IsCancellationRequested) - { - var localClient = accept.GetAwaiter().GetResult(); - OnConnect?.Invoke(this, new ProxyConnectionEventArgs(localClient)); - var remoteClient = new TcpClient() {NoDelay = true}; - tokenSource.Token.Register(() => - { - localClient.Dispose(); - remoteClient.Dispose(); - }); - remoteClient.ConnectAsync(hostName, port).Wait(tokenSource.Token); - if (!tokenSource.IsCancellationRequested) - { - - - Task.Factory.StartNew(() => { ForwardToSql(localClient, remoteClient); }); - Task.Factory.StartNew(() => { ForwardToClient(localClient, remoteClient); }); - } - else - { - Trace.TraceInformation("AsyncInit aborted due to cancellation token set"); - } - } - } - } - - /// - /// Fires before the proxy writes a buffer to the host - /// - public event EventHandler OnWriteHost; - - /// - /// Fires before the proxy writes a buffer to the client - /// - public event EventHandler OnWriteClient; - - /// - /// Fires when a new connection to the proxy's port is accepted - /// - public event EventHandler OnConnect; - - private void ForwardToSql(TcpClient ourClient, TcpClient sqlClient) - { - long index = 0; - try - { - while (!disposed) - { - byte[] buffer = new byte[BufferSizeBytes]; - int bytesRead = ourClient.GetStream().ReadAsync(buffer, 0, buffer.Length, tokenSource.Token).Result; - if (!tokenSource.Token.IsCancellationRequested) - { - OnWriteHost?.Invoke(this, new StreamWriteEventArgs(index++, buffer, bytesRead)); - sqlClient.GetStream().Write(buffer, 0, bytesRead); - } - } - } - catch (Exception) - { - if (!disposed) - { - throw; - } - } - finally - { - Trace.TraceInformation("ForwardToSql exiting"); - } - } - - private void ForwardToClient(TcpClient ourClient, TcpClient sqlClient) - { - long index = 0; - try - { - while (!disposed) - { - byte[] buffer = new byte[BufferSizeBytes]; - int bytesRead = sqlClient.GetStream().ReadAsync(buffer, 0, buffer.Length, tokenSource.Token).Result; - if (!tokenSource.Token.IsCancellationRequested) - { - OnWriteClient?.Invoke(this, new StreamWriteEventArgs(index++, buffer, bytesRead)); - ourClient.GetStream().Write(buffer, 0, bytesRead); - } - } - } - catch (Exception) - { - if (!disposed) - { - throw; - } - } - finally - { - Trace.TraceInformation("ForwardToClient exiting"); - } - } - - private static void GetTcpInfoFromDataSource(string dataSource, out string hostName, out int port) - { - string[] dataSourceParts = dataSource.Split(','); - if (dataSourceParts.Length == 1) - { - hostName = dataSourceParts[0].Replace("tcp:", ""); - port = 1433; - } - else if (dataSourceParts.Length == 2) - { - hostName = dataSourceParts[0].Replace("tcp:", ""); - port = int.Parse(dataSourceParts[1]); - } - else - { - throw new InvalidOperationException("TCP Connection String not in correct format!"); - } - } - - public void Dispose() - { - disposed = true; - tokenSource.Cancel(); - Trace.TraceInformation("Disposing TcpListener on port {0}", Port); - listener?.Stop(); - } - } - - public class StreamWriteEventArgs : EventArgs - { - public StreamWriteEventArgs(long index, byte[]buffer, int bytesWritten) - { - Index = index; - Buffer = buffer; - BytesWritten = bytesWritten; - } - public long Index; - public byte[] Buffer; - public int BytesWritten; - } - - public class ProxyConnectionEventArgs : EventArgs - { - public ProxyConnectionEventArgs(TcpClient client) - { - Client = client; - } - public TcpClient Client; - } -} +using System; +using System.Data.SqlClient; +using System.Net.Sockets; +using System.Net; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.SqlServer.SmoSamples +{ + /// + /// Provides an in-memory proxy with callbacks that allow tests to run code before transmission and after receipt of + /// data on the wire + /// + [DebuggerDisplay("{connectionString}:[{Port}]")] + class GenericSqlProxy : IDisposable + { + // We pick a buffer size that's large enough to hold most single replies so we don't over-inject latency + private const int BufferSizeBytes = 128 * 1024; + readonly string connectionString; + volatile bool disposed; + private TcpListener listener = null; + private readonly CancellationTokenSource tokenSource = new CancellationTokenSource(); + + /// + /// Constructs a GenericSqlProxy for the local default sql instance + /// + public GenericSqlProxy() : this(".") + { + + } + + /// + /// Construct a new GenericSqlProxy for the given connection string + /// + /// + public GenericSqlProxy(string connectionString) + { + this.connectionString = connectionString; + } + + public int Port { get; private set; } + + /// + /// Initializes the proxy by opening the TCP listener and copying data between client and server + /// + /// local port number to use. 0 will use a random port + /// The connection string to use for the SqlConnection + public string Initialize(int localPort = 0) + { + var builder = new SqlConnectionStringBuilder(connectionString); + GetTcpInfoFromDataSource(builder.DataSource, out string hostName, out int port); + listener = new TcpListener(IPAddress.Loopback, localPort); + listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); + listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + listener.Start(); + Port = ((IPEndPoint) listener.LocalEndpoint).Port; + Trace.TraceInformation($"Starting TcpListener on port {Port}"); + Task.Factory.StartNew(() => { AsyncInit(listener, hostName, port); }); + return new SqlConnectionStringBuilder(builder.ConnectionString) + { + DataSource = $"tcp:127.0.0.1,{Port}" + }.ConnectionString; + } + + private void AsyncInit(TcpListener tcpListener, string hostName, int port) + { + + while (!disposed) + { + var accept = tcpListener.AcceptTcpClientAsync(); + if (accept.Wait(1000, tokenSource.Token) && !tokenSource.IsCancellationRequested) + { + var localClient = accept.GetAwaiter().GetResult(); + OnConnect?.Invoke(this, new ProxyConnectionEventArgs(localClient)); + var remoteClient = new TcpClient() {NoDelay = true}; + tokenSource.Token.Register(() => + { + localClient.Dispose(); + remoteClient.Dispose(); + }); + remoteClient.ConnectAsync(hostName, port).Wait(tokenSource.Token); + if (!tokenSource.IsCancellationRequested) + { + + + Task.Factory.StartNew(() => { ForwardToSql(localClient, remoteClient); }); + Task.Factory.StartNew(() => { ForwardToClient(localClient, remoteClient); }); + } + else + { + Trace.TraceInformation("AsyncInit aborted due to cancellation token set"); + } + } + } + } + + /// + /// Fires before the proxy writes a buffer to the host + /// + public event EventHandler OnWriteHost; + + /// + /// Fires before the proxy writes a buffer to the client + /// + public event EventHandler OnWriteClient; + + /// + /// Fires when a new connection to the proxy's port is accepted + /// + public event EventHandler OnConnect; + + private void ForwardToSql(TcpClient ourClient, TcpClient sqlClient) + { + long index = 0; + try + { + while (!disposed) + { + byte[] buffer = new byte[BufferSizeBytes]; + int bytesRead = ourClient.GetStream().ReadAsync(buffer, 0, buffer.Length, tokenSource.Token).Result; + if (!tokenSource.Token.IsCancellationRequested) + { + OnWriteHost?.Invoke(this, new StreamWriteEventArgs(index++, buffer, bytesRead)); + sqlClient.GetStream().Write(buffer, 0, bytesRead); + } + } + } + catch (Exception) + { + if (!disposed) + { + throw; + } + } + finally + { + Trace.TraceInformation("ForwardToSql exiting"); + } + } + + private void ForwardToClient(TcpClient ourClient, TcpClient sqlClient) + { + long index = 0; + try + { + while (!disposed) + { + byte[] buffer = new byte[BufferSizeBytes]; + int bytesRead = sqlClient.GetStream().ReadAsync(buffer, 0, buffer.Length, tokenSource.Token).Result; + if (!tokenSource.Token.IsCancellationRequested) + { + OnWriteClient?.Invoke(this, new StreamWriteEventArgs(index++, buffer, bytesRead)); + ourClient.GetStream().Write(buffer, 0, bytesRead); + } + } + } + catch (Exception) + { + if (!disposed) + { + throw; + } + } + finally + { + Trace.TraceInformation("ForwardToClient exiting"); + } + } + + private static void GetTcpInfoFromDataSource(string dataSource, out string hostName, out int port) + { + string[] dataSourceParts = dataSource.Split(','); + if (dataSourceParts.Length == 1) + { + hostName = dataSourceParts[0].Replace("tcp:", ""); + port = 1433; + } + else if (dataSourceParts.Length == 2) + { + hostName = dataSourceParts[0].Replace("tcp:", ""); + port = int.Parse(dataSourceParts[1]); + } + else + { + throw new InvalidOperationException("TCP Connection String not in correct format!"); + } + } + + public void Dispose() + { + disposed = true; + tokenSource.Cancel(); + Trace.TraceInformation("Disposing TcpListener on port {0}", Port); + listener?.Stop(); + } + } + + public class StreamWriteEventArgs : EventArgs + { + public StreamWriteEventArgs(long index, byte[]buffer, int bytesWritten) + { + Index = index; + Buffer = buffer; + BytesWritten = bytesWritten; + } + public long Index; + public byte[] Buffer; + public int BytesWritten; + } + + public class ProxyConnectionEventArgs : EventArgs + { + public ProxyConnectionEventArgs(TcpClient client) + { + Client = client; + } + public TcpClient Client; + } +} diff --git a/samples/features/sql-management-objects/src/SmoSamples.csproj b/samples/features/sql-management-objects/src/SmoSamples.csproj index 55ff8d136e..73090a9835 100644 --- a/samples/features/sql-management-objects/src/SmoSamples.csproj +++ b/samples/features/sql-management-objects/src/SmoSamples.csproj @@ -1,36 +1,29 @@ - - - Library - netcoreapp2.1 - false - false - - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Microsoft.SqlSerer.SmoSamples - - - Microsoft.SqlServer.SmoSamples - - - - - - - - - - - - - - - - - + + + Library + netcoreapp2.1 + false + false + + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Microsoft.SqlSerer.SmoSamples + + + Microsoft.SqlServer.SmoSamples + + + + + + + + + + + + + + + + + diff --git a/samples/features/sql-management-objects/src/SmoSamples.sln b/samples/features/sql-management-objects/src/SmoSamples.sln new file mode 100644 index 0000000000..559bb64ed2 --- /dev/null +++ b/samples/features/sql-management-objects/src/SmoSamples.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.572 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmoSamples", "SmoSamples.csproj", "{7923416F-F384-458E-991C-65AD376F54D0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7923416F-F384-458E-991C-65AD376F54D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7923416F-F384-458E-991C-65AD376F54D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7923416F-F384-458E-991C-65AD376F54D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7923416F-F384-458E-991C-65AD376F54D0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D78231A7-6CE4-407D-B13E-DC6A7F972E3C} + EndGlobalSection +EndGlobal diff --git a/samples/features/sql-management-objects/src/localhost.runsettings b/samples/features/sql-management-objects/src/localhost.runsettings index 5f086f9ae7..58f228d5c8 100644 --- a/samples/features/sql-management-objects/src/localhost.runsettings +++ b/samples/features/sql-management-objects/src/localhost.runsettings @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + \ No newline at end of file