Skip to content

Commit

Permalink
Added lots of XML documentation comments
Browse files Browse the repository at this point in the history
Reworked the interface for SetBaseline
Tried to make the vocabulary consistent: The Migrator can "Deploy" online or offline which will produce a "Deployment Plan" which includes a sequence of "Migrations" to "run" and "Stored Code Definitions" to "apply"
Refactored the common SQL Server connection properties into a parameters class.
Added console and TeamCity progress reporters
Added a MergedMigrationProvider so users can have several kinds of migrations in a project.
  • Loading branch information
mike-schenk committed Apr 27, 2017
1 parent e3ef1a8 commit 091660c
Show file tree
Hide file tree
Showing 27 changed files with 673 additions and 167 deletions.
2 changes: 0 additions & 2 deletions Tests/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using dotMigrator;

namespace Tests
Expand Down
6 changes: 4 additions & 2 deletions Tests/FakeJournal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ public void CreateJournal()
IsCreated = true;
}

public void SetBaseline(IEnumerable<Migration> baselineMigrations)
public IReadOnlyList<DeployedMigration> SetBaseline(IEnumerable<Migration> baselineMigrations)
{
CreateJournal();
foreach (var migration in baselineMigrations)
{
_migrations.Add(migration.Name, new DeployedMigration(migration.MigrationNumber, migration.Name, migration.Fingerprint, true));
var deployedMigration = new DeployedMigration(migration.MigrationNumber, migration.Name, migration.Fingerprint, true);
_migrations.Add(migration.Name, deployedMigration);
}
return _migrations.Values.OrderBy(m => m.MigrationNumber).ToList();
}

public void RecordStartMigration(Migration migrationToRun)
Expand Down
107 changes: 107 additions & 0 deletions dotMigrator.SqlServer/ConnectionProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System.Data.SqlClient;

