Skip to content

Commit

Permalink
Merge pull request #12 from Shuttle/no-blocking
Browse files Browse the repository at this point in the history
Removed  blocking implementation as DatabaseContext does not need to be thread-safe.
  • Loading branch information
eben-roux authored Jul 21, 2024
2 parents a83b60e + 14077ab commit aecbd0d
Show file tree
Hide file tree
Showing 14 changed files with 64 additions and 198 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Provides an abstraction built directly on ADO.NET which falls within the Micro O

# Overview

***NOTE:*** Since a database connection is represented by a `IDatabaseContext` instance it is important to understand that this instance is not thread-safe. It is therefore important to ensure that the `IDatabaseContext` instance is not shared between threads. See the `DatabaseContextScope` to ensure thread-safe database context flow.

The `Shuttle.Core.Data` package provides a thin abstraction over ADO.NET by making use of the `DbProviderFactories`. Even though it provides object/relational mapping mechanisms it is in no way a fully fledged ORM.

## Configuration
Expand Down
16 changes: 0 additions & 16 deletions Shuttle.Core.Data.Tests/AsyncFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,22 +71,6 @@ public void Should_be_able_to_use_the_different_database_context_for_separate_ta
Task.WaitAll(tasks.ToArray());
}

[Test]
public void Should_be_able_to_use_the_same_database_context_across_tasks()
{
var tasks = new List<Task>();

using (DatabaseContextFactory.Create())
{
for (var i = 0; i < 10; i++)
{
tasks.Add(DatabaseGateway.GetRowsAsync(_rowsQuery));
}

Task.WaitAll(tasks.ToArray());
}
}

[Test]
public async Task Should_be_able_to_use_the_same_database_context_across_synchronized_tasks_async()
{
Expand Down
3 changes: 2 additions & 1 deletion Shuttle.Core.Data.Tests/DbCommandFactoryFixture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Data;
using System.Data.Common;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
Expand All @@ -14,7 +15,7 @@ public void Should_be_able_to_create_a_command()
var factory = new DbCommandFactory(Options.Create(new DataAccessOptions { CommandTimeout = 15 }));
var connection = new Mock<IDbConnection>();
var query = new Mock<IQuery>();
var command = new Mock<IDbCommand>();
var command = new Mock<DbCommand>();

command.SetupSet(m => m.CommandTimeout = 15).Verifiable("CommandTimeout not set to 15");

Expand Down
63 changes: 0 additions & 63 deletions Shuttle.Core.Data/BlockedDbCommand.cs

This file was deleted.

24 changes: 0 additions & 24 deletions Shuttle.Core.Data/BlockedDbConnection.cs

This file was deleted.

25 changes: 0 additions & 25 deletions Shuttle.Core.Data/BlockedDbDataReader.cs

This file was deleted.

33 changes: 12 additions & 21 deletions Shuttle.Core.Data/DatabaseContext.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
using System;
using System.Data;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using Shuttle.Core.Contract;
using Shuttle.Core.Threading;

namespace Shuttle.Core.Data
{
public class DatabaseContext : IDatabaseContext
{
private readonly IDatabaseContextService _databaseContextService;
private readonly IDbCommandFactory _dbCommandFactory;
private readonly SemaphoreSlim _dbCommandLock = new SemaphoreSlim(1, 1);
private readonly IDbConnection _dbConnection;
private readonly SemaphoreSlim _dbConnectionLock = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim _dbDataReaderLock = new SemaphoreSlim(1, 1);
private bool _disposed;

public DatabaseContext(string name, string providerName, IDbConnection dbConnection, IDbCommandFactory dbCommandFactory, IDatabaseContextService databaseContextService)
Expand All @@ -39,27 +34,23 @@ public DatabaseContext(string name, string providerName, IDbConnection dbConnect

public Guid Key { get; }
public string Name { get; }
public IDbTransaction Transaction { get; private set; }
public DbTransaction Transaction { get; private set; }
public string ProviderName { get; }

public BlockedDbCommand CreateCommand(IQuery query)
public DbCommand CreateCommand(IQuery query)
{
GuardDisposed();

_dbCommandLock.Wait(CancellationToken.None);

var command = _dbCommandFactory.Create(GetOpenConnectionAsync(true).GetAwaiter().GetResult(), Guard.AgainstNull(query, nameof(query)));

command.Transaction = Transaction;

return new BlockedDbCommand((DbCommand)command, new BlockingSemaphoreSlim(_dbCommandLock), _dbDataReaderLock);
return command;
}

public BlockedDbConnection GetDbConnection()
public IDbConnection GetDbConnection()
{
_dbConnectionLock.Wait(CancellationToken.None);

return new BlockedDbConnection(GetOpenConnectionAsync(true).GetAwaiter().GetResult(), new BlockingSemaphoreSlim(_dbConnectionLock));
return GetOpenConnectionAsync(true).GetAwaiter().GetResult();
}

public bool HasTransaction => Transaction != null;
Expand Down Expand Up @@ -112,6 +103,13 @@ public void Dispose()
Disposed?.Invoke(this, EventArgs.Empty);
}

public async ValueTask DisposeAsync()
{
Dispose();

await new ValueTask();
}

private async Task<IDatabaseContext> BeginTransactionAsync(IsolationLevel isolationLevel, bool sync)
{
if (HasTransaction || System.Transactions.Transaction.Current != null)
Expand Down Expand Up @@ -180,12 +178,5 @@ private void GuardDisposed()

throw new ObjectDisposedException(nameof(DatabaseContext));
}

public async ValueTask DisposeAsync()
{
Dispose();

await new ValueTask();
}
}
}
7 changes: 1 addition & 6 deletions Shuttle.Core.Data/DatabaseContextService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,7 @@ public IDatabaseContext Find(Predicate<IDatabaseContext> match)

private DatabaseContextCollection GetDatabaseContextCollection()
{
if (DatabaseContextScope.Current == null)
{
return _databaseContextCollection;
}

return DatabaseContextScope.Current;
return DatabaseContextScope.Current ?? _databaseContextCollection;
}
}
}
9 changes: 5 additions & 4 deletions Shuttle.Core.Data/DatabaseGateway.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -35,7 +36,7 @@ private async Task<DataTable> GetDataTableAsync(IQuery query, CancellationToken

using (var reader = sync ? GetReader(query, cancellationToken) : await GetReaderAsync(query, cancellationToken).ConfigureAwait(false))
{
results.Load(reader.DbDataReader);
results.Load(reader);
}

return results;
Expand Down Expand Up @@ -74,17 +75,17 @@ private async Task<DataRow> GetRowAsync(IQuery query, CancellationToken cancella
return table.Rows[0];
}

public BlockedDbDataReader GetReader(IQuery query, CancellationToken cancellationToken = default)
public DbDataReader GetReader(IQuery query, CancellationToken cancellationToken = default)
{
return GetReaderAsync(query, cancellationToken, true).GetAwaiter().GetResult();
}

public async Task<BlockedDbDataReader> GetReaderAsync(IQuery query, CancellationToken cancellationToken = default)
public async Task<DbDataReader> GetReaderAsync(IQuery query, CancellationToken cancellationToken = default)
{
return await GetReaderAsync(query, cancellationToken, false).ConfigureAwait(false);
}

private async Task<BlockedDbDataReader> GetReaderAsync(IQuery query, CancellationToken cancellationToken, bool sync)
private async Task<DbDataReader> GetReaderAsync(IQuery query, CancellationToken cancellationToken, bool sync)
{
Guard.AgainstNull(query, nameof(query));

Expand Down
5 changes: 3 additions & 2 deletions Shuttle.Core.Data/DbCommandFactory.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Data;
using System.Data.Common;
using Microsoft.Extensions.Options;
using Shuttle.Core.Contract;

Expand All @@ -18,7 +19,7 @@ public DbCommandFactory(IOptions<DataAccessOptions> options)

public event EventHandler<DbCommandCreatedEventArgs> DbCommandCreated;

public IDbCommand Create(IDbConnection connection, IQuery query)
public DbCommand Create(IDbConnection connection, IQuery query)
{
var command = Guard.AgainstNull(connection, nameof(connection)).CreateCommand();

Expand All @@ -28,7 +29,7 @@ public IDbCommand Create(IDbConnection connection, IQuery query)

DbCommandCreated?.Invoke(this, new DbCommandCreatedEventArgs(command));

return command;
return (DbCommand)command;
}
}
}
7 changes: 4 additions & 3 deletions Shuttle.Core.Data/IDatabaseContext.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Data;
using System.Data.Common;
using System.Threading.Tasks;
using IsolationLevel = System.Data.IsolationLevel;

