From b3edd3398d8d381744f3bfeedbd8293b8f32db85 Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Sun, 25 Aug 2024 04:05:09 +0200 Subject: [PATCH] (feature): init --- .gitattributes | 63 +++++ .gitignore | 266 ++++++++++++++++++ .../BaseCollectionDataProviderBase.cs | 201 +++++++++++++ .../CollectionDataProviderBase.cs | 122 ++++++++ .../Abstractions/DataProviderBase.cs | 22 ++ .../Abstractions/Dto/DtoDocumentBase.cs | 11 + .../Abstractions/Dto/DtoObjectBase.cs | 9 + .../MaksIT.MongoDB.Linq.csproj | 17 ++ src/MaksIT.MongoDB.Linq/MongoSession.cs | 21 ++ .../MongoSessionManager.cs | 56 ++++ 10 files changed, 788 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 src/MaksIT.MongoDB.Linq/Abstractions/BaseCollectionDataProviderBase.cs create mode 100644 src/MaksIT.MongoDB.Linq/Abstractions/CollectionDataProviderBase.cs create mode 100644 src/MaksIT.MongoDB.Linq/Abstractions/DataProviderBase.cs create mode 100644 src/MaksIT.MongoDB.Linq/Abstractions/Dto/DtoDocumentBase.cs create mode 100644 src/MaksIT.MongoDB.Linq/Abstractions/Dto/DtoObjectBase.cs create mode 100644 src/MaksIT.MongoDB.Linq/MaksIT.MongoDB.Linq.csproj create mode 100644 src/MaksIT.MongoDB.Linq/MongoSession.cs create mode 100644 src/MaksIT.MongoDB.Linq/MongoSessionManager.cs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f09bf8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,266 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs +.directory + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + + +**/*docker-compose/LetsEncryptServer/acme +**/*docker-compose/LetsEncryptServer/cache \ No newline at end of file diff --git a/src/MaksIT.MongoDB.Linq/Abstractions/BaseCollectionDataProviderBase.cs b/src/MaksIT.MongoDB.Linq/Abstractions/BaseCollectionDataProviderBase.cs new file mode 100644 index 0000000..51cfacd --- /dev/null +++ b/src/MaksIT.MongoDB.Linq/Abstractions/BaseCollectionDataProviderBase.cs @@ -0,0 +1,201 @@ +using System.Linq.Expressions; + +using Microsoft.Extensions.Logging; + +using MongoDB.Bson.Serialization; +using MongoDB.Driver; +using MaksIT.MongoDBLinq.Abstractions; +using MaksIT.MongoDBLinq.Abstractions.Domain; + +using MaksIT.Results; + + +namespace MaksIT.Vault.Abstractions { + + public abstract class BaseCollectionDataProviderBase : DataProviderBase where TDomainDocument : DtoDocumentBase { + + protected readonly IIdGenerator IdGenerator; + protected readonly IMongoCollection Collection; + + protected readonly string _errorMessage = "MaksIT.MongoDB.Linq - Data provider error"; + + protected BaseCollectionDataProviderBase( + ILogger logger, + IMongoClient client, + IIdGenerator idGenerator, + string databaseName, + string collectionName + ) : base(logger, client, databaseName) { + IdGenerator = idGenerator; + + if (!Database.ListCollectionNames().ToList().Contains(collectionName)) + Database.CreateCollection(collectionName); + + Collection = Database.GetCollection(collectionName); + } + + #region Insert + protected virtual async Task> InsertAsync(TDomainDocument document, IClientSessionHandle? session) { + try { + if (session != null) + await Collection.InsertOneAsync(session, document); + else + await Collection.InsertOneAsync(document); + + return Result.Ok(document.Id); + } + catch (Exception ex) { + Logger.LogError(ex, _errorMessage); + return Result.InternalServerError(null, _errorMessage); + } + } + #endregion + + #region InsertMany + protected virtual async Task?>> InsertManyAsync(List documents, IClientSessionHandle? session) { + try { + if (session != null) + await Collection.InsertManyAsync(session, documents); + else + await Collection.InsertManyAsync(documents); + + return Result?>.Ok(documents.Select(x => x.Id).ToList()); + } + catch (Exception ex) { + Logger.LogError(ex, _errorMessage); + return Result?>.InternalServerError(null, _errorMessage); + } + } + #endregion + + #region Get + protected virtual IQueryable GetQuery() => Collection.AsQueryable(); + #endregion + + #region Update + protected virtual async Task> UpdateWithPredicateAsync(TDomainDocument document, Expression> predicate, IClientSessionHandle? session) { + try { + if (session != null) + await Collection.ReplaceOneAsync(session, predicate, document); + else + await Collection.ReplaceOneAsync(predicate, document); + + return Result.Ok(document.Id); + } + catch (Exception ex) { + Logger.LogError(ex, _errorMessage); + return Result.InternalServerError(null, _errorMessage); + } + } + #endregion + + #region UdateMany + protected virtual async Task?>> UpdateManyWithPredicateAsync(List documents, Expression> predicate, IClientSessionHandle? session) { + try { + var tasks = new List>(); + + foreach (var document in documents) { + var filter = Builders.Filter.Where(predicate); + var updateOptions = new ReplaceOptions { IsUpsert = false }; + + if (session != null) + tasks.Add(Collection.ReplaceOneAsync(session, filter, document, updateOptions)); + else + tasks.Add(Collection.ReplaceOneAsync(filter, document, updateOptions)); + } + + await Task.WhenAll(tasks); + + var upsertedIds = documents.Select(doc => doc.Id).ToList(); + return Result?>.Ok(upsertedIds); + } + catch (Exception ex) { + Logger.LogError(ex, _errorMessage); + return Result?>.InternalServerError(null, _errorMessage); + } + } + #endregion + + #region Upsert + protected virtual async Task> UpsertWithPredicateAsync(TDomainDocument documents, Expression> predicate, IClientSessionHandle? session) { + try { + var updateOptions = new ReplaceOptions { + IsUpsert = true + }; + + if (session != null) + await Collection.ReplaceOneAsync(session, predicate, documents, updateOptions); + else + await Collection.ReplaceOneAsync(predicate, documents, updateOptions); + + return Result.Ok(documents.Id); + } + catch (Exception ex) { + Logger.LogError(ex, _errorMessage); + return Result.InternalServerError(null, _errorMessage); + } + } + #endregion + + #region UpsertMany + protected virtual async Task?>> UpsertManyWithPredicateAsync(List documents, Expression> predicate, IClientSessionHandle? session) { + try { + var tasks = new List>(); + + foreach (var document in documents) { + var filter = Builders.Filter.Where(predicate); + var updateOptions = new ReplaceOptions { IsUpsert = true }; + + if (session != null) + tasks.Add(Collection.ReplaceOneAsync(session, filter, document, updateOptions)); + else + tasks.Add(Collection.ReplaceOneAsync(filter, document, updateOptions)); + } + + await Task.WhenAll(tasks); + + var upsertedIds = documents.Select(doc => doc.Id).ToList(); + return Result?>.Ok(upsertedIds); + } + catch (Exception ex) { + Logger.LogError(ex, _errorMessage); + return Result?>.InternalServerError(null, _errorMessage); + } + } + #endregion + + #region Delete + protected virtual async Task DeleteWithPredicateAsync(Expression> predicate, IClientSessionHandle? session) { + try { + if (session != null) + await Collection.DeleteOneAsync(session, predicate); + else + await Collection.DeleteOneAsync(predicate); + + return Result.Ok(); + } + catch (Exception ex) { + Logger.LogError(ex, _errorMessage); + return Result.InternalServerError(_errorMessage); + } + } + #endregion + + #region DeleteMany + protected virtual async Task DeleteManyWithPredicateAsync(Expression> predicate, IClientSessionHandle? session) { + try { + if (session != null) + await Collection.DeleteManyAsync(session, predicate); + else + await Collection.DeleteManyAsync(predicate); + + return Result.Ok(); + } + catch (Exception ex) { + Logger.LogError(ex, _errorMessage); + return Result.InternalServerError(_errorMessage); + } + } + #endregion + } +} diff --git a/src/MaksIT.MongoDB.Linq/Abstractions/CollectionDataProviderBase.cs b/src/MaksIT.MongoDB.Linq/Abstractions/CollectionDataProviderBase.cs new file mode 100644 index 0000000..59a52d5 --- /dev/null +++ b/src/MaksIT.MongoDB.Linq/Abstractions/CollectionDataProviderBase.cs @@ -0,0 +1,122 @@ +using System.Linq.Expressions; + +using Microsoft.Extensions.Logging; + +using MongoDB.Driver; +using MongoDB.Bson.Serialization; + +using MaksIT.MongoDBLinq.Abstractions.Domain; + +using MaksIT.Results; + +namespace MaksIT.Vault.Abstractions { + + public abstract class CollectionDataProviderBase : BaseCollectionDataProviderBase where TDomainDocument : DtoDocumentBase { + + protected CollectionDataProviderBase( + ILogger logger, + IMongoClient client, + IIdGenerator idGenerator, + string databaseName, + string collectionName + ) : base(logger, client, idGenerator, databaseName, collectionName) { } + + #region Insert + public Result Insert(TDomainDocument obj, IClientSessionHandle? session) => + InsertAsync(obj, session).Result; + #endregion + + #region InsertMany + public Result?> InsertMany(List objList, IClientSessionHandle? session) => + InsertManyAsync(objList, session).Result; + #endregion + + #region Count + protected Result CountWithPredicate(Expression> predicate) => + CountWithPredicate(new List>> { predicate }); + + private protected Result CountWithPredicate(List>> predicates) { + try { + var query = GetWithPredicate(predicates); + + var result = query.Count(); + + return Result.Ok(result); + } + catch (Exception ex) { + Logger.LogError(ex, _errorMessage); + return Result.InternalServerError(null, _errorMessage); + } + } + #endregion + + #region Get + protected Result?> GetWithPredicate(Expression> predicate, Expression> selector) => + GetWithPredicate(new List>> { predicate }, selector, null, null); + + protected Result?> GetWithPredicate(Expression> predicate, Expression> selector, int? skip, int? limit) => + GetWithPredicate(new List>> { predicate }, selector, skip, limit); + + protected Result?> GetWithPredicate(List>> predicates, Expression> selector, int? skip, int? limit) { + try { + var query = GetWithPredicate(predicates).Select(selector); + + if (skip != null) + query = query.Skip(skip.Value); + + if (limit != null) + query = query.Take(limit.Value); + + var result = query.ToList(); + + return result.Count > 0 + ? Result?>.Ok(result) + : Result?>.NotFound(null); + } + catch (Exception ex) { + Logger.LogError(ex, _errorMessage); + return Result?>.InternalServerError(null, _errorMessage); + } + } + + protected IQueryable GetWithPredicate(List>> predicates) { + var query = GetQuery(); + + foreach (var predicate in predicates) + query = query.Where(predicate); + + return query; + } + #endregion + + #region Update + protected Result UpdateWithPredicate(TDomainDocument obj, Expression> predicate, IClientSessionHandle? session) => + UpdateWithPredicateAsync(obj, predicate, session).Result; + #endregion + + #region UpdateMany + public Result?> UpdateManyWithPredicate(Expression> predicate, List objList, IClientSessionHandle? session) => + UpdateManyWithPredicateAsync(objList, predicate, session).Result; + #endregion + + #region Upsert + protected Result UpsertWithPredicate(TDomainDocument obj, Expression> predicate, IClientSessionHandle? session) => + UpsertWithPredicateAsync(obj, predicate, session).Result; + #endregion + + #region UpsertMany + public Result?> UpsertManyWithPredicate(List objList, Expression> predicate, IClientSessionHandle? session) => + UpsertManyWithPredicateAsync(objList, predicate, session).Result; + #endregion + + #region Delete + protected Result DeleteWithPredicate(Expression> predicate, IClientSessionHandle? session) => + DeleteWithPredicateAsync(predicate, session).Result; + #endregion + + #region DeleteMany + protected Result DeleteManyWithPredicate(Expression> predicate, IClientSessionHandle? session) => + DeleteManyWithPredicateAsync(predicate, session).Result; + #endregion + } +} diff --git a/src/MaksIT.MongoDB.Linq/Abstractions/DataProviderBase.cs b/src/MaksIT.MongoDB.Linq/Abstractions/DataProviderBase.cs new file mode 100644 index 0000000..13d2aed --- /dev/null +++ b/src/MaksIT.MongoDB.Linq/Abstractions/DataProviderBase.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Logging; +using MongoDB.Driver; + +namespace MaksIT.MongoDBLinq.Abstractions { + public abstract class DataProviderBase { + protected readonly ILogger Logger; + protected readonly IMongoDatabase Database; + + private readonly IMongoClient _client; + + protected DataProviderBase( + ILogger logger, + IMongoClient client, + string databaseName) { + Logger = logger; + + _client = client; + Database = _client.GetDatabase(databaseName); + + } + } +} diff --git a/src/MaksIT.MongoDB.Linq/Abstractions/Dto/DtoDocumentBase.cs b/src/MaksIT.MongoDB.Linq/Abstractions/Dto/DtoDocumentBase.cs new file mode 100644 index 0000000..65ed3b5 --- /dev/null +++ b/src/MaksIT.MongoDB.Linq/Abstractions/Dto/DtoDocumentBase.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MaksIT.MongoDBLinq.Abstractions.Domain { + public abstract class DtoDocumentBase : DtoObjectBase { + public Guid Id { get; set; } + } +} diff --git a/src/MaksIT.MongoDB.Linq/Abstractions/Dto/DtoObjectBase.cs b/src/MaksIT.MongoDB.Linq/Abstractions/Dto/DtoObjectBase.cs new file mode 100644 index 0000000..fea3645 --- /dev/null +++ b/src/MaksIT.MongoDB.Linq/Abstractions/Dto/DtoObjectBase.cs @@ -0,0 +1,9 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MaksIT.MongoDBLinq.Abstractions.Domain { + public abstract class DtoObjectBase { } +} diff --git a/src/MaksIT.MongoDB.Linq/MaksIT.MongoDB.Linq.csproj b/src/MaksIT.MongoDB.Linq/MaksIT.MongoDB.Linq.csproj new file mode 100644 index 0000000..2142b76 --- /dev/null +++ b/src/MaksIT.MongoDB.Linq/MaksIT.MongoDB.Linq.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/src/MaksIT.MongoDB.Linq/MongoSession.cs b/src/MaksIT.MongoDB.Linq/MongoSession.cs new file mode 100644 index 0000000..4c19ae2 --- /dev/null +++ b/src/MaksIT.MongoDB.Linq/MongoSession.cs @@ -0,0 +1,21 @@ +using MongoDB.Driver; +using System; + +namespace MaksIT.MongoDB.Linq { + public class DisposableMongoSession : IDisposable { + private readonly IClientSessionHandle _session; + + public DisposableMongoSession(IClientSessionHandle session, string sessionId) { + _session = session; + SessionId = sessionId; + } + + public IClientSessionHandle Session => _session; + + public string SessionId { get; } + + public void Dispose() { + _session?.Dispose(); + } + } +} diff --git a/src/MaksIT.MongoDB.Linq/MongoSessionManager.cs b/src/MaksIT.MongoDB.Linq/MongoSessionManager.cs new file mode 100644 index 0000000..ac1fc42 --- /dev/null +++ b/src/MaksIT.MongoDB.Linq/MongoSessionManager.cs @@ -0,0 +1,56 @@ +using MongoDB.Driver; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; + +namespace MaksIT.MongoDB.Linq { + public interface IMongoSessionManager { + Task GetOrCreateSessionAsync(); + void ReleaseSession(DisposableMongoSession session); + } + + public class MongoSessionManager : IMongoSessionManager { + private readonly IMongoClient _client; + private readonly ILogger _logger; + private readonly ConcurrentDictionary _sessions; + + public MongoSessionManager(ILogger logger, IMongoClient client) { + _logger = logger; + _client = client; + _sessions = new ConcurrentDictionary(); + } + + public async Task GetOrCreateSessionAsync() { + // Generate a unique session ID + var sessionId = Guid.NewGuid().ToString(); + + if (_sessions.TryGetValue(sessionId, out var existingSession)) { + _logger.LogInformation("Reusing existing session with ID: {SessionId}", sessionId); + return existingSession; + } + + _logger.LogInformation("Creating a new session with ID: {SessionId}", sessionId); + var sessionHandle = await _client.StartSessionAsync(); + var newSession = new DisposableMongoSession(sessionHandle, sessionId); + + if (_sessions.TryAdd(sessionId, newSession)) { + return newSession; + } + + _logger.LogError("Failed to add session with ID: {SessionId}", sessionId); + newSession.Dispose(); + throw new InvalidOperationException("Failed to create or retrieve session."); + } + + public void ReleaseSession(DisposableMongoSession session) { + if (_sessions.TryRemove(session.SessionId, out var _)) { + _logger.LogInformation("Releasing and disposing session with ID: {SessionId}", session.SessionId); + session.Dispose(); + } + else { + _logger.LogWarning("Failed to find session with ID: {SessionId} for release.", session.SessionId); + } + } + } +}