diff --git a/Changelog.md b/Changelog.md
index f9d0557..e298766 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -4,6 +4,7 @@
- Target Framework supported: netstandard2.0, net462
- Updated System.Data.SqlClient 4.8.5
+- Added Azure SQL support to MsSqlDialect [#31](https://github.com/NEventStore/NEventStore.Persistence.SQL/issues/31)
- Fix: NEventStore constraint failed with MySql 8.x (works with 5.7) [#487](https://github.com/NEventStore/NEventStore/issues/487)
### Breaking Change
@@ -11,6 +12,7 @@
- The fix for [#487](https://github.com/NEventStore/NEventStore/issues/487) changed how the `Commits` table is created for MySql 8.x:
to update an existing database in order to run on 8.x you need to manually update the `Commits` table schema and change the constraint of the `CommitId` column
from: `CommitId binary(16) NOT NULL CHECK (CommitId != 0)` to: `CommitId binary(16) NOT NULL CHECK (CommitId <> 0x00)`.
+- MsSqlDialects now have an `useAzureSql` parameter, if set to `true` the statement `WITH (READCOMMITTEDLOCK)` will be added to any `FROM Commits` query.
## 9.0.1
diff --git a/src/NEventStore.Persistence.MsSql.Tests/PersistenceEngineFixture.cs b/src/NEventStore.Persistence.MsSql.Tests/PersistenceEngineFixture.cs
index 0e62580..6ac6d95 100644
--- a/src/NEventStore.Persistence.MsSql.Tests/PersistenceEngineFixture.cs
+++ b/src/NEventStore.Persistence.MsSql.Tests/PersistenceEngineFixture.cs
@@ -1,20 +1,20 @@
using NEventStore.Persistence.Sql.Tests;
-namespace NEventStore.Persistence.AcceptanceTests
-{
+namespace NEventStore.Persistence.AcceptanceTests {
using NEventStore.Persistence.Sql;
using NEventStore.Persistence.Sql.SqlDialects;
using NEventStore.Serialization;
using System.Data.SqlClient;
using System.Transactions;
- public partial class PersistenceEngineFixture
- {
+ public partial class PersistenceEngineFixture {
+ public ISqlDialect SqlDialect { get; set; } = new MsSqlDialect();
+
///
/// this mimic the current NEventStore default values which is run outside any transaction (creates a scope that
/// suppresses any transaction)
///
- public TransactionScopeOption? ScopeOption { get; set; } = null; // the old default: TransactionScopeOption.Suppress;
+ public TransactionScopeOption? ScopeOption { get; set; } // the old default: TransactionScopeOption.Suppress;
public PersistenceEngineFixture()
{
@@ -22,7 +22,7 @@ public PersistenceEngineFixture()
_createPersistence = pageSize =>
new SqlPersistenceFactory(new EnviromentConnectionFactory("MsSql", "System.Data.SqlClient"),
new BinarySerializer(),
- new MsSqlDialect(),
+ SqlDialect,
pageSize: pageSize,
scopeOption: ScopeOption
).Build();
@@ -30,7 +30,7 @@ public PersistenceEngineFixture()
_createPersistence = pageSize =>
new SqlPersistenceFactory(new EnviromentConnectionFactory("MsSql", SqlClientFactory.Instance),
new BinarySerializer(),
- new MsSqlDialect(),
+ SqlDialect,
pageSize: pageSize,
scopeOption: ScopeOption
).Build();
diff --git a/src/NEventStore.Persistence.Sql.Tests/PersistenceTests.Transactions.cs b/src/NEventStore.Persistence.Sql.Tests/PersistenceTests.Transactions.cs
index 63fd41e..2f1333b 100644
--- a/src/NEventStore.Persistence.Sql.Tests/PersistenceTests.Transactions.cs
+++ b/src/NEventStore.Persistence.Sql.Tests/PersistenceTests.Transactions.cs
@@ -25,6 +25,10 @@ namespace NEventStore.Persistence.AcceptanceTests
using Xunit.Should;
#endif
+ // The following tests actually works well only for MsSqlServer
+ // we'll need some refactoring to have initialization work
+ // for different of kinds of databases.
+
public enum TransactionScopeConcern
{
NoTransaction = 0,
@@ -319,11 +323,11 @@ public class Unsupported_Multiple_Completing_TransactionScopes_When_EnlistInAmbi
public Unsupported_Multiple_Completing_TransactionScopes_When_EnlistInAmbientTransaction_is_and_IsolationLevel_is(
TransactionScopeConcern enlistInAmbientTransaction,
IsolationLevel transationIsolationLevel
- ) : base(enlistInAmbientTransaction, transationIsolationLevel, true)
+ ) : base(enlistInAmbientTransaction, transationIsolationLevel, completeTransaction: true)
{ }
[Fact]
- public void should_throw_an_Exception_only_if_no_transaction_or_enlist_in_ambient_transaction_and_IsolationLevel_is_Serializable()
+ public void Should_throw_an_Exception_only_if_no_transaction_or_enlist_in_ambient_transaction_and_IsolationLevel_is_Serializable()
{
_thrown.Should().BeOfType();
_thrown.InnerException.Should().BeOfType();
diff --git a/src/NEventStore.Persistence.Sql/SqlDialects/MsSqlDialect.cs b/src/NEventStore.Persistence.Sql/SqlDialects/MsSqlDialect.cs
index 0de1364..061d8cc 100644
--- a/src/NEventStore.Persistence.Sql/SqlDialects/MsSqlDialect.cs
+++ b/src/NEventStore.Persistence.Sql/SqlDialects/MsSqlDialect.cs
@@ -2,78 +2,77 @@
using System.Transactions;
using IsolationLevel = System.Data.IsolationLevel;
-namespace NEventStore.Persistence.Sql.SqlDialects
-{
+namespace NEventStore.Persistence.Sql.SqlDialects {
using System;
using System.Data.SqlClient;
- public class MsSqlDialect : CommonSqlDialect
- {
+ public class MsSqlDialect : CommonSqlDialect {
private const int UniqueIndexViolation = 2601;
private const int UniqueKeyViolation = 2627;
- public override string InitializeStorage
- {
+ ///
+ /// Add "WITH (READCOMMITTEDLOCK)" hint to any "FROM Commits" clause
+ /// (#31) Make MsSqlDialect compatible with AzureSql and READ COMMITTED SNAPSHOT
+ ///
+ private readonly bool _addReadCommittedLockToFromCommits;
+
+ ///
+ /// Constructor
+ ///
+ /// Add "WITH (READCOMMITTEDLOCK)" hint to any "FROM Commits" clause, can have an impact on multiple transactions scenarios
+ public MsSqlDialect(bool addReadCommittedLockToFromCommits = false) {
+ _addReadCommittedLockToFromCommits = addReadCommittedLockToFromCommits;
+ }
+
+ public override string InitializeStorage {
get { return MsSqlStatements.InitializeStorage; }
}
- public override string GetSnapshot
- {
+ public override string GetSnapshot {
get { return "SET ROWCOUNT 1;\n" + base.GetSnapshot.Replace("LIMIT 1;", ";"); }
}
- public override string GetCommitsFromStartingRevision
- {
- get { return NaturalPaging(base.GetCommitsFromStartingRevision); }
+ public override string GetCommitsFromStartingRevision {
+ get { return AddReadCommittedLockToFromCommits(NaturalPaging(base.GetCommitsFromStartingRevision)); }
}
- public override string GetCommitsFromInstant
- {
- get { return CommonTableExpressionPaging(base.GetCommitsFromInstant); }
+ public override string GetCommitsFromInstant {
+ get { return AddReadCommittedLockToFromCommits(CommonTableExpressionPaging(base.GetCommitsFromInstant)); }
}
- public override string GetCommitsFromToInstant
- {
- get { return CommonTableExpressionPaging(base.GetCommitsFromToInstant); }
+ public override string GetCommitsFromToInstant {
+ get { return AddReadCommittedLockToFromCommits(CommonTableExpressionPaging(base.GetCommitsFromToInstant)); }
}
- public override string PersistCommit
- {
+ public override string PersistCommit {
get { return MsSqlStatements.PersistCommits; }
}
- public override string GetCommitsFromCheckpoint
- {
- get { return CommonTableExpressionPaging(base.GetCommitsFromCheckpoint); }
+ public override string GetCommitsFromCheckpoint {
+ get { return AddReadCommittedLockToFromCommits(CommonTableExpressionPaging(base.GetCommitsFromCheckpoint)); }
}
- public override string GetCommitsFromToCheckpoint
- {
- get { return CommonTableExpressionPaging(base.GetCommitsFromToCheckpoint); }
+ public override string GetCommitsFromToCheckpoint {
+ get { return AddReadCommittedLockToFromCommits(CommonTableExpressionPaging(base.GetCommitsFromToCheckpoint)); }
}
- public override string GetCommitsFromBucketAndCheckpoint
- {
- get { return CommonTableExpressionPaging(base.GetCommitsFromBucketAndCheckpoint); }
+ public override string GetCommitsFromBucketAndCheckpoint {
+ get { return AddReadCommittedLockToFromCommits(CommonTableExpressionPaging(base.GetCommitsFromBucketAndCheckpoint)); }
}
- public override string GetCommitsFromToBucketAndCheckpoint
- {
- get { return CommonTableExpressionPaging(base.GetCommitsFromToBucketAndCheckpoint); }
+ public override string GetCommitsFromToBucketAndCheckpoint {
+ get { return AddReadCommittedLockToFromCommits(CommonTableExpressionPaging(base.GetCommitsFromToBucketAndCheckpoint)); }
}
- public override string GetStreamsRequiringSnapshots
- {
+ public override string GetStreamsRequiringSnapshots {
get { return NaturalPaging(base.GetStreamsRequiringSnapshots); }
}
- private static string NaturalPaging(string query)
- {
+ private static string NaturalPaging(string query) {
return "SET ROWCOUNT @Limit;\n" + RemovePaging(query);
}
- private static string CommonTableExpressionPaging(string query)
- {
+ private static string CommonTableExpressionPaging(string query) {
query = RemovePaging(query);
int orderByIndex = query.IndexOf("ORDER BY");
string orderBy = query.Substring(orderByIndex).Replace(";", string.Empty);
@@ -83,37 +82,45 @@ private static string CommonTableExpressionPaging(string query)
string from = query.Substring(fromIndex);
string select = query.Substring(0, fromIndex);
- string value = MsSqlStatements.PagedQueryFormat.FormatWith(select, orderBy, from);
- return value;
+ return MsSqlStatements.PagedQueryFormat.FormatWith(select, orderBy, from);
}
- private static string RemovePaging(string query)
- {
+ private static string RemovePaging(string query) {
return query
.Replace("\n LIMIT @Limit OFFSET @Skip;", ";")
.Replace("\n LIMIT @Limit;", ";");
}
- public override bool IsDuplicate(Exception exception)
- {
+ public override bool IsDuplicate(Exception exception) {
var dbException = exception as SqlException;
return dbException != null
&& (dbException.Number == UniqueIndexViolation || dbException.Number == UniqueKeyViolation);
}
- public override IDbTransaction OpenTransaction(IDbConnection connection)
- {
+ public override IDbTransaction OpenTransaction(IDbConnection connection) {
if (Transaction.Current == null)
return connection.BeginTransaction(IsolationLevel.ReadCommitted);
return base.OpenTransaction(connection);
}
+
+ ///
+ /// (#31) Add 'WITH (READCOMMITTEDLOCK)' to all 'FROM Commits' statements
+ ///
+ ///
+ private string AddReadCommittedLockToFromCommits(string query) {
+ if (!_addReadCommittedLockToFromCommits) {
+ return query;
+ }
+ return query.Replace("FROM Commits", "FROM Commits WITH (READCOMMITTEDLOCK)");
+ }
}
- public class MsSql2005Dialect : MsSqlDialect
- {
- public override DbType GetDateTimeDbType()
- {
+ public class MsSql2005Dialect : MsSqlDialect {
+ public MsSql2005Dialect(bool addReadCommittedLockToFromCommits = false) : base(addReadCommittedLockToFromCommits) {
+ }
+
+ public override DbType GetDateTimeDbType() {
return DbType.DateTime;
}
}