mirror of
https://github.com/Microsoft/sql-server-samples.git
synced 2025-12-08 14:58:54 +00:00
Smosamples (#2)
* initial SMO sample skeleton project * add linux test runner * change namepsace * add Urn tests * Complete the collection sample * fix a comment
This commit is contained in:
@@ -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)<br/>
|
||||
[Before you begin](#before-you-begin)<br/>
|
||||
[Run this sample](#run-this-sample)<br/>
|
||||
[Sample details](#sample-details)<br/>
|
||||
[Disclaimers](#disclaimers)<br/>
|
||||
[Related links](#related-links)<br/>
|
||||
|
||||
|
||||
<a name=about-this-sample></a>
|
||||
|
||||
## About this sample
|
||||
|
||||
<!-- Delete the ones that don't apply -->
|
||||
- **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#
|
||||
|
||||
<a name=before-you-begin></a>
|
||||
|
||||
## 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
|
||||
|
||||
<a name=run-this-sample></a>
|
||||
|
||||
## Run this sample
|
||||
|
||||
|
||||
<a name=sample-details></a>
|
||||
|
||||
## Sample details
|
||||
|
||||
Each unit test demonstrates a specific aspect of SMO-based application development, either in isolation or in conjunction with other SMO components. <br/>
|
||||
Feature areas tested include:
|
||||
1. Efficient use of collections
|
||||
2. Sql query capture
|
||||
3. Events
|
||||
4. URNs
|
||||
5. Script generation
|
||||
|
||||
|
||||
<a name=related-links></a>
|
||||
|
||||
## Related Links
|
||||
The SMO NuGet package is at https://www.nuget.org/packages/Microsoft.SqlServer.SqlManagementObjects/ <br/>
|
||||
Documentation for the APIs is at https://docs.microsoft.com/en-us/sql/relational-databases/server-management-objects-smo/overview-smo<br/>
|
||||
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 <br/>
|
||||
@@ -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"]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/opt/mssql/bin/sqlservr & /tmp/backup/restore.sh
|
||||
tail -f /dev/null
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the database to use for the tests
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the folder where result files should be written
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// creates a new database with a random name, runs the action, and drops the database
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="preCreateAction"></param>
|
||||
public static void ExecuteWithDbDrop(this VisualStudio.TestTools.UnitTesting.TestContext context, Action<Database> action, Action<Database> 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
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an in-memory proxy with callbacks that allow tests to run code before transmission and after receipt of
|
||||
/// data on the wire
|
||||
/// </summary>
|
||||
[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();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a GenericSqlProxy for the local default sql instance
|
||||
/// </summary>
|
||||
public GenericSqlProxy() : this(".")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new GenericSqlProxy for the given connection string
|
||||
/// </summary>
|
||||
/// <param name="connectionString"></param>
|
||||
public GenericSqlProxy(string connectionString)
|
||||
{
|
||||
this.connectionString = connectionString;
|
||||
}
|
||||
|
||||
public int Port { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the proxy by opening the TCP listener and copying data between client and server
|
||||
/// </summary>
|
||||
/// <param name="localPort">local port number to use. 0 will use a random port</param>
|
||||
/// <returns>The connection string to use for the SqlConnection</returns>
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fires before the proxy writes a buffer to the host
|
||||
/// </summary>
|
||||
public event EventHandler<StreamWriteEventArgs> OnWriteHost;
|
||||
|
||||
/// <summary>
|
||||
/// Fires before the proxy writes a buffer to the client
|
||||
/// </summary>
|
||||
public event EventHandler<StreamWriteEventArgs> OnWriteClient;
|
||||
|
||||
/// <summary>
|
||||
/// Fires when a new connection to the proxy's port is accepted
|
||||
/// </summary>
|
||||
public event EventHandler<ProxyConnectionEventArgs> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup Label="Configuration">
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<IsPackable>false</IsPackable>
|
||||
<ApplicationIcon />
|
||||
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<DefaultNamespace>Microsoft.SqlSerer.SmoSamples</DefaultNamespace>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Microsoft.SqlServer.SmoSamples</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="out\**" />
|
||||
<EmbeddedResource Remove="out\**" />
|
||||
<None Remove="out\**" />
|
||||
</ItemGroup>
|
||||
<!-- <ItemGroup>
|
||||
<None Remove="ScriptOutput_Standalone_Linux.baseline.txt" />
|
||||
<None Remove="ScriptOutput_Standalone_Win.baseline.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="ScriptOutput_Standalone_Linux.baseline.txt" />
|
||||
<EmbeddedResource Include="ScriptOutput_Standalone_Win.baseline.txt" />
|
||||
</ItemGroup> -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.6.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
|
||||
<PackageReference Include="Microsoft.SqlServer.SqlManagementObjects" Version="150.18118.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
|
||||
<PackageReference Include="NUnit" Version="3.11.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -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<FailedOperationException>(() =>
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RunSettings>
|
||||
<TestRunParameters>
|
||||
<Parameter name="connectionString" value="server=localhost;User Id=sa;Password=[password];Timeout=60" />
|
||||
<Parameter name="testDatabase" value="WideWorldImporters" />
|
||||
<Parameter name="proxyPort" value="0" />
|
||||
</TestRunParameters>
|
||||
</RunSettings>
|
||||
Reference in New Issue
Block a user