2025-03-15 01:39:43 +03:30

153 lines
4.4 KiB
C#

using System.Globalization;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver;
namespace BuildingBlocks.Mongo;
// https://www.thecodebuzz.com/mongodb-repository-implementation-unit-testing-net-core-example/
public class MongoDbContext : IMongoDbContext
{
public IClientSessionHandle? Session { get; set; }
public IMongoDatabase Database { get; }
public IMongoClient MongoClient { get; }
protected readonly IList<Func<Task>> _commands;
private static readonly bool _isSerializerRegisterd;
static MongoDbContext()
{
BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String));
}
public MongoDbContext(IOptions<MongoOptions> options)
{
RegisterConventions();
MongoClient = new MongoClient(options.Value.ConnectionString);
var databaseName = options.Value.DatabaseName;
Database = MongoClient.GetDatabase(databaseName);
// Every command will be stored and it'll be processed at SaveChanges
_commands = new List<Func<Task>>();
}
private static void RegisterConventions()
{
ConventionRegistry.Register(
"conventions",
new ConventionPack
{
new CamelCaseElementNameConvention(),
new IgnoreExtraElementsConvention(true),
new EnumRepresentationConvention(BsonType.String),
new IgnoreIfDefaultConvention(false),
new ImmutablePocoConvention()
}, _ => true);
}
public IMongoCollection<T> GetCollection<T>(string? name = null)
{
return Database.GetCollection<T>(name ?? typeof(T).Name.ToLower(CultureInfo.CurrentCulture));
}
public void Dispose()
{
while (Session is { IsInTransaction: true })
Thread.Sleep(TimeSpan.FromMilliseconds(100));
GC.SuppressFinalize(this);
}
public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
var result = _commands.Count;
using (Session = await MongoClient.StartSessionAsync(cancellationToken: cancellationToken))
{
Session.StartTransaction();
try
{
var commandTasks = _commands.Select(c => c());
await Task.WhenAll(commandTasks);
await Session.CommitTransactionAsync(cancellationToken);
}
catch (System.Exception ex)
{
await Session.AbortTransactionAsync(cancellationToken);
_commands.Clear();
throw;
}
}
_commands.Clear();
return result;
}
public async Task BeginTransactionAsync(CancellationToken cancellationToken = default)
{
Session = await MongoClient.StartSessionAsync(cancellationToken: cancellationToken);
Session.StartTransaction();
}
public async Task CommitTransactionAsync(CancellationToken cancellationToken = default)
{
if (Session is { IsInTransaction: true })
await Session.CommitTransactionAsync(cancellationToken);
Session?.Dispose();
}
public async Task RollbackTransaction(CancellationToken cancellationToken = default)
{
await Session?.AbortTransactionAsync(cancellationToken)!;
}
public void AddCommand(Func<Task> func)
{
_commands.Add(func);
}
public async Task ExecuteTransactionalAsync(Func<Task> action, CancellationToken cancellationToken = default)
{
await BeginTransactionAsync(cancellationToken);
try
{
await action();
await CommitTransactionAsync(cancellationToken);
}
catch
{
await RollbackTransaction(cancellationToken);
throw;
}
}
public async Task<T> ExecuteTransactionalAsync<T>(
Func<Task<T>> action,
CancellationToken cancellationToken = default)
{
await BeginTransactionAsync(cancellationToken);
try
{
var result = await action();
await CommitTransactionAsync(cancellationToken);
return result;
}
catch
{
await RollbackTransaction(cancellationToken);
throw;
}
}
}