Expand All @@ -15,9 +16,9 @@ public interface IDatabaseContext : IDisposable, IAsyncDisposable
Guid Key { get; }
string Name { get; }

IDbTransaction Transaction { get; }
BlockedDbCommand CreateCommand(IQuery query);
BlockedDbConnection GetDbConnection();
DbTransaction Transaction { get; }
DbCommand CreateCommand(IQuery query);
IDbConnection GetDbConnection();

bool HasTransaction { get; }
string ProviderName { get; }
Expand Down
5 changes: 3 additions & 2 deletions Shuttle.Core.Data/IDatabaseGateway.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;

namespace Shuttle.Core.Data
{
public interface IDatabaseGateway
{
BlockedDbDataReader GetReader(IQuery query, CancellationToken cancellationToken = default);
DbDataReader GetReader(IQuery query, CancellationToken cancellationToken = default);
int Execute(IQuery query, CancellationToken cancellationToken = default);
T GetScalar<T>(IQuery query, CancellationToken cancellationToken = default);
DataTable GetDataTable(IQuery query, CancellationToken cancellationToken = default);
IEnumerable<DataRow> GetRows(IQuery query, CancellationToken cancellationToken = default);
DataRow GetRow(IQuery query, CancellationToken cancellationToken = default);

Task<BlockedDbDataReader> GetReaderAsync(IQuery query, CancellationToken cancellationToken = default);
Task<DbDataReader> GetReaderAsync(IQuery query, CancellationToken cancellationToken = default);
Task<int> ExecuteAsync(IQuery query, CancellationToken cancellationToken = default);
Task<T> GetScalarAsync<T>(IQuery query, CancellationToken cancellationToken = default);
Task<DataTable> GetDataTableAsync(IQuery query, CancellationToken cancellationToken = default);
Expand Down
3 changes: 2 additions & 1 deletion Shuttle.Core.Data/IDbCommandFactory.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System;
using System.Data;
using System.Data.Common;

namespace Shuttle.Core.Data
{
public interface IDbCommandFactory
{
event EventHandler<DbCommandCreatedEventArgs> DbCommandCreated;

IDbCommand Create(IDbConnection connection, IQuery query);
DbCommand Create(IDbConnection connection, IQuery query);
}
}
Loading

0 comments on commit aecbd0d

Please sign in to comment.