From a9506d3877346e8f16e1ed58170d2ec6c64add08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tayfun=20Y=C4=B1lmaz?= Date: Fri, 9 Jan 2026 12:16:32 +0300 Subject: [PATCH 1/2] feat: add lazy error evaluation overloads for Ensure and EnsureAsync --- .../BBT/Aether/Results/ResultExtensions.cs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/framework/src/BBT.Aether.Core/BBT/Aether/Results/ResultExtensions.cs b/framework/src/BBT.Aether.Core/BBT/Aether/Results/ResultExtensions.cs index 7d0ec63..4e7e6f2 100644 --- a/framework/src/BBT.Aether.Core/BBT/Aether/Results/ResultExtensions.cs +++ b/framework/src/BBT.Aether.Core/BBT/Aether/Results/ResultExtensions.cs @@ -231,6 +231,28 @@ public static Result Tap(this Result result, Action sideEffect) public static Result Ensure(this Result result, Func predicate, Error error) => result.IsSuccess && !predicate(result.Value!) ? Result.Fail(error) : result; + /// + /// Ensures a predicate is satisfied, otherwise returns an error (lazy evaluation). + /// + /// Value type + /// The source result + /// Condition that must be true + /// Function to create error if predicate fails + /// The original result if predicate passes, otherwise a failure + public static Result Ensure(this Result result, Func predicate, Func errorFactory) + => result.IsSuccess && !predicate(result.Value!) ? Result.Fail(errorFactory()) : result; + + /// + /// Ensures a predicate is satisfied, otherwise returns an error (lazy evaluation with value access). + /// + /// Value type + /// The source result + /// Condition that must be true + /// Function to create error based on the value if predicate fails + /// The original result if predicate passes, otherwise a failure + public static Result Ensure(this Result result, Func predicate, Func errorFactory) + => result.IsSuccess && !predicate(result.Value!) ? Result.Fail(errorFactory(result.Value!)) : result; + /// /// Executes a side effect on failure without altering the error. /// Useful for logging, alerting, or cleanup actions on failure. @@ -349,6 +371,34 @@ public static async Task> EnsureAsync(this Result result, Func.Fail(error); } + /// + /// Async version of Ensure for sync Result with async predicate (lazy error evaluation). + /// + /// Value type + /// The source result + /// Async predicate that must be true + /// Function to create error if predicate fails + /// The original result if predicate passes, otherwise a failure + public static async Task> EnsureAsync(this Result result, Func> predicate, Func errorFactory) + { + if (!result.IsSuccess) return result; + return await predicate(result.Value!) ? result : Result.Fail(errorFactory()); + } + + /// + /// Async version of Ensure for sync Result with async predicate (lazy error evaluation with value access). + /// + /// Value type + /// The source result + /// Async predicate that must be true + /// Function to create error based on the value if predicate fails + /// The original result if predicate passes, otherwise a failure + public static async Task> EnsureAsync(this Result result, Func> predicate, Func errorFactory) + { + if (!result.IsSuccess) return result; + return await predicate(result.Value!) ? result : Result.Fail(errorFactory(result.Value!)); + } + /// /// Async version of Map for Task-wrapped results. /// @@ -458,6 +508,34 @@ public static async Task> EnsureAsync(this Task> task, Fu return result.Ensure(predicate, error); } + /// + /// Async version of Ensure for Task-wrapped results (lazy error evaluation). + /// + /// Value type + /// The source result task + /// Condition that must be true + /// Function to create error if predicate fails + /// The original result if predicate passes, otherwise a failure + public static async Task> EnsureAsync(this Task> task, Func predicate, Func errorFactory) + { + var result = await task; + return result.Ensure(predicate, errorFactory); + } + + /// + /// Async version of Ensure for Task-wrapped results (lazy error evaluation with value access). + /// + /// Value type + /// The source result task + /// Condition that must be true + /// Function to create error based on the value if predicate fails + /// The original result if predicate passes, otherwise a failure + public static async Task> EnsureAsync(this Task> task, Func predicate, Func errorFactory) + { + var result = await task; + return result.Ensure(predicate, errorFactory); + } + /// /// Async version of Match for Task-wrapped results with sync handlers. /// From d8e5322ae7fc64b3a7d8b8d3918ae40e782c8505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tayfun=20Y=C4=B1lmaz?= Date: Fri, 9 Jan 2026 12:16:33 +0300 Subject: [PATCH 2/2] feat: add ServiceScopeFactory extensions for new scope and unit of work execution --- .../ServiceScopeFactoryExtensions.cs | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 framework/src/BBT.Aether.Core/BBT/Aether/DependencyInjection/ServiceScopeFactoryExtensions.cs diff --git a/framework/src/BBT.Aether.Core/BBT/Aether/DependencyInjection/ServiceScopeFactoryExtensions.cs b/framework/src/BBT.Aether.Core/BBT/Aether/DependencyInjection/ServiceScopeFactoryExtensions.cs new file mode 100644 index 0000000..ed68f98 --- /dev/null +++ b/framework/src/BBT.Aether.Core/BBT/Aether/DependencyInjection/ServiceScopeFactoryExtensions.cs @@ -0,0 +1,160 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using BBT.Aether.Uow; +using Microsoft.Extensions.DependencyInjection; + +namespace BBT.Aether.DependencyInjection; + +/// +/// Extension methods for IServiceScopeFactory. +/// +public static class ServiceScopeFactoryExtensions +{ + /// + /// Executes the given action within a new dependency injection scope and a new unit of work. + /// Manages ambient service provider propagation. + /// + /// The service scope factory. + /// The action to execute, receiving the scoped service provider. + /// Unit of work options. Defaults to a new UoW with default settings. + /// Cancellation token. + /// A task representing the asynchronous operation. + public static async Task ExecuteInNewUnitOfWorkScopeAsync( + this IServiceScopeFactory scopeFactory, + Func action, + UnitOfWorkOptions? options = null, + CancellationToken cancellationToken = default) + { + await using var scope = scopeFactory.CreateAsyncScope(); + var sp = scope.ServiceProvider; + + // Propagate ambient service provider for the new scope + var previousAmbient = AmbientServiceProvider.Current; + AmbientServiceProvider.Current = sp; + + try + { + var uowManager = sp.GetRequiredService(); + + // Begin UnitOfWork + await using var uow = await uowManager.BeginAsync(options, cancellationToken); + + // Execute action + await action(sp); + + // Commit UnitOfWork + await uow.CommitAsync(cancellationToken); + } + finally + { + // Restore previous ambient context + AmbientServiceProvider.Current = previousAmbient; + } + } + + /// + /// Executes the given function within a new dependency injection scope and a new unit of work, returning a result. + /// Manages ambient service provider propagation. + /// + /// The type of the result. + /// The service scope factory. + /// The function to execute, receiving the scoped service provider. + /// Unit of work options. Defaults to a new UoW with default settings. + /// Cancellation token. + /// The result of the function execution. + public static async Task ExecuteInNewUnitOfWorkScopeAsync( + this IServiceScopeFactory scopeFactory, + Func> func, + UnitOfWorkOptions? options = null, + CancellationToken cancellationToken = default) + { + await using var scope = scopeFactory.CreateAsyncScope(); + var sp = scope.ServiceProvider; + + // Propagate ambient service provider for the new scope + var previousAmbient = AmbientServiceProvider.Current; + AmbientServiceProvider.Current = sp; + + try + { + var uowManager = sp.GetRequiredService(); + + // Begin UnitOfWork + await using var uow = await uowManager.BeginAsync(options, cancellationToken); + + // Execute function + var result = await func(sp); + + // Commit UnitOfWork + await uow.CommitAsync(cancellationToken); + + return result; + } + finally + { + // Restore previous ambient context + AmbientServiceProvider.Current = previousAmbient; + } + } + + /// + /// Executes the given action within a new dependency injection scope without starting a unit of work. + /// Manages ambient service provider propagation. + /// + /// The service scope factory. + /// The action to execute, receiving the scoped service provider. + /// A task representing the asynchronous operation. + public static async Task ExecuteInNewScopeAsync( + this IServiceScopeFactory scopeFactory, + Func action) + { + await using var scope = scopeFactory.CreateAsyncScope(); + var sp = scope.ServiceProvider; + + // Propagate ambient service provider for the new scope + var previousAmbient = AmbientServiceProvider.Current; + AmbientServiceProvider.Current = sp; + + try + { + await action(sp); + } + finally + { + // Restore previous ambient context + AmbientServiceProvider.Current = previousAmbient; + } + } + + /// + /// Executes the given function within a new dependency injection scope without starting a unit of work, returning a result. + /// Manages ambient service provider propagation. + /// + /// The type of the result. + /// The service scope factory. + /// The function to execute, receiving the scoped service provider. + /// The result of the function execution. + public static async Task ExecuteInNewScopeAsync( + this IServiceScopeFactory scopeFactory, + Func> func) + { + await using var scope = scopeFactory.CreateAsyncScope(); + var sp = scope.ServiceProvider; + + // Propagate ambient service provider for the new scope + var previousAmbient = AmbientServiceProvider.Current; + AmbientServiceProvider.Current = sp; + + try + { + return await func(sp); + } + finally + { + // Restore previous ambient context + AmbientServiceProvider.Current = previousAmbient; + } + } +} +}