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:
David Shiflet
2019-04-30 23:33:10 -04:00
committed by GitHub
parent 9740f4d1ba
commit b765d311c1
15 changed files with 726 additions and 0 deletions
+7
View File
@@ -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.
@@ -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>