Skip to content

Commit b765d31

Browse files
authored
Smosamples (#2)
* initial SMO sample skeleton project * add linux test runner * change namepsace * add Urn tests * Complete the collection sample * fix a comment
1 parent 9740f4d commit b765d31

15 files changed

Lines changed: 726 additions & 0 deletions

samples/features/readme.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,15 @@ Built-in temporal functions enable you to easily track history of changes in a t
2828

2929
Graph tables enable you to add a non-relational capability to your database.
3030

31+
[SQL Management Objects (SMO)](sql-management-objects)
32+
33+
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.
34+
3135
## Samples for Business Intelligence features within SQL Server
3236

3337
[Reporting Services (SSRS)](reporting-services)
3438

3539
Reporting Services provides reporting capabilities for your organziation. Reporting Services can be integrated with SharePoint Server or used as a standalone service.
40+
41+
42+
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# SmoSamples
2+
3+
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.
4+
5+
6+
### Contents
7+
8+
[About this sample](#about-this-sample)<br/>
9+
[Before you begin](#before-you-begin)<br/>
10+
[Run this sample](#run-this-sample)<br/>
11+
[Sample details](#sample-details)<br/>
12+
[Disclaimers](#disclaimers)<br/>
13+
[Related links](#related-links)<br/>
14+
15+
16+
<a name=about-this-sample></a>
17+
18+
## About this sample
19+
20+
<!-- Delete the ones that don't apply -->
21+
- **Applies to:** SQL Server 2016 (or higher), Azure SQL Database, Azure SQL Data Warehouse
22+
- **Key features:**
23+
- Unit tests and a docker file that demonstrate proper use of SMO features against a working SQL Server instance.
24+
- **Programming Language:**
25+
- C#
26+
27+
<a name=before-you-begin></a>
28+
29+
## Before you begin
30+
31+
To run this sample, you need the following prerequisites.
32+
33+
**Software prerequisites:**
34+
35+
1. SQL Server 2016 (or higher) or an Azure SQL Database with the full WideWorldImporters sample database, or
36+
2. Docker
37+
3. At minimum the dotnet 2.2 SDK, or Visual Studio 2017
38+
39+
<a name=run-this-sample></a>
40+
41+
## Run this sample
42+
43+
44+
<a name=sample-details></a>
45+
46+
## Sample details
47+
48+
Each unit test demonstrates a specific aspect of SMO-based application development, either in isolation or in conjunction with other SMO components. <br/>
49+
Feature areas tested include:
50+
1. Efficient use of collections
51+
2. Sql query capture
52+
3. Events
53+
4. URNs
54+
5. Script generation
55+
56+
57+
<a name=related-links></a>
58+
59+
## Related Links
60+
The SMO NuGet package is at https://www.nuget.org/packages/Microsoft.SqlServer.SqlManagementObjects/ <br/>
61+
Documentation for the APIs is at https://docs.microsoft.com/en-us/sql/relational-databases/server-management-objects-smo/overview-smo<br/>
62+
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/>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM mcr.microsoft.com/mssql/server:2017-latest
2+
WORKDIR /tmp/backup
3+
RUN wget -q https://github.com/Microsoft/sql-server-samples/releases/download/wide-world-importers-v1.0/WideWorldImporters-Full.bak
4+
COPY restore.sql .
5+
COPY restore.sh .
6+
COPY entrypoint.sh .
7+
CMD ["/bin/bash", "/tmp/backup/entrypoint.sh"]
8+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/opt/mssql/bin/sqlservr & /tmp/backup/restore.sh
2+
tail -f /dev/null
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
sleep 35s
2+
echo sa_password is $SA_PASSWORD
3+
/opt/mssql-tools/bin/sqlcmd -S . -U sa -P $SA_PASSWORD -i /tmp/backup/restore.sql
4+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
RESTORE DATABASE WideWorldImporters FROM DISK = "/tmp/backup/WideWorldImporters-Full.bak"
2+
WITH MOVE "WWI_Primary" TO "/var/opt/mssql/data/WideWorldImporters.mdf",
3+
MOVE "WWI_Userdata" TO "/var/opt/mssql/data/WideWorldImporters_UserData.ndf",
4+
MOVE "WWI_Log" TO "/var/opt/mssql/data/WideWorldImporters.ldf", MOVE "WWI_InMemory_Data_1"
5+
TO "/var/opt/mssql/data/WideWorldImporters_InMemory_Data_1"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
@echo off
2+
set pwd=Passwd__%random%
3+
echo Building the SQL Linux Docker container
4+
docker pull mcr.microsoft.com/mssql/server:2017-latest
5+
docker build -t sqllinux prep
6+
echo Running the SQL linux docker image
7+
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
8+
echo Waiting 90 seconds for SQL server to restore WideWorldImporters
9+
timeout /t 90
10+
setlocal
11+
echo running tests against SQL 2017 database WideWorldImporters
12+
set TEST_PASSWORD=%pwd%
13+
dotnet publish src -o out
14+
dotnet vstest src\out\SmoSamples.dll /logger:console /settings:src\localhost.runsettings
15+
endlocal
16+
echo Terminating docker container
17+
docker kill sqlserver
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
pwd=Pwd$RANDOM
2+
echo Building the SQL Linux Docker container
3+
docker pull mcr.microsoft.com/mssql/server:2017-latest
4+
docker build -t sqllinux prep
5+
echo Running the SQL linux docker image
6+
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
7+
echo Waiting 2 minutes for SQL server to restore WideWorldImporters
8+
sleep 120
9+
echo running tests against SQL 2017 database WideWorldImporters
10+
export TEST_PASSWORD=$pwd
11+
dotnet publish src
12+
dotnet vstest src/bin/Debug/netcoreapp2.1/SmoSamples.dll --logger:console --Settings:src/localhost.runsettings
13+
echo Terminating docker container
14+
docker kill sqlserver
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+

2+
using System.Diagnostics;
3+
using Microsoft.SqlServer.Management.Smo;
4+
5+
namespace Microsoft.SqlServer.SmoSamples
6+
{
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Text;
10+
using Microsoft.VisualStudio.TestTools.UnitTesting;
11+
using NUnit.Framework;
12+
using Assert = NUnit.Framework.Assert;
13+
14+
[TestClass]
15+
public class CollectionSamples
16+
{
17+
public VisualStudio.TestTools.UnitTesting.TestContext TestContext { get; set; }
18+
19+
[TestMethod]
20+
public void Collection_iteration_is_faster_with_SetDefaultInitFields()
21+
{
22+
using (var connectionMetrics = ConnectionMetrics.SetupMeasuredConnection(TestContext, 50))
23+
{
24+
var server = new Management.Smo.Server(connectionMetrics.ServerConnection);
25+
var database = server.Databases[TestContext.GetTestDatabaseName()];
26+
connectionMetrics.Reset();
27+
foreach (Table table in database.Tables)
28+
{
29+
Trace.TraceInformation(
30+
$"Unoptimized table Name: {table.Name}\tSchema:{table.Schema}\tFileGroup:{table.FileGroup}");
31+
}
32+
33+
var unoptimizedMetrics = (connectionMetrics.QueryCount, connectionMetrics.BytesSent, connectionMetrics.BytesRead, connectionMetrics.ConnectionCount);
34+
Trace.TraceInformation(string.Join($"{Environment.NewLine}\t", new[]
35+
{
36+
"Unoptimized metrics:",
37+
$"QueryCount:{unoptimizedMetrics.QueryCount}", $"ConnectionCount:{unoptimizedMetrics.ConnectionCount}",
38+
$"BytesSent:{unoptimizedMetrics.BytesSent}", $"BytesRead:{unoptimizedMetrics.BytesRead}"
39+
}));
40+
41+
connectionMetrics.Reset();
42+
server.SetDefaultInitFields(typeof(Table), "Name", "Schema", "FileGroup");
43+
database.Tables.Refresh();
44+
foreach (Table table in database.Tables)
45+
{
46+
Trace.TraceInformation(
47+
$"Optimized table Name: {table.Name}\tSchema:{table.Schema}\tFileGroup:{table.FileGroup}");
48+
}
49+
50+
var optimizedMetrics = (connectionMetrics.QueryCount, connectionMetrics.BytesSent, connectionMetrics.BytesRead, connectionMetrics.ConnectionCount);
51+
Trace.TraceInformation(string.Join($"{Environment.NewLine}\t", new[]
52+
{
53+
"Optimized Metrics:",
54+
$"QueryCount:{optimizedMetrics.QueryCount}", $"ConnectionCount:{optimizedMetrics.ConnectionCount}",
55+
$"BytesSent:{optimizedMetrics.BytesSent}", $"BytesRead:{optimizedMetrics.BytesRead}"
56+
}));
57+
Assert.That(optimizedMetrics.BytesRead, Is.LessThan(unoptimizedMetrics.BytesRead), "BytesRead");
58+
Assert.That(optimizedMetrics.BytesSent, Is.LessThan(unoptimizedMetrics.BytesSent), "BytesSent");
59+
Assert.That(optimizedMetrics.ConnectionCount, Is.AtMost(unoptimizedMetrics.ConnectionCount), "ConnectionCount");
60+
Assert.That(optimizedMetrics.QueryCount, Is.LessThan(unoptimizedMetrics.QueryCount), "QueryCount");
61+
}
62+
}
63+
}
64+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
using Microsoft.SqlServer.Management.Common;
2+
using Microsoft.SqlServer.Management.Smo;
3+
using Microsoft.VisualStudio.TestTools.UnitTesting;
4+
using NUnit.Framework;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Data.SqlClient;
8+
using System.Diagnostics;
9+
using System.Reflection;
10+
using System.Text;
11+
using Assert = NUnit.Framework.Assert;
12+
namespace Microsoft.SqlServer.SmoSamples
13+
{
14+
// Used by test classes to initialize and retrieve a ServerConnection for use in the tests themselves
15+
static class ConnectionHelpers
16+
{
17+
18+
public static ServerConnection GetTestConnection(this VisualStudio.TestTools.UnitTesting.TestContext context, ConnectionType connectionType = ConnectionType.Default)
19+
{
20+
var connectionString = context.GetConnectionString();
21+
var connectionStrBuilder = new SqlConnectionStringBuilder(connectionString);
22+
var instanceName = connectionStrBuilder.DataSource;
23+
var sqlServerLogin = connectionStrBuilder.UserID;
24+
var password = connectionStrBuilder.Password;
25+
if (connectionType == ConnectionType.SqlConnection)
26+
{
27+
return new ServerConnection(new SqlConnection(connectionString));
28+
}
29+
if (connectionType == ConnectionType.Integrated)
30+
{
31+
return new ServerConnection(instanceName);
32+
}
33+
if (connectionType == ConnectionType.SqlAuth )
34+
{
35+
if (string.IsNullOrWhiteSpace(sqlServerLogin) || string.IsNullOrWhiteSpace(password))
36+
{
37+
throw new ArgumentException("username and password values are missing from test connection string");
38+
}
39+
return new ServerConnection(instanceName, sqlServerLogin, password);
40+
}
41+
if (string.IsNullOrEmpty(sqlServerLogin))
42+
{
43+
return new ServerConnection(instanceName);
44+
}
45+
return new ServerConnection(instanceName, sqlServerLogin, password);
46+
}
47+
48+
public static string GetConnectionString(this VisualStudio.TestTools.UnitTesting.TestContext context)
49+
{
50+
var connectionString = context.Properties["connectionString"].ToString();
51+
Assert.That(connectionString, Is.Not.Empty, "connectionString must be set");
52+
connectionString = connectionString.Replace("[hostname]", Environment.GetEnvironmentVariable("TEST_HOSTNAME")).
53+
Replace("[username]", Environment.GetEnvironmentVariable("TEST_USERNAME")).
54+
Replace("[password]", Environment.GetEnvironmentVariable("TEST_PASSWORD")).
55+
Replace("[database]", Environment.GetEnvironmentVariable("TEST_DATABASE"));
56+
Console.WriteLine("Connection string: {0}", connectionString);
57+
return connectionString;
58+
}
59+
60+
/// <summary>
61+
/// Returns the name of the database to use for the tests
62+
/// </summary>
63+
/// <returns></returns>
64+
public static string GetTestDatabaseName(this VisualStudio.TestTools.UnitTesting.TestContext context)
65+
{
66+
var databaseName = Environment.GetEnvironmentVariable("TEST_DATABASE");
67+
if (string.IsNullOrEmpty(databaseName))
68+
{
69+
databaseName = context.Properties["testDatabase"].ToString();
70+
}
71+
Assert.That(databaseName, Is.Not.Empty, "testDatabase must be set");
72+
Console.WriteLine("Test database: {0}", databaseName);
73+
return databaseName;
74+
}
75+
76+
/// <summary>
77+
/// Returns the folder where result files should be written
78+
/// </summary>
79+
/// <param name="context"></param>
80+
/// <returns></returns>
81+
public static string GetResultsFolder(this VisualStudio.TestTools.UnitTesting.TestContext context)
82+
{
83+
var path = Environment.GetEnvironmentVariable("RESULTS_FOLDER");
84+
if (string.IsNullOrEmpty(path))
85+
{
86+
path = context.Properties.ContainsKey("resultsFolder") ? context.Properties["resultsFolder"].ToString() : null;
87+
}
88+
if (string.IsNullOrEmpty(path))
89+
{
90+
path = PathWrapper.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
91+
path = PathWrapper.Combine(path, "results");
92+
}
93+
return path;
94+
}
95+
96+
/// <summary>
97+
/// creates a new database with a random name, runs the action, and drops the database
98+
/// </summary>
99+
/// <param name="context"></param>
100+
/// <param name="action"></param>
101+
/// <param name="preCreateAction"></param>
102+
public static void ExecuteWithDbDrop(this VisualStudio.TestTools.UnitTesting.TestContext context, Action<Database> action, Action<Database> preCreateAction = null)
103+
{
104+
var dbName = string.Format("{0}{1}", context.TestName, new Random().Next());
105+
var serverConnection = context.GetTestConnection();
106+
var server = new Management.Smo.Server(serverConnection);
107+
var database = new Database(server, dbName);
108+
preCreateAction?.Invoke(database);
109+
database.Create();
110+
try
111+
{
112+
action(database);
113+
}
114+
finally
115+
{
116+
try
117+
{
118+
database.Drop();
119+
}
120+
catch (Exception e)
121+
{
122+
Trace.TraceError("Unable to drop database {0}: {1}", dbName, e);
123+
}
124+
}
125+
}
126+
}
127+
128+
enum ConnectionType
129+
{
130+
Default, // whatever is specified in the config
131+
Integrated, // integrated auth
132+
SqlAuth, // SQL auth
133+
SqlConnection // Create a SqlConnection first from the connection string
134+
}
135+
}

0 commit comments

Comments
 (0)