namespace dotMigrator.SqlServer
{
/// <summary>
/// A class to encapsulate the properties of a Sql Server connection.
/// </summary>
public class ConnectionProperties
{
/// <summary>
/// A connection string suitable for the SqlConnection object.
/// </summary>
public string ConnectionString { get; }

/// <summary>
/// The server and/or instance name of the SQL server to connect to
/// </summary>
public string ServerInstance { get; }

/// <summary>
/// The name of the database to connect to ("Initial Catalog")
/// </summary>
public string TargetDatabaseName { get; }

/// <summary>
/// The SQL authentication user name if not using windows integrated security
/// </summary>
public string SqlUserName { get; }

/// <summary>
/// The SQL authentication password if not using windows integrated security
/// </summary>
public string SqlUserPassword { get; }

/// <summary>
/// Indicates whether to use windows integrated security. When this is true, the SqlUserName and SqlUserPassword are ignored.
/// </summary>
public bool UseWindowsIntegratedSecurity { get; }

/// <summary>
/// Constructor to use when the individual connection property values are available.
/// </summary>
/// <param name="serverInstance"></param>
/// <param name="targetDatabaseName"></param>
/// <param name="sqlUserName"></param>
/// <param name="sqlUserPassword"></param>
/// <param name="useWindowsIntegratedSecurity"></param>
public ConnectionProperties(
string serverInstance,
string targetDatabaseName,
string sqlUserName = null,
string sqlUserPassword = null,
bool useWindowsIntegratedSecurity = true)
{
ServerInstance = serverInstance;
TargetDatabaseName = targetDatabaseName;
SqlUserName = sqlUserName;
SqlUserPassword = sqlUserPassword;
UseWindowsIntegratedSecurity = useWindowsIntegratedSecurity;

var connectionBuilder = new SqlConnectionStringBuilder
{
DataSource = ServerInstance,
ApplicationName = "dotMigrator",
InitialCatalog = TargetDatabaseName,
};
if (UseWindowsIntegratedSecurity)
{
connectionBuilder.IntegratedSecurity = true;
}
else
{
connectionBuilder.UserID = SqlUserName;
connectionBuilder.Password = SqlUserPassword;
}
ConnectionString = connectionBuilder.ConnectionString;
}

/// <summary>
/// Constructor to use when specific connection string property values are needed, or when
/// a connection string is more easily available than the individual connection properties
/// </summary>
/// <param name="connectionString"></param>
public ConnectionProperties(string connectionString)
{
ConnectionString = connectionString;
var bldr = new SqlConnectionStringBuilder(connectionString);
ServerInstance = bldr.DataSource;
TargetDatabaseName = bldr.InitialCatalog;
SqlUserName = bldr.UserID;
SqlUserPassword = bldr.Password;
UseWindowsIntegratedSecurity = bldr.IntegratedSecurity;
}

/// <summary>
/// Creates and opens a new SqlConnection to the target database
/// </summary>
/// <returns></returns>
public SqlConnection OpenConnection()
{
var connection = new SqlConnection(ConnectionString);
connection.Open();
return connection;

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("dotMigrator.SqlServerTableJournal")]
[assembly: AssemblyTitle("dotMigrator.SqlServer")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("dotMigrator.SqlServerTableJournal")]
[assembly: AssemblyProduct("dotMigrator.SqlServer")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
Expand All @@ -32,5 +32,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.*")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyVersion("0.2.*")]
[assembly: AssemblyFileVersion("0.2.0.0")]
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;

namespace dotMigrator.SqlServerTableJournal
namespace dotMigrator.SqlServer
{
public class SqlServerTableJournal : IJournal, IDisposable
/// <summary>
/// Holds the journal for both migrations and stored code definitions in a single table
/// </summary>
public class SingleTableJournal : IJournal, IDisposable
{
private readonly string _serverInstance;
private readonly string _targetDatabaseName;
private readonly string _sqlUserName;
private readonly string _sqlUserPassword;
private readonly bool _useWindowsIntegratedSecurity;
private readonly ConnectionProperties _connectionProperties;
private readonly IProgressReporter _progressReporter;

private SqlConnection _connection;
Expand All @@ -20,39 +20,28 @@ public class SqlServerTableJournal : IJournal, IDisposable
private SqlCommand _upsertCommand;
private SqlCommand _setCompleteCommand;

public SqlServerTableJournal(
string serverInstance,
string targetDatabaseName,
string sqlUserName,
string sqlUserPassword,
bool useWindowsIntegratedSecurity,
/// <summary>
/// Constructs the journal object which will use the supplied connection properties and holds the progressReporter without connecting to the database
/// </summary>
/// <param name="connectionProperties"></param>
/// <param name="progressReporter"></param>
public SingleTableJournal(
ConnectionProperties connectionProperties,
IProgressReporter progressReporter)
{
_serverInstance = serverInstance;
_sqlUserName = sqlUserName;
_sqlUserPassword = sqlUserPassword;
_useWindowsIntegratedSecurity = useWindowsIntegratedSecurity;
_connectionProperties = connectionProperties;
_progressReporter = progressReporter;
_targetDatabaseName = targetDatabaseName;
}

/// <summary>
/// Connects to the target database and prepares to read and write from the journal table "_DeployedScripts"
/// </summary>
public void Open()
{
if (_connection != null)
return;
// open our Sql connection and look for the _DeployedScripts table
var connectionBuilder = new SqlConnectionStringBuilder { DataSource = _serverInstance, ApplicationName = "dotMigrator", InitialCatalog = _targetDatabaseName };
if (_useWindowsIntegratedSecurity)
{
connectionBuilder.IntegratedSecurity = true;
}
else
{
connectionBuilder.UserID = _sqlUserName;
connectionBuilder.Password = _sqlUserPassword;
}
_connection = new SqlConnection(connectionBuilder.ConnectionString);
_connection.Open();

_connection = _connectionProperties.OpenConnection();

_selectCommand = _connection.CreateCommand();
_selectCommand.CommandText =
Expand Down Expand Up @@ -107,6 +96,9 @@ public void Open()
_setCompleteCommand.Parameters.Add("@CompletedTs", SqlDbType.DateTime2);
}

/// <summary>
/// Ensures the journal table "[dbo].[_DeployedScripts]" is present in the target database, creating it if necessary.
/// </summary>
public void CreateJournal()
{
var findTableCommand = new SqlCommand("SELECT OBJECT_ID('_DeployedScripts')", _connection);
Expand All @@ -124,17 +116,25 @@ [Fingerprint] [nvarchar](50) NOT NULL
)",
_connection);

_progressReporter.Report($"Preparing database \"{_targetDatabaseName}\" for future deployments...");
_progressReporter.Report($"Preparing database \"{_connectionProperties.TargetDatabaseName}\" for future deployments...");
createTableCommand.ExecuteNonQuery();
_progressReporter.Report("Done");
}
}

public void SetBaseline(IEnumerable<Migration> baselineMigrations)
/// <summary>
/// Records that a series of migrations has already been completed in a target data store.
/// This is used when an existing data store is being put under management by dotMigrator
/// </summary>
/// <param name="baselineMigrations"></param>
/// <returns></returns>
public IReadOnlyList<DeployedMigration> SetBaseline(IEnumerable<Migration> baselineMigrations)
{
// first we'll call CreateJournal to ensure the table is already set up.
CreateJournal();

var toReturn = new List<DeployedMigration>();

foreach (var migration in baselineMigrations)
{
// insert into our table
Expand All @@ -145,9 +145,16 @@ public void SetBaseline(IEnumerable<Migration> baselineMigrations)
_insertCommand.Parameters["@CompletedTs"].Value = DateTime.Now;
_insertCommand.Parameters["@Fingerprint"].Value = migration.Fingerprint;
_insertCommand.ExecuteNonQuery();
toReturn.Add(new DeployedMigration(migration.MigrationNumber, migration.Name, migration.Fingerprint, true));
}
return toReturn.OrderBy(m => m.MigrationNumber).ToList();
}

/// <summary>
/// Insert or update the migration identified by its name in the journal.
/// It will be recorded as an incomplete migration.
/// </summary>
/// <param name="migration"></param>
public void RecordStartMigration(Migration migration)
{
// insert or update the fingerprint of a migration.
Expand All @@ -160,13 +167,25 @@ public void RecordStartMigration(Migration migration)
_upsertCommand.ExecuteNonQuery();
}

/// <summary>
/// Update the identified migration in the journal as having been completed.
/// It can be assumed that this will only be called for the most recently started mgiration.
/// </summary>
/// <param name="migration"></param>
public void RecordCompleteMigration(Migration migration)
{
_setCompleteCommand.Parameters["@Name"].Value = migration.Name;
_setCompleteCommand.Parameters["@CompletedTs"].Value = DateTime.Now;
_setCompleteCommand.ExecuteNonQuery();
}

/// <summary>
/// Insert a record to the journal that a new stored code definition has been completely applied,
/// or update an existing record with the new fingerprint of a stored code definition that has
/// just been applied.
/// </summary>
/// <param name="storedCodeDefinition"></param>
/// <param name="lastMigrationNumber"></param>
public void RecordStoredCodeDefinition(StoredCodeDefinition storedCodeDefinition, int lastMigrationNumber)
{
// insert or update the fingerprint of a stored code definition.
Expand All @@ -179,6 +198,9 @@ public void RecordStoredCodeDefinition(StoredCodeDefinition storedCodeDefinition
_upsertCommand.ExecuteNonQuery();
}

/// <summary>
/// Returns the sequenced list of offline and online migrations that have been recorded in the journal
/// </summary>
public IReadOnlyList<DeployedMigration> GetDeployedMigrations()
{
var toReturn = new List<DeployedMigration>();
Expand All @@ -198,6 +220,12 @@ public IReadOnlyList<DeployedMigration> GetDeployedMigrations()
return toReturn;
}

/// <summary>
/// Returns all of the stored code definitions that have been previously applied as recorded in this journal.
/// The order of the definitions does not matter since the names are used to match them up with the available ones
/// that the MigrationsProvider finds.
/// </summary>
/// <returns></returns>
public IReadOnlyList<DeployedStoredCodeDefinition> GetDeployedStoredCodeDefinitions()
{
var toReturn = new List<DeployedStoredCodeDefinition>();
Expand All @@ -217,9 +245,13 @@ public IReadOnlyList<DeployedStoredCodeDefinition> GetDeployedStoredCodeDefiniti
return toReturn;
}

/// <summary>
/// Close the connection to the database
/// </summary>
public void Dispose()
{
_connection?.Dispose();
_connection = null;
_selectCommand?.Dispose();
_insertCommand?.Dispose();
_upsertCommand?.Dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<ProjectGuid>{BEE4497E-6BDF-4A8F-A8A5-D3CCC0F2F74B}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>dotMigrator.SqlServerTableJournal</RootNamespace>
<AssemblyName>dotMigrator.SqlServerTableJournal</AssemblyName>
<RootNamespace>dotMigrator.SqlServer</RootNamespace>
<AssemblyName>dotMigrator.SqlServer</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
Expand All @@ -28,7 +28,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\dotMigrator.SqlServerTableJournal.xml</DocumentationFile>
<DocumentationFile>bin\Release\dotMigrator.SqlServer.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
Expand All @@ -37,7 +37,8 @@
<Reference Include="System.Data" />
</ItemGroup>
<ItemGroup>
<Compile Include="SqlServerTableJournal.cs" />
<Compile Include="ConnectionProperties.cs" />
<Compile Include="SingleTableJournal.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
Expand All @@ -47,4 +48,5 @@
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildProjectDirectory)\..\dotMigrator.targets"/>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.*")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyVersion("0.2.*")]
[assembly: AssemblyFileVersion("0.2.0.0")]
Loading

0 comments on commit 091660c

Please sign in to comment.