Skip to content

Commit aecbd0d

Browse files
authored
Merge pull request #12 from Shuttle/no-blocking
Removed blocking implementation as DatabaseContext does not need to be thread-safe.
2 parents a83b60e + 14077ab commit aecbd0d

14 files changed

+64
-198
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Provides an abstraction built directly on ADO.NET which falls within the Micro O
88

99
# Overview
1010

11+
***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.
12+
1113
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.
1214

1315
## Configuration

Shuttle.Core.Data.Tests/AsyncFixture.cs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -71,22 +71,6 @@ public void Should_be_able_to_use_the_different_database_context_for_separate_ta
7171
Task.WaitAll(tasks.ToArray());
7272
}
7373

74-
[Test]
75-
public void Should_be_able_to_use_the_same_database_context_across_tasks()
76-
{
77-
var tasks = new List<Task>();
78-
79-
using (DatabaseContextFactory.Create())
80-
{
81-
for (var i = 0; i < 10; i++)
82-
{
83-
tasks.Add(DatabaseGateway.GetRowsAsync(_rowsQuery));
84-
}
85-
86-
Task.WaitAll(tasks.ToArray());
87-
}
88-
}
89-
9074
[Test]
9175
public async Task Should_be_able_to_use_the_same_database_context_across_synchronized_tasks_async()
9276
{

Shuttle.Core.Data.Tests/DbCommandFactoryFixture.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Data;
2+
using System.Data.Common;
23
using Microsoft.Extensions.Options;
34
using Moq;
45
using NUnit.Framework;
@@ -14,7 +15,7 @@ public void Should_be_able_to_create_a_command()
1415
var factory = new DbCommandFactory(Options.Create(new DataAccessOptions { CommandTimeout = 15 }));
1516
var connection = new Mock<IDbConnection>();
1617
var query = new Mock<IQuery>();
17-
var command = new Mock<IDbCommand>();
18+
var command = new Mock<DbCommand>();
1819

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

Shuttle.Core.Data/BlockedDbCommand.cs

Lines changed: 0 additions & 63 deletions
This file was deleted.

Shuttle.Core.Data/BlockedDbConnection.cs

Lines changed: 0 additions & 24 deletions
This file was deleted.

Shuttle.Core.Data/BlockedDbDataReader.cs

Lines changed: 0 additions & 25 deletions
This file was deleted.

Shuttle.Core.Data/DatabaseContext.cs

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
using System;
22
using System.Data;
33
using System.Data.Common;
4-
using System.Threading;
54
using System.Threading.Tasks;
65
using Shuttle.Core.Contract;
7-
using Shuttle.Core.Threading;
86

97
namespace Shuttle.Core.Data
108
{
119
public class DatabaseContext : IDatabaseContext
1210
{
1311
private readonly IDatabaseContextService _databaseContextService;
1412
private readonly IDbCommandFactory _dbCommandFactory;
15-
private readonly SemaphoreSlim _dbCommandLock = new SemaphoreSlim(1, 1);
1613
private readonly IDbConnection _dbConnection;
17-
private readonly SemaphoreSlim _dbConnectionLock = new SemaphoreSlim(1, 1);
18-
private readonly SemaphoreSlim _dbDataReaderLock = new SemaphoreSlim(1, 1);
1914
private bool _disposed;
2015

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

4035
public Guid Key { get; }
4136
public string Name { get; }
42-
public IDbTransaction Transaction { get; private set; }
37+
public DbTransaction Transaction { get; private set; }
4338
public string ProviderName { get; }
4439

45-
public BlockedDbCommand CreateCommand(IQuery query)
40+
public DbCommand CreateCommand(IQuery query)
4641
{
4742
GuardDisposed();
4843

49-
_dbCommandLock.Wait(CancellationToken.None);
50-
5144
var command = _dbCommandFactory.Create(GetOpenConnectionAsync(true).GetAwaiter().GetResult(), Guard.AgainstNull(query, nameof(query)));
5245

5346
command.Transaction = Transaction;
5447

55-
return new BlockedDbCommand((DbCommand)command, new BlockingSemaphoreSlim(_dbCommandLock), _dbDataReaderLock);
48+
return command;
5649
}
5750

58-
public BlockedDbConnection GetDbConnection()
51+
public IDbConnection GetDbConnection()
5952
{
60-
_dbConnectionLock.Wait(CancellationToken.None);
61-
62-
return new BlockedDbConnection(GetOpenConnectionAsync(true).GetAwaiter().GetResult(), new BlockingSemaphoreSlim(_dbConnectionLock));
53+
return GetOpenConnectionAsync(true).GetAwaiter().GetResult();
6354
}
6455

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

106+
public async ValueTask DisposeAsync()
107+
{
108+
Dispose();
109+
110+
await new ValueTask();
111+
}
112+
115113
private async Task<IDatabaseContext> BeginTransactionAsync(IsolationLevel isolationLevel, bool sync)
116114
{
117115
if (HasTransaction || System.Transactions.Transaction.Current != null)
@@ -180,12 +178,5 @@ private void GuardDisposed()
180178

181179
throw new ObjectDisposedException(nameof(DatabaseContext));
182180
}
183-
184-
public async ValueTask DisposeAsync()
185-
{
186-
Dispose();
187-
188-
await new ValueTask();
189-
}
190181
}
191182
}

Shuttle.Core.Data/DatabaseContextService.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,7 @@ public IDatabaseContext Find(Predicate<IDatabaseContext> match)
109109

110110
private DatabaseContextCollection GetDatabaseContextCollection()
111111
{
112-
if (DatabaseContextScope.Current == null)
113-
{
114-
return _databaseContextCollection;
115-
}
116-
117-
return DatabaseContextScope.Current;
112+
return DatabaseContextScope.Current ?? _databaseContextCollection;
118113
}
119114
}
120115
}

Shuttle.Core.Data/DatabaseGateway.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Data;
4+
using System.Data.Common;
45
using System.Linq;
56
using System.Threading;
67
using System.Threading.Tasks;
@@ -35,7 +36,7 @@ private async Task<DataTable> GetDataTableAsync(IQuery query, CancellationToken
3536

3637
using (var reader = sync ? GetReader(query, cancellationToken) : await GetReaderAsync(query, cancellationToken).ConfigureAwait(false))
3738
{
38-
results.Load(reader.DbDataReader);
39+
results.Load(reader);
3940
}
4041

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

77-
public BlockedDbDataReader GetReader(IQuery query, CancellationToken cancellationToken = default)
78+
public DbDataReader GetReader(IQuery query, CancellationToken cancellationToken = default)
7879
{
7980
return GetReaderAsync(query, cancellationToken, true).GetAwaiter().GetResult();
8081
}
8182

82-
public async Task<BlockedDbDataReader> GetReaderAsync(IQuery query, CancellationToken cancellationToken = default)
83+
public async Task<DbDataReader> GetReaderAsync(IQuery query, CancellationToken cancellationToken = default)
8384
{
8485
return await GetReaderAsync(query, cancellationToken, false).ConfigureAwait(false);
8586
}
8687

87-
private async Task<BlockedDbDataReader> GetReaderAsync(IQuery query, CancellationToken cancellationToken, bool sync)
88+
private async Task<DbDataReader> GetReaderAsync(IQuery query, CancellationToken cancellationToken, bool sync)
8889
{
8990
Guard.AgainstNull(query, nameof(query));
9091

Shuttle.Core.Data/DbCommandFactory.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Data;
3+
using System.Data.Common;
34
using Microsoft.Extensions.Options;
45
using Shuttle.Core.Contract;
56

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

1920
public event EventHandler<DbCommandCreatedEventArgs> DbCommandCreated;
2021

21-
public IDbCommand Create(IDbConnection connection, IQuery query)
22+
public DbCommand Create(IDbConnection connection, IQuery query)
2223
{
2324
var command = Guard.AgainstNull(connection, nameof(connection)).CreateCommand();
2425

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

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

31-
return command;
32+
return (DbCommand)command;
3233
}
3334
}
3435
}

Shuttle.Core.Data/IDatabaseContext.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Data;
3+
using System.Data.Common;
34
using System.Threading.Tasks;
45
using IsolationLevel = System.Data.IsolationLevel;
56

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

18-
IDbTransaction Transaction { get; }
19-
BlockedDbCommand CreateCommand(IQuery query);
20-
BlockedDbConnection GetDbConnection();
19+
DbTransaction Transaction { get; }
20+
DbCommand CreateCommand(IQuery query);
21+
IDbConnection GetDbConnection();
2122

2223
bool HasTransaction { get; }
2324
string ProviderName { get; }

Shuttle.Core.Data/IDatabaseGateway.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
using System.Collections.Generic;
22
using System.Data;
3+
using System.Data.Common;
34
using System.Threading;
45
using System.Threading.Tasks;
56

67
namespace Shuttle.Core.Data
78
{
89
public interface IDatabaseGateway
910
{
10-
BlockedDbDataReader GetReader(IQuery query, CancellationToken cancellationToken = default);
11+
DbDataReader GetReader(IQuery query, CancellationToken cancellationToken = default);
1112
int Execute(IQuery query, CancellationToken cancellationToken = default);
1213
T GetScalar<T>(IQuery query, CancellationToken cancellationToken = default);
1314
DataTable GetDataTable(IQuery query, CancellationToken cancellationToken = default);
1415
IEnumerable<DataRow> GetRows(IQuery query, CancellationToken cancellationToken = default);
1516
DataRow GetRow(IQuery query, CancellationToken cancellationToken = default);
1617

17-
Task<BlockedDbDataReader> GetReaderAsync(IQuery query, CancellationToken cancellationToken = default);
18+
Task<DbDataReader> GetReaderAsync(IQuery query, CancellationToken cancellationToken = default);
1819
Task<int> ExecuteAsync(IQuery query, CancellationToken cancellationToken = default);
1920
Task<T> GetScalarAsync<T>(IQuery query, CancellationToken cancellationToken = default);
2021
Task<DataTable> GetDataTableAsync(IQuery query, CancellationToken cancellationToken = default);
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
using System;
22
using System.Data;
3+
using System.Data.Common;
34

45
namespace Shuttle.Core.Data
56
{
67
public interface IDbCommandFactory
78
{
89
event EventHandler<DbCommandCreatedEventArgs> DbCommandCreated;
910

10-
IDbCommand Create(IDbConnection connection, IQuery query);
11+
DbCommand Create(IDbConnection connection, IQuery query);
1112
}
1213
}

0 commit comments

Comments
 (0)