diff --git a/src/Context.Tests/Context.Tests.csproj b/src/Context.Tests/Context.Tests.csproj
index f1afb72..4fec853 100644
--- a/src/Context.Tests/Context.Tests.csproj
+++ b/src/Context.Tests/Context.Tests.csproj
@@ -8,6 +8,7 @@
+
diff --git a/src/Context.Tests/Helpers/Bar.cs b/src/Context.Tests/Helpers/Bar.cs
index 3d506df..0bfa81a 100644
--- a/src/Context.Tests/Helpers/Bar.cs
+++ b/src/Context.Tests/Helpers/Bar.cs
@@ -2,5 +2,7 @@ namespace MongoDB.Extensions.Context.Tests
{
public class Bar
{
+ public int Id { get; set; }
+ public string? BarName { get; set; }
}
}
diff --git a/src/Context.Tests/Helpers/Foo.cs b/src/Context.Tests/Helpers/Foo.cs
index dda2a87..90560dc 100644
--- a/src/Context.Tests/Helpers/Foo.cs
+++ b/src/Context.Tests/Helpers/Foo.cs
@@ -2,5 +2,7 @@ namespace MongoDB.Extensions.Context.Tests
{
public class Foo
{
+ public int Id { get; set; }
+ public string? FooName { get; set; }
}
}
diff --git a/src/Context.Tests/MongoTransactionDbContextTests.cs b/src/Context.Tests/MongoTransactionDbContextTests.cs
new file mode 100644
index 0000000..d41337e
--- /dev/null
+++ b/src/Context.Tests/MongoTransactionDbContextTests.cs
@@ -0,0 +1,267 @@
+using System;
+using System.Reflection.Metadata;
+using System.Threading.Tasks;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Serializers;
+using MongoDB.Driver;
+using MongoDB.Prime.Extensions;
+using Snapshooter.Xunit;
+using Squadron;
+using Xunit;
+
+namespace MongoDB.Extensions.Context.Tests
+{
+ public class MongoTransactionDbContextTests : IClassFixture
+ {
+ private readonly MongoOptions _mongoOptions;
+ private readonly IMongoDatabase _mongoDatabase;
+
+ public MongoTransactionDbContextTests(MongoReplicaSetResource mongoResource)
+ {
+ _mongoDatabase = mongoResource.CreateDatabase();
+ _mongoOptions = new MongoOptions
+ {
+ ConnectionString = mongoResource.ConnectionString,
+ DatabaseName = _mongoDatabase.DatabaseNamespace.DatabaseName
+ };
+ }
+
+ #region StartNewTransactionAsync Tests
+
+ [Fact]
+ public async Task StartNewTransactionAsync_CreateNewTransactionDbContext_OptionsCorrect()
+ {
+ // Arrange
+ var testMongoDbContext = new TestMongoDbContext(_mongoOptions);
+
+ // Act
+ using IMongoTransactionDbContext transactionDbContext =
+ await testMongoDbContext.StartNewTransactionAsync();
+
+ // Assert
+ Snapshot.Match(transactionDbContext.TransactionOptions);
+ }
+
+ [Fact]
+ public async Task StartNewTransactionAsync_SetTransactionTransactionOptions_OptionsCorrect()
+ {
+ // Arrange
+ var testMongoDbContext = new TestMongoDbContext(_mongoOptions);
+
+ TransactionOptions transactionOptions =
+ new TransactionOptions(
+ ReadConcern.Local,
+ ReadPreference.Secondary,
+ WriteConcern.W3.With(journal: false),
+ TimeSpan.FromSeconds(300));
+
+ // Act
+ IMongoTransactionDbContext transactionDbContext =
+ await testMongoDbContext.StartNewTransactionAsync(transactionOptions);
+
+ // Assert
+ Snapshot.Match(transactionDbContext.TransactionOptions);
+ }
+
+ [Fact]
+ public async Task StartNewTransactionAsync_ClientSessionIdCompare_DifferentIds()
+ {
+ // Arrange
+ var testMongoDbContext = new TestMongoDbContext(_mongoOptions);
+ IClientSessionHandle baseSession =
+ await testMongoDbContext.Client.StartSessionAsync();
+
+ // Act
+ using MongoTransactionDbContext transactionDbContext = (MongoTransactionDbContext)
+ await testMongoDbContext.StartNewTransactionAsync();
+
+ // Assert
+ Assert.NotEqual(
+ baseSession.GetSessionId(),
+ transactionDbContext.ClientSession.GetSessionId());
+ }
+
+ [Fact]
+ public async Task StartNewTransactionAsync_GetTwicSameCollections_CollectionCached()
+ {
+ // Arrange
+ var testMongoDbContext = new TestMongoDbContext(_mongoOptions);
+
+ using IMongoTransactionDbContext transactionDbContext =
+ await testMongoDbContext.StartNewTransactionAsync();
+
+ // Act
+ IMongoCollection ref1 = transactionDbContext.CreateCollection();
+ IMongoCollection ref2 = transactionDbContext.CreateCollection();
+ IMongoCollection ref3 = transactionDbContext.GetCollection();
+
+ // Assert
+ Assert.Same(ref1, ref2);
+ Assert.Same(ref2, ref3);
+ }
+
+ [Fact]
+ public async Task StartNewTransactionAsync_AddFooBarWithoutCommit_NothingSaved()
+ {
+ // Arrange
+ var testMongoDbContext = new TestMongoDbContext(_mongoOptions);
+
+ using IMongoTransactionDbContext transactionDbContext =
+ await testMongoDbContext.StartNewTransactionAsync();
+
+ // Act
+ transactionDbContext.GetCollection().InsertOne(new Foo { Id = 1, FooName = "Foo1" });
+ transactionDbContext.GetCollection().InsertOne(new Bar { Id = 1, BarName = "Bar1" });
+
+ // Assert
+ Assert.Empty(_mongoDatabase.GetCollection().Dump());
+ Assert.Empty(_mongoDatabase.GetCollection().Dump());
+ }
+
+ [Fact]
+ public async Task StartNewTransactionAsync_AddFooBarWithCommit_AllSaved()
+ {
+ // Arrange
+ var testMongoDbContext = new TestMongoDbContext(_mongoOptions);
+
+ using IMongoTransactionDbContext transactionDbContext =
+ await testMongoDbContext.StartNewTransactionAsync();
+
+ // Act
+ transactionDbContext.GetCollection().InsertOne(new Foo { Id = 1, FooName = "Foo1" });
+ transactionDbContext.GetCollection().InsertOne(new Bar { Id = 1, BarName = "Bar1" });
+ await transactionDbContext.CommitAsync();
+
+ // Assert
+ Snapshot.Match(_mongoDatabase.DumpAllCollections());
+ }
+
+ [Fact]
+ public async Task StartNewTransactionAsync_AddFooBarWithRollback_NothingSaved()
+ {
+ // Arrange
+ var testMongoDbContext = new TestMongoDbContext(_mongoOptions);
+
+ using IMongoTransactionDbContext transactionDbContext =
+ await testMongoDbContext.StartNewTransactionAsync();
+
+ // Act
+ transactionDbContext.GetCollection().InsertOne(new Foo { Id = 1, FooName = "Foo1" });
+ transactionDbContext.GetCollection().InsertOne(new Bar { Id = 1, BarName = "Bar1" });
+ await transactionDbContext.RollbackAsync();
+
+ // Assert
+ Assert.Empty(_mongoDatabase.GetCollection().Dump());
+ Assert.Empty(_mongoDatabase.GetCollection().Dump());
+ }
+
+ [Fact]
+ public async Task StartNewTransactionAsync_TwoTransactionContextWithSameObjects_ConcurrencyExceptionAndSaved()
+ {
+ // Arrange
+ var testMongoDbContext = new TestMongoDbContext(_mongoOptions);
+
+ using IMongoTransactionDbContext transactionDbContext1 =
+ await testMongoDbContext.StartNewTransactionAsync();
+
+ using IMongoTransactionDbContext transactionDbContext2 =
+ await testMongoDbContext.StartNewTransactionAsync();
+
+ IMongoCollection transactionCollection1 =
+ transactionDbContext1.GetCollection();
+
+ IMongoCollection transactionCollection2 =
+ transactionDbContext2.GetCollection();
+
+ // Act
+ transactionCollection1.InsertOne(new Foo { Id = 1, FooName = "Foo1a" });
+ transactionCollection2.InsertOne(new Foo { Id = 1, FooName = "Foo1b" });
+ await transactionDbContext1.CommitAsync();
+ Func commit2Action = async () => await transactionDbContext2.CommitAsync();
+
+ // Assert
+ await Assert.ThrowsAsync(commit2Action);
+ Snapshot.Match(_mongoDatabase.DumpAllCollections());
+ }
+
+ [Fact]
+ public async Task StartNewTransactionAsync_TwoTransactionContextWithDifferentObjectsNoCommit_NothingSaved()
+ {
+ // Arrange
+ var testMongoDbContext = new TestMongoDbContext(_mongoOptions);
+ testMongoDbContext.CreateCollection();
+
+ using IMongoTransactionDbContext transactionDbContext1 =
+ await testMongoDbContext.StartNewTransactionAsync();
+
+ using IMongoTransactionDbContext transactionDbContext2 =
+ await testMongoDbContext.StartNewTransactionAsync();
+
+ IMongoCollection transactionCollection1 =
+ transactionDbContext1.GetCollection();
+
+ IMongoCollection transactionCollection2 =
+ transactionDbContext2.GetCollection();
+
+ // Act
+ transactionCollection1.InsertOne(new Foo { Id = 1, FooName = "Foo1a" });
+ transactionCollection2.InsertOne(new Foo { Id = 2, FooName = "Foo1b" });
+
+ // Assert
+ Assert.Empty(_mongoDatabase.GetCollection().Dump());
+ Assert.Empty(_mongoDatabase.GetCollection().Dump());
+ }
+
+ [Fact]
+ public async Task StartNewTransactionAsync_TwoTransactionContextWithDifferentObjects_AllSaved()
+ {
+ // Arrange
+ var testMongoDbContext = new TestMongoDbContext(_mongoOptions);
+ testMongoDbContext.CreateCollection();
+
+ using IMongoTransactionDbContext transactionDbContext1 =
+ await testMongoDbContext.StartNewTransactionAsync();
+
+ using IMongoTransactionDbContext transactionDbContext2 =
+ await testMongoDbContext.StartNewTransactionAsync();
+
+ IMongoCollection transactionCollection1 =
+ transactionDbContext1.GetCollection();
+
+ IMongoCollection transactionCollection2 =
+ transactionDbContext2.GetCollection();
+
+ // Act
+ transactionCollection1.InsertOne(new Foo { Id = 1, FooName = "Foo1a" });
+ await transactionDbContext1.CommitAsync();
+
+ transactionCollection2.InsertOne(new Foo { Id = 544656454, FooName = "Foo1b" });
+ await transactionDbContext2.CommitAsync();
+
+ // Assert
+ Snapshot.Match(_mongoDatabase.DumpAllCollections());
+ }
+
+ #endregion
+
+ #region Private Helpers
+
+ private class TestMongoDbContext : MongoDbContext
+ {
+ public TestMongoDbContext(MongoOptions mongoOptions) : base(mongoOptions)
+ {
+ }
+
+ public TestMongoDbContext(MongoOptions mongoOptions, bool enableAutoInit)
+ : base(mongoOptions, enableAutoInit)
+ {
+ }
+
+ protected override void OnConfiguring(IMongoDatabaseBuilder mongoDatabaseBuilder)
+ {
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_AddFooBarWithCommit_AllSaved.snap b/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_AddFooBarWithCommit_AllSaved.snap
new file mode 100644
index 0000000..efe4f51
--- /dev/null
+++ b/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_AddFooBarWithCommit_AllSaved.snap
@@ -0,0 +1,32 @@
+[
+ {
+ "Key": "Bar",
+ "Value": [
+ [
+ {
+ "Name": "_id",
+ "Value": 1
+ },
+ {
+ "Name": "BarName",
+ "Value": "Bar1"
+ }
+ ]
+ ]
+ },
+ {
+ "Key": "Foo",
+ "Value": [
+ [
+ {
+ "Name": "_id",
+ "Value": 1
+ },
+ {
+ "Name": "FooName",
+ "Value": "Foo1"
+ }
+ ]
+ ]
+ }
+]
diff --git a/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_AddFooBarWithRollback_NothingSaved.snap b/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_AddFooBarWithRollback_NothingSaved.snap
new file mode 100644
index 0000000..f117788
--- /dev/null
+++ b/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_AddFooBarWithRollback_NothingSaved.snap
@@ -0,0 +1 @@
+{}
diff --git a/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_CreateNewTransactionDbContext_OptionsCorrect.snap b/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_CreateNewTransactionDbContext_OptionsCorrect.snap
new file mode 100644
index 0000000..138f9fc
--- /dev/null
+++ b/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_CreateNewTransactionDbContext_OptionsCorrect.snap
@@ -0,0 +1,23 @@
+{
+ "MaxCommitTime": "00:01:00",
+ "ReadConcern": {
+ "IsServerDefault": false,
+ "Level": "Majority"
+ },
+ "ReadPreference": {
+ "Hedge": null,
+ "MaxStaleness": null,
+ "ReadPreferenceMode": "Primary",
+ "TagSets": []
+ },
+ "WriteConcern": {
+ "FSync": null,
+ "IsAcknowledged": true,
+ "IsServerDefault": false,
+ "Journal": true,
+ "W": {
+ "Value": "majority"
+ },
+ "WTimeout": null
+ }
+}
diff --git a/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_SetTransactionTransactionOptions_OptionsCorrect.snap b/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_SetTransactionTransactionOptions_OptionsCorrect.snap
new file mode 100644
index 0000000..aad5bf9
--- /dev/null
+++ b/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_SetTransactionTransactionOptions_OptionsCorrect.snap
@@ -0,0 +1,23 @@
+{
+ "MaxCommitTime": "00:05:00",
+ "ReadConcern": {
+ "IsServerDefault": false,
+ "Level": "Local"
+ },
+ "ReadPreference": {
+ "Hedge": null,
+ "MaxStaleness": null,
+ "ReadPreferenceMode": "Secondary",
+ "TagSets": []
+ },
+ "WriteConcern": {
+ "FSync": null,
+ "IsAcknowledged": true,
+ "IsServerDefault": false,
+ "Journal": false,
+ "W": {
+ "Value": 3
+ },
+ "WTimeout": null
+ }
+}
diff --git a/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_TwoTransactionContextWithDifferentObjects_AllSaved.snap b/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_TwoTransactionContextWithDifferentObjects_AllSaved.snap
new file mode 100644
index 0000000..6e9c350
--- /dev/null
+++ b/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_TwoTransactionContextWithDifferentObjects_AllSaved.snap
@@ -0,0 +1,27 @@
+[
+ {
+ "Key": "Foo",
+ "Value": [
+ [
+ {
+ "Name": "_id",
+ "Value": 1
+ },
+ {
+ "Name": "FooName",
+ "Value": "Foo1a"
+ }
+ ],
+ [
+ {
+ "Name": "_id",
+ "Value": 544656454
+ },
+ {
+ "Name": "FooName",
+ "Value": "Foo1b"
+ }
+ ]
+ ]
+ }
+]
diff --git a/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_TwoTransactionContextWithSameObjects_ConcurrencyExceptionAndSaved.snap b/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_TwoTransactionContextWithSameObjects_ConcurrencyExceptionAndSaved.snap
new file mode 100644
index 0000000..821719f
--- /dev/null
+++ b/src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_TwoTransactionContextWithSameObjects_ConcurrencyExceptionAndSaved.snap
@@ -0,0 +1,17 @@
+[
+ {
+ "Key": "Foo",
+ "Value": [
+ [
+ {
+ "Name": "_id",
+ "Value": 1
+ },
+ {
+ "Name": "FooName",
+ "Value": "Foo1a"
+ }
+ ]
+ ]
+ }
+]
diff --git a/src/Context/Context.csproj b/src/Context/Context.csproj
index b62c316..e720159 100644
--- a/src/Context/Context.csproj
+++ b/src/Context/Context.csproj
@@ -42,4 +42,8 @@
+
+
+
+
diff --git a/src/Context/DefaultDefinitions.cs b/src/Context/DefaultDefinitions.cs
new file mode 100644
index 0000000..c722861
--- /dev/null
+++ b/src/Context/DefaultDefinitions.cs
@@ -0,0 +1,15 @@
+using System;
+using MongoDB.Driver;
+
+namespace MongoDB.Extensions.Context
+{
+ public static class DefaultDefinitions
+ {
+ public static readonly TransactionOptions DefaultTransactionOptions =
+ new TransactionOptions(
+ ReadConcern.Majority,
+ ReadPreference.Primary,
+ WriteConcern.WMajority.With(journal: true),
+ TimeSpan.FromSeconds(60));
+ }
+}
diff --git a/src/Context/IMongoDbTransaction.cs b/src/Context/IMongoDbTransaction.cs
new file mode 100644
index 0000000..0575b48
--- /dev/null
+++ b/src/Context/IMongoDbTransaction.cs
@@ -0,0 +1,13 @@
+using System.Threading.Tasks;
+using System.Threading;
+using MongoDB.Driver;
+
+namespace MongoDB.Extensions.Context
+{
+ public interface IMongoDbTransaction
+ {
+ Task StartNewTransactionAsync(
+ TransactionOptions? transactionOptions = null,
+ CancellationToken cancellationToken = default);
+ }
+}
diff --git a/src/Context/IMongoTransactionDbContext.cs b/src/Context/IMongoTransactionDbContext.cs
new file mode 100644
index 0000000..dcfdb60
--- /dev/null
+++ b/src/Context/IMongoTransactionDbContext.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Threading.Tasks;
+using System.Threading;
+using MongoDB.Driver;
+
+namespace MongoDB.Extensions.Context;
+
+public interface IMongoTransactionDbContext : IMongoDbContext, IDisposable
+{
+ TransactionOptions TransactionOptions { get; }
+
+ IMongoCollection GetCollection() where TDocument : class;
+
+ Task CommitAsync(CancellationToken cancellationToken = default);
+ Task RollbackAsync(CancellationToken cancellationToken = default);
+}
diff --git a/src/Context/Internal/MongoDbContextData.cs b/src/Context/Internal/MongoDbContextData.cs
index f72c395..c164303 100644
--- a/src/Context/Internal/MongoDbContextData.cs
+++ b/src/Context/Internal/MongoDbContextData.cs
@@ -37,9 +37,13 @@ private IMongoCollection GetConfiguredCollection()
{
lock (_lockObject)
{
+ configuredCollection =
+ _mongoCollections.TryGetCollection();
+
if (configuredCollection == null)
{
- return AddDefaultCollection();
+ configuredCollection =
+ AddDefaultCollection();
}
}
}
diff --git a/src/Context/MongoDbContext.cs b/src/Context/MongoDbContext.cs
index 2e6e90d..056542a 100644
--- a/src/Context/MongoDbContext.cs
+++ b/src/Context/MongoDbContext.cs
@@ -1,86 +1,102 @@
using System;
+using System.Threading;
+using System.Threading.Tasks;
using MongoDB.Driver;
-namespace MongoDB.Extensions.Context
-{
- public abstract class MongoDbContext : IMongoDbContext
- {
- private MongoDbContextData? _mongoDbContextData;
+namespace MongoDB.Extensions.Context;
- private readonly object _lockObject = new object();
+public abstract class MongoDbContext : IMongoDbContext, IMongoDbTransaction
+{
+ private MongoDbContextData? _mongoDbContextData;
- public MongoDbContext(MongoOptions mongoOptions) : this(mongoOptions, true)
- {
- }
+ private readonly object _lockObject = new object();
- public MongoDbContext(MongoOptions mongoOptions, bool enableAutoInitialize)
- {
- MongoOptions = mongoOptions.Validate();
+ public MongoDbContext(MongoOptions mongoOptions) : this(mongoOptions, true)
+ {
+ }
- if (enableAutoInitialize)
- {
- Initialize();
- }
- }
- public MongoOptions MongoOptions { get; }
+ public MongoDbContext(MongoOptions mongoOptions, bool enableAutoInitialize)
+ {
+ MongoOptions = mongoOptions.Validate();
- public IMongoClient Client
+ if (enableAutoInitialize)
{
- get
- {
- EnsureInitialized();
- return _mongoDbContextData!.Client;
- }
+ Initialize();
}
+ }
+ public MongoOptions MongoOptions { get; }
- public IMongoDatabase Database
+ public IMongoClient Client
+ {
+ get
{
- get
- {
- EnsureInitialized();
- return _mongoDbContextData!.Database;
- }
+ EnsureInitialized();
+ return _mongoDbContextData!.Client;
}
-
- public IMongoCollection CreateCollection()
- where TDocument : class
+ }
+
+ public IMongoDatabase Database
+ {
+ get
{
EnsureInitialized();
- return _mongoDbContextData!.GetCollection();
+ return _mongoDbContextData!.Database;
}
+ }
+
+ public IMongoCollection CreateCollection()
+ where TDocument : class
+ {
+ EnsureInitialized();
+ return _mongoDbContextData!.GetCollection();
+ }
- protected abstract void OnConfiguring(IMongoDatabaseBuilder mongoDatabaseBuilder);
-
- public virtual void Initialize()
+ protected abstract void OnConfiguring(IMongoDatabaseBuilder mongoDatabaseBuilder);
+
+ public virtual void Initialize()
+ {
+ if(_mongoDbContextData == null)
{
- if(_mongoDbContextData == null)
+ lock (_lockObject)
{
- lock (_lockObject)
+ if (_mongoDbContextData == null)
{
- if (_mongoDbContextData == null)
- {
- var mongoDatabaseBuilder = new MongoDatabaseBuilder(MongoOptions);
+ var mongoDatabaseBuilder = new MongoDatabaseBuilder(MongoOptions);
- OnConfiguring(mongoDatabaseBuilder);
+ OnConfiguring(mongoDatabaseBuilder);
- _mongoDbContextData = mongoDatabaseBuilder.Build();
- }
+ _mongoDbContextData = mongoDatabaseBuilder.Build();
}
}
}
+ }
- private void EnsureInitialized()
+ private void EnsureInitialized()
+ {
+ if (_mongoDbContextData == null)
{
- if (_mongoDbContextData == null)
+ lock (_lockObject)
{
- lock (_lockObject)
+ if (_mongoDbContextData == null)
{
- if (_mongoDbContextData == null)
- {
- throw new InvalidOperationException("MongoDbContext not initialized.");
- }
+ throw new InvalidOperationException("MongoDbContext not initialized.");
}
}
}
}
+
+ public async Task StartNewTransactionAsync(
+ TransactionOptions? transactionOptions = null,
+ CancellationToken cancellationToken = default)
+ {
+ transactionOptions ??= DefaultDefinitions.DefaultTransactionOptions;
+
+ IClientSessionHandle clientSession = await Client
+ .StartSessionAsync(cancellationToken: cancellationToken);
+
+ clientSession.StartTransaction(transactionOptions);
+
+ return new MongoTransactionDbContext(
+ clientSession, transactionOptions, this);
+ }
}
diff --git a/src/Context/MongoTransactionDbContext.cs b/src/Context/MongoTransactionDbContext.cs
new file mode 100644
index 0000000..800a5de
--- /dev/null
+++ b/src/Context/MongoTransactionDbContext.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Threading.Tasks;
+using System.Threading;
+using MongoDB.Driver;
+using System.Collections.Concurrent;
+using MongoDB.Extensions.Transactions;
+using MongoDB.Extensions.Context.Internal;
+
+namespace MongoDB.Extensions.Context;
+
+public class MongoTransactionDbContext : IMongoTransactionDbContext
+{
+ private readonly MongoDbContext _mongoDbContext;
+ private readonly MongoCollections _mongoCollections;
+ private readonly object _lockObject = new object();
+
+ public MongoTransactionDbContext(
+ IClientSessionHandle clientSession,
+ TransactionOptions transactionOptions,
+ MongoDbContext mongoDbContext)
+ {
+ _mongoDbContext = mongoDbContext;
+ _mongoCollections = new MongoCollections();
+
+ ClientSession = clientSession;
+ TransactionOptions = transactionOptions;
+ MongoOptions = mongoDbContext.MongoOptions;
+ Client = mongoDbContext.Client.AsTransactionClient(clientSession);
+ Database = mongoDbContext.Database.AsTransactionDatabase(clientSession);
+ }
+
+ public TransactionOptions TransactionOptions { get; }
+
+ public MongoOptions MongoOptions { get; }
+
+ public IMongoClient Client { get; }
+
+ public IMongoDatabase Database { get; }
+
+ public IClientSessionHandle ClientSession { get; }
+
+ public IMongoCollection GetCollection()
+ where TDocument : class => CreateCollection();
+
+ public IMongoCollection CreateCollection()
+ where TDocument : class
+ {
+ IMongoCollection? collection =
+ _mongoCollections.TryGetCollection();
+
+ if (collection is { })
+ {
+ return collection;
+ }
+
+ lock(_lockObject)
+ {
+ collection = _mongoCollections
+ .TryGetCollection();
+
+ if (collection is { })
+ {
+ return collection;
+ }
+
+ collection = _mongoDbContext
+ .CreateCollection()
+ .AsTransactionCollection(ClientSession);
+
+ _mongoCollections.Add(collection);
+ }
+
+ return collection;
+ }
+
+ public async Task CommitAsync(CancellationToken cancellationToken = default)
+ {
+ await ClientSession
+ .CommitTransactionAsync(cancellationToken);
+ }
+
+ public async Task RollbackAsync(CancellationToken cancellationToken = default)
+ {
+ await ClientSession
+ .AbortTransactionAsync(cancellationToken);
+ }
+
+ #region IDisposable
+
+ private bool _disposed;
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ ClientSession.Dispose();
+ }
+
+ _disposed = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ #endregion
+}
diff --git a/src/Prime.Extensions/ClientSessionHandleExtensions.cs b/src/Prime.Extensions/ClientSessionHandleExtensions.cs
new file mode 100644
index 0000000..7061ccc
--- /dev/null
+++ b/src/Prime.Extensions/ClientSessionHandleExtensions.cs
@@ -0,0 +1,12 @@
+using System;
+using MongoDB.Driver;
+
+namespace MongoDB.Prime.Extensions;
+
+public static class ClientSessionHandleExtensions
+{
+ public static Guid GetSessionId(this IClientSessionHandle clientSessionHandle)
+ {
+ return clientSessionHandle.ServerSession.Id["id"].AsGuid;
+ }
+}
diff --git a/src/Prime.Extensions/MongoCollectionExtensions.cs b/src/Prime.Extensions/MongoCollectionExtensions.cs
index 12c2917..73fee20 100644
--- a/src/Prime.Extensions/MongoCollectionExtensions.cs
+++ b/src/Prime.Extensions/MongoCollectionExtensions.cs
@@ -62,6 +62,23 @@ public static Task CountDocumentsAsync(
FilterDefinition.Empty, options, cancellationToken);
}
+ ///
+ /// Reads all entries of a collection and returns a list of it.
+ ///
+ /// The type of the document.
+ /// The collection to dump.
+ public static IEnumerable Dump(
+ this IMongoCollection collection)
+ {
+ SortDefinition sortDefinition =
+ Builders.Sort.Ascending("_id");
+
+ return collection
+ .Find(FilterDefinition.Empty)
+ .Sort(sortDefinition)
+ .ToList();
+ }
+
public static Task InsertOneAsync(
this IMongoCollection collection,
TDocument document,
diff --git a/src/Prime.Extensions/MongoDatabaseExtensions.cs b/src/Prime.Extensions/MongoDatabaseExtensions.cs
index 2c1445f..c51afda 100644
--- a/src/Prime.Extensions/MongoDatabaseExtensions.cs
+++ b/src/Prime.Extensions/MongoDatabaseExtensions.cs
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Linq;
-using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -8,120 +7,142 @@
using MongoDB.Bson.IO;
using MongoDB.Driver;
-namespace MongoDB.Prime.Extensions
+namespace MongoDB.Prime.Extensions;
+
+public static class MongoDatabaseExtensions
{
- public static class MongoDatabaseExtensions
+ public static void EnableProfiling(
+ this IMongoDatabase mongoDatabase,
+ ProfileLevel profileLevel = ProfileLevel.All)
{
- public static void EnableProfiling(
- this IMongoDatabase mongoDatabase,
- ProfileLevel profileLevel = ProfileLevel.All)
- {
- var profileCommand = new BsonDocument("profile", (int)profileLevel);
+ var profileCommand = new BsonDocument("profile", (int)profileLevel);
- mongoDatabase.RunCommand(profileCommand);
- }
+ mongoDatabase.RunCommand(profileCommand);
+ }
- public static ProfilingStatus GetProfilingStatus(
- this IMongoDatabase mongoDatabase)
- {
- var profileStatusCommand = new BsonDocument("profile", -1);
+ public static ProfilingStatus GetProfilingStatus(
+ this IMongoDatabase mongoDatabase)
+ {
+ var profileStatusCommand = new BsonDocument("profile", -1);
- BsonDocument profileBsonDocument =
- mongoDatabase.RunCommand(profileStatusCommand);
+ BsonDocument profileBsonDocument =
+ mongoDatabase.RunCommand(profileStatusCommand);
- return CreateProfilingStatus(profileBsonDocument);
- }
+ return CreateProfilingStatus(profileBsonDocument);
+ }
- private static ProfilingStatus CreateProfilingStatus(
- BsonDocument profileBsonDocument)
- {
- return new ProfilingStatus(
- level: (ProfileLevel)profileBsonDocument["was"].AsInt32,
- slowMs: profileBsonDocument["slowms"].AsInt32,
- sampleRate: profileBsonDocument["sampleRate"].AsDouble,
- filter: profileBsonDocument["ok"].AsDouble.ToString());
- }
+ private static ProfilingStatus CreateProfilingStatus(
+ BsonDocument profileBsonDocument)
+ {
+ return new ProfilingStatus(
+ level: (ProfileLevel)profileBsonDocument["was"].AsInt32,
+ slowMs: profileBsonDocument["slowms"].AsInt32,
+ sampleRate: profileBsonDocument["sampleRate"].AsDouble,
+ filter: profileBsonDocument["ok"].AsDouble.ToString());
+ }
- public static IEnumerable GetProfiledOperations(
- this IMongoDatabase mongoDatabase)
- {
- IMongoCollection collection = mongoDatabase
- .GetCollection("system.profile");
-
- List docs = collection
- .Find(new BsonDocument())
- .ToList();
-
- IEnumerable jsons = docs.Select(bson => bson.ToJson(
- new JsonWriterSettings
- {
- OutputMode = JsonOutputMode.RelaxedExtendedJson
- }));
-
- IEnumerable normalizedJson = jsons
- .Select(json => JsonSerializer
- .Serialize(
- JsonDocument.Parse(json).RootElement,
- new JsonSerializerOptions()
- {
- WriteIndented = true
- }));
-
- return normalizedJson;
- }
+ public static IEnumerable GetProfiledOperations(
+ this IMongoDatabase mongoDatabase)
+ {
+ IMongoCollection collection = mongoDatabase
+ .GetCollection("system.profile");
+
+ List docs = collection
+ .Find(new BsonDocument())
+ .ToList();
+
+ IEnumerable jsons = docs.Select(bson => bson.ToJson(
+ new JsonWriterSettings
+ {
+ OutputMode = JsonOutputMode.RelaxedExtendedJson
+ }));
+
+ IEnumerable normalizedJson = jsons
+ .Select(json => JsonSerializer
+ .Serialize(
+ JsonDocument.Parse(json).RootElement,
+ new JsonSerializerOptions()
+ {
+ WriteIndented = true
+ }));
+
+ return normalizedJson;
+ }
+
+ ///
+ /// Gets the collection by Type name. The name of the collection
+ /// will be the typeof(TDocument).Name.
+ ///
+ /// The type of document.
+ /// The mongo database.
+ /// The mongo collection settings.
+ /// The mongo collection with the name of the document type.
+ public static IMongoCollection GetCollection(
+ this IMongoDatabase mongoDatabase,
+ MongoCollectionSettings? settings = null)
+ {
+ return mongoDatabase
+ .GetCollection(typeof(TDocument).Name, settings);
+ }
- ///
- /// Gets the collection by Type name. The name of the collection
- /// will be the typeof(TDocument).Name.
- ///
- /// The type of document.
- /// The mongo database.
- /// The mongo collection settings.
- /// The mongo collection with the name of the document type.
- public static IMongoCollection GetCollection(
- this IMongoDatabase mongoDatabase,
- MongoCollectionSettings? settings = null)
+ ///
+ /// Deletes all entries of every collection of the mongo database.
+ /// The collections will NOT be dropped and the indexes stay unmodified.
+ ///
+ /// The database to clean the collections.
+ public static void CleanAllCollections(
+ this IMongoDatabase mongoDatabase)
+ {
+ foreach (var name in mongoDatabase.ListCollectionNames().ToList())
{
- return mongoDatabase
- .GetCollection(typeof(TDocument).Name, settings);
+ IMongoCollection collection =
+ mongoDatabase.GetCollection(name);
+
+ collection.CleanCollection();
}
+ }
- ///
- /// Deletes all entries of every collection of the mongo database.
- /// The collections will NOT be dropped and the indexes stay unmodified.
- ///
- /// The database to clean the collections.
- public static void CleanAllCollections(
- this IMongoDatabase mongoDatabase)
+ ///
+ /// Deletes all entries of every collection of the mongo database.
+ /// The collections will NOT be dropped and the indexes stay unmodified.
+ ///
+ /// The database to clean the collections.
+ public static async Task CleanAllCollectionsAsync(
+ this IMongoDatabase mongoDatabase,
+ CancellationToken cancellationToken = default)
+ {
+ IAsyncCursor cursor = await mongoDatabase
+ .ListCollectionNamesAsync(cancellationToken: cancellationToken);
+
+ foreach (var name in await cursor.ToListAsync(cancellationToken))
{
- foreach (var name in mongoDatabase.ListCollectionNames().ToList())
- {
- IMongoCollection collection =
- mongoDatabase.GetCollection(name);
+ IMongoCollection collection =
+ mongoDatabase.GetCollection(name);
- collection.CleanCollection();
- }
+ await collection.CleanCollectionAsync(cancellationToken);
}
+ }
- ///
- /// Deletes all entries of every collection of the mongo database.
- /// The collections will NOT be dropped and the indexes stay unmodified.
- ///
- /// The database to clean the collections.
- public static async Task CleanAllCollectionsAsync(
- this IMongoDatabase mongoDatabase,
- CancellationToken cancellationToken = default)
- {
- IAsyncCursor cursor = await mongoDatabase
- .ListCollectionNamesAsync(cancellationToken: cancellationToken);
+ ///
+ /// Dumps all collections and returns it in a Dictionary.
+ ///
+ /// The database to dump all collections.
+ public static IEnumerable>> DumpAllCollections(
+ this IMongoDatabase mongoDatabase)
+ {
+ List>> dumpedCollections =
+ new List>>();
- foreach (var name in await cursor.ToListAsync(cancellationToken))
- {
- IMongoCollection collection =
- mongoDatabase.GetCollection(name);
+ foreach (var name in mongoDatabase.ListCollectionNames().ToList())
+ {
+ IEnumerable dumpedCollection =
+ mongoDatabase.GetCollection(name).Dump();
- await collection.CleanCollectionAsync(cancellationToken);
- }
+ dumpedCollections.Add(
+ new KeyValuePair>(
+ name, dumpedCollection));
}
+
+ return dumpedCollections.OrderBy(entry => entry.Key);
}
}
diff --git a/src/Session/ClientSessionHandleExtensions.cs b/src/Session/ClientSessionHandleExtensions.cs
index d996b47..bf462f1 100644
--- a/src/Session/ClientSessionHandleExtensions.cs
+++ b/src/Session/ClientSessionHandleExtensions.cs
@@ -5,6 +5,7 @@ namespace MongoDB.Extensions.Session
{
public static class ClientSessionHandleExtensions
{
+ [Obsolete("This method has been moved to MongoDB.Prime.Extensions")]
public static Guid GetSessionId(this IClientSessionHandle clientSessionHandle)
{
return clientSessionHandle.ServerSession.Id["id"].AsGuid;