Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ build/
bld/
[Bb]in/
[Oo]bj/
out/

# Visual Studio 2015 cache/options directory
.vs/
Expand Down Expand Up @@ -435,8 +436,8 @@ samples/databases/wide-world-importers/workload-drivers/order-insert/Multithread
/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

# Certificates
*.pem
*.p12

7 changes: 7 additions & 0 deletions samples/features/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.



62 changes: 62 additions & 0 deletions samples/features/sql-management-objects/README.md
Original file line number Diff line number Diff line change
@@ -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
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".

<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/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/>
8 changes: 8 additions & 0 deletions samples/features/sql-management-objects/prep/dockerfile
Original file line number Diff line number Diff line change
@@ -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"]

2 changes: 2 additions & 0 deletions samples/features/sql-management-objects/prep/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/opt/mssql/bin/sqlservr & /tmp/backup/restore.sh
tail -f /dev/null
5 changes: 5 additions & 0 deletions samples/features/sql-management-objects/prep/restore.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Wait for SQL Server to start and be ready to accept connections
sleep 35s
Comment thread
shueybubbles marked this conversation as resolved.
echo sa_password is $SA_PASSWORD
/opt/mssql-tools/bin/sqlcmd -S . -U sa -P $SA_PASSWORD -i /tmp/backup/restore.sql

5 changes: 5 additions & 0 deletions samples/features/sql-management-objects/prep/restore.sql
Original file line number Diff line number Diff line change
@@ -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"
17 changes: 17 additions & 0 deletions samples/features/sql-management-objects/runtests.cmd
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions samples/features/sql-management-objects/runtests.sh
Original file line number Diff line number Diff line change
@@ -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
74 changes: 74 additions & 0 deletions samples/features/sql-management-objects/src/CollectionSamples.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
namespace Microsoft.SqlServer.SmoSamples
{
using System;
Comment thread
shueybubbles marked this conversation as resolved.
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;

[TestClass]
public class CollectionSamples
{
public VisualStudio.TestTools.UnitTesting.TestContext TestContext { get; set; }

/// <summary>
/// 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
/// </summary>
[TestMethod]
public void Collection_iteration_is_faster_with_SetDefaultInitFields()
Comment thread
shueybubbles marked this conversation as resolved.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So overall I like the idea of having actual examples users can run to see the benefits. But it'd definitely be great to also explain how this is actionable for them - ideally every test would have an explanation of what it's demonstrating and how users could use it in their own code.

{
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)
{
// Accessing FileGroup triggers a query to fetch it
Trace.TraceInformation(
$"Unoptimized table Name: {table.Name}\tSchema:{table.Schema}\tFileGroup:{table.FileGroup}");
}

var unoptimizedMetrics = (QueryCount: connectionMetrics.QueryCount,
BytesSent: connectionMetrics.BytesSent, BytesRead: connectionMetrics.BytesRead,
ConnectionCount: 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)
{
// 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 = (QueryCount: connectionMetrics.QueryCount,
BytesSent: connectionMetrics.BytesSent, BytesRead: connectionMetrics.BytesRead,
ConnectionCount: 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");
}
}
}
}
150 changes: 150 additions & 0 deletions samples/features/sql-management-objects/src/ConnectionHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +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
{
/// <summary>
/// Used by test classes to initialize and retrieve a ServerConnection for use in the tests themselves
/// </summary>
static class ConnectionHelpers
{

/// <summary>
/// Returns a ServerConnection based on the connectionString parameter defined in the runsettings file
/// </summary>
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);
}

/// <summary>
/// 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
/// </summary>
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
/// If TEST_DATABASE environment variable is set, that value is used, otherwise the
/// testDatabase parameter from runsettings is used.
/// </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
}
}
Loading