Skip to content

Commit

Permalink
Add transaction handler to shared components. (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonpollett authored Apr 16, 2020
1 parent f19a1fe commit 640c9bb
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

namespace Microsoft.Health.Abstractions.Exceptions
{
public class TransactionFailedException : MicrosoftHealthException
{
public TransactionFailedException()
: base(Resources.TransactionProcessingException)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;

namespace Microsoft.Health.Abstractions.Features.Transactions
{
public interface ITransactionHandler : IDisposable
{
ITransactionScope BeginTransaction();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;

namespace Microsoft.Health.Abstractions.Features.Transactions
{
public interface ITransactionScope : IDisposable
{
void Complete();
}
}
9 changes: 9 additions & 0 deletions src/Microsoft.Health.Abstractions/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.Health.Abstractions/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,7 @@
<data name="ServiceUnavailable" xml:space="preserve">
<value>The operation could not be completed, because the service was unable to accept new requests. It is safe to retry the operation. If the issue persists, please contact support.</value>
</data>
<data name="TransactionProcessingException" xml:space="preserve">
<value>There was an error while processing the transaction.</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using System.Data;
using System.Data.SqlClient;
using EnsureThat;
using Microsoft.Health.SqlServer.Configs;
using Microsoft.Health.SqlServer.Features.Storage;

namespace Microsoft.Health.SqlServer.Features.Client
{
public class SqlConnectionWrapper : IDisposable
{
private readonly bool _enlistInTransactionIfPresent;
private readonly SqlTransactionHandler _sqlTransactionHandler;

public SqlConnectionWrapper(SqlServerDataStoreConfiguration configuration, SqlTransactionHandler sqlTransactionHandler, bool enlistInTransactionIfPresent)
{
EnsureArg.IsNotNull(configuration, nameof(configuration));
EnsureArg.IsNotNull(sqlTransactionHandler, nameof(sqlTransactionHandler));

_sqlTransactionHandler = sqlTransactionHandler;
_enlistInTransactionIfPresent = enlistInTransactionIfPresent;

if (_enlistInTransactionIfPresent && sqlTransactionHandler.SqlTransactionScope?.SqlConnection != null)
{
SqlConnection = sqlTransactionHandler.SqlTransactionScope.SqlConnection;
}
else
{
SqlConnection = new SqlConnection(configuration.ConnectionString);
}

if (_enlistInTransactionIfPresent && sqlTransactionHandler.SqlTransactionScope != null && sqlTransactionHandler.SqlTransactionScope.SqlConnection == null)
{
sqlTransactionHandler.SqlTransactionScope.SqlConnection = SqlConnection;
}

if (SqlConnection.State != ConnectionState.Open)
{
SqlConnection.Open();
}

if (enlistInTransactionIfPresent && sqlTransactionHandler.SqlTransactionScope != null)
{
SqlTransaction = sqlTransactionHandler.SqlTransactionScope.SqlTransaction ?? SqlConnection.BeginTransaction();

if (sqlTransactionHandler.SqlTransactionScope.SqlTransaction == null)
{
sqlTransactionHandler.SqlTransactionScope.SqlTransaction = SqlTransaction;
}
}
}

public SqlConnection SqlConnection { get; }

public SqlTransaction SqlTransaction { get; }

public SqlCommand CreateSqlCommand()
{
SqlCommand sqlCommand = SqlConnection.CreateCommand();
sqlCommand.Transaction = SqlTransaction;

return sqlCommand;
}

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (!_enlistInTransactionIfPresent || _sqlTransactionHandler.SqlTransactionScope == null)
{
SqlConnection?.Dispose();
SqlTransaction?.Dispose();
}
}
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using EnsureThat;
using Microsoft.Health.SqlServer.Configs;
using Microsoft.Health.SqlServer.Features.Storage;

namespace Microsoft.Health.SqlServer.Features.Client
{
public class SqlConnectionWrapperFactory
{
private readonly SqlServerDataStoreConfiguration _configuration;
private readonly SqlTransactionHandler _sqlTransactionHandler;

public SqlConnectionWrapperFactory(SqlServerDataStoreConfiguration configuration, SqlTransactionHandler sqlTransactionHandler)
{
EnsureArg.IsNotNull(configuration, nameof(configuration));
EnsureArg.IsNotNull(sqlTransactionHandler, nameof(sqlTransactionHandler));

_configuration = configuration;
_sqlTransactionHandler = sqlTransactionHandler;
}

public SqlConnectionWrapper ObtainSqlConnectionWrapper(bool enlistInTransaction = false)
{
return new SqlConnectionWrapper(_configuration, _sqlTransactionHandler, enlistInTransaction);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using System.Diagnostics;
using Microsoft.Health.Abstractions.Exceptions;
using Microsoft.Health.Abstractions.Features.Transactions;

namespace Microsoft.Health.SqlServer.Features.Storage
{
public class SqlTransactionHandler : ITransactionHandler
{
public SqlTransactionScope SqlTransactionScope { get; private set; }

public ITransactionScope BeginTransaction()
{
Debug.Assert(SqlTransactionScope == null, "The existing SQL transaction scope should be completed before starting a new transaction.");

if (SqlTransactionScope != null)
{
throw new TransactionFailedException();
}

SqlTransactionScope = new SqlTransactionScope(this);

return SqlTransactionScope;
}

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
SqlTransactionScope?.Dispose();

SqlTransactionScope = null;
}
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using System.Data.SqlClient;
using EnsureThat;
using Microsoft.Health.Abstractions.Features.Transactions;

namespace Microsoft.Health.SqlServer.Features.Storage
{
public class SqlTransactionScope : ITransactionScope
{
private bool _isDisposed;
private readonly SqlTransactionHandler _sqlTransactionHandler;

public SqlTransactionScope(SqlTransactionHandler sqlTransactionHandler)
{
EnsureArg.IsNotNull(sqlTransactionHandler, nameof(SqlTransactionHandler));

_sqlTransactionHandler = sqlTransactionHandler;
}

public SqlConnection SqlConnection { get; set; }

public SqlTransaction SqlTransaction { get; set; }

public void Complete()
{
SqlTransaction?.Commit();
}

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_isDisposed)
{
return;
}

SqlConnection?.Dispose();
SqlTransaction?.Dispose();

SqlConnection = null;
SqlTransaction = null;

_isDisposed = true;

_sqlTransactionHandler.Dispose();
}
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Health.Extensions.DependencyInjection;
using Microsoft.Health.SqlServer.Configs;
using Microsoft.Health.SqlServer.Features.Client;
using Microsoft.Health.SqlServer.Features.Schema;
using Microsoft.Health.SqlServer.Features.Storage;

namespace Microsoft.Health.SqlServer.Registration
{
Expand Down Expand Up @@ -43,6 +45,16 @@ public static IServiceCollection AddSqlServerBase<TSchemaVersionEnum>(
.AsSelf()
.AsImplementedInterfaces();

services.Add<SqlTransactionHandler>()
.Scoped()
.AsSelf()
.AsImplementedInterfaces();

services.Add<SqlConnectionWrapperFactory>()
.Scoped()
.AsSelf()
.AsImplementedInterfaces();

return services;
}
}
Expand Down

0 comments on commit 640c9bb

Please sign in to comment.