-
Notifications
You must be signed in to change notification settings - Fork 0
32 add lazy error evaluation overloads for ensure and ensureasync methods #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
32 add lazy error evaluation overloads for ensure and ensureasync methods #33
Conversation
Reviewer's GuideAdds lazily evaluated error factory overloads for Ensure/EnsureAsync result extension methods and introduces IServiceScopeFactory extensions to run operations in new DI scopes with optional Unit of Work management and ambient service provider propagation. Sequence diagram for ExecuteInNewUnitOfWorkScopeAsync_with_UnitOfWorksequenceDiagram
actor client
participant scopeFactory as scopeFactory
participant scope as scope
participant serviceProvider as serviceProvider
participant AmbientServiceProvider as AmbientServiceProvider
participant uowManager as uowManager
participant uow as uow
client->>scopeFactory: ExecuteInNewUnitOfWorkScopeAsync(action, options, cancellationToken)
activate scopeFactory
scopeFactory->>scopeFactory: CreateAsyncScope()
scopeFactory-->>scope: scope
scopeFactory->>scope: ServiceProvider
scope-->>serviceProvider: serviceProvider
scopeFactory->>AmbientServiceProvider: set Current = serviceProvider
AmbientServiceProvider-->>scopeFactory: previousAmbient
scopeFactory->>serviceProvider: GetRequiredService(IUnitOfWorkManager)
serviceProvider-->>uowManager: uowManager
scopeFactory->>uowManager: BeginAsync(options, cancellationToken)
activate uowManager
uowManager-->>uow: uow
deactivate uowManager
scopeFactory->>client: invoke action(serviceProvider)
client-->>scopeFactory: Task completed
scopeFactory->>uow: CommitAsync(cancellationToken)
uow-->>scopeFactory: commit completed
scopeFactory->>AmbientServiceProvider: restore Current = previousAmbient
scopeFactory-->>client: Task completed
deactivate scopeFactory
scopeFactory->>scope: dispose async
scope-->>scopeFactory: disposed
scopeFactory->>uow: dispose async
uow-->>scopeFactory: disposed
Class diagram for ResultExtensions_and_ServiceScopeFactoryExtensions_changesclassDiagram
class ResultT {
bool IsSuccess
T Value
+static ResultT Fail(Error error)
}
class Error
class ResultExtensions {
+static ResultT EnsureT(ResultT result, FuncTBool predicate, Error error)
+static ResultT EnsureT(ResultT result, FuncTBool predicate, FuncError errorFactory)
+static ResultT EnsureT(ResultT result, FuncTBool predicate, FuncTError errorFactory)
+static TaskResultT EnsureAsyncT(ResultT result, FuncTTaskBool predicate, Error error)
+static TaskResultT EnsureAsyncT(ResultT result, FuncTTaskBool predicate, FuncError errorFactory)
+static TaskResultT EnsureAsyncT(ResultT result, FuncTTaskBool predicate, FuncTError errorFactory)
+static TaskResultT EnsureAsyncT(TaskResultT task, FuncTBool predicate, Error error)
+static TaskResultT EnsureAsyncT(TaskResultT task, FuncTBool predicate, FuncError errorFactory)
+static TaskResultT EnsureAsyncT(TaskResultT task, FuncTBool predicate, FuncTError errorFactory)
}
class ServiceScopeFactoryExtensions {
+static Task ExecuteInNewUnitOfWorkScopeAsync(IServiceScopeFactory scopeFactory, FuncIServiceProviderTask action, UnitOfWorkOptions options, CancellationToken cancellationToken)
+static TaskT ExecuteInNewUnitOfWorkScopeAsyncT(IServiceScopeFactory scopeFactory, FuncIServiceProviderTaskT func, UnitOfWorkOptions options, CancellationToken cancellationToken)
+static Task ExecuteInNewScopeAsync(IServiceScopeFactory scopeFactory, FuncIServiceProviderTask action)
+static TaskT ExecuteInNewScopeAsyncT(IServiceScopeFactory scopeFactory, FuncIServiceProviderTaskT func)
}
class IServiceScopeFactory {
+AsyncServiceScope CreateAsyncScope()
}
class AsyncServiceScope {
IServiceProvider ServiceProvider
+DisposeAsync()
}
class IServiceProvider {
+T GetRequiredServiceT()
}
class AmbientServiceProvider {
static IServiceProvider Current
}
class IUnitOfWorkManager {
+TaskIUnitOfWork BeginAsync(UnitOfWorkOptions options, CancellationToken cancellationToken)
}
class IUnitOfWork {
+Task CommitAsync(CancellationToken cancellationToken)
+DisposeAsync()
}
class UnitOfWorkOptions
class Task
class TaskT
class TaskResultT
class TaskIUnitOfWork
class FuncTBool
class FuncTTaskBool
class FuncError
class FuncTError
class FuncIServiceProviderTask
class FuncIServiceProviderTaskT
ResultExtensions ..> ResultT
ResultExtensions ..> Error
ResultExtensions ..> TaskResultT
ResultExtensions ..> FuncTBool
ResultExtensions ..> FuncTTaskBool
ResultExtensions ..> FuncError
ResultExtensions ..> FuncTError
ServiceScopeFactoryExtensions ..> IServiceScopeFactory
ServiceScopeFactoryExtensions ..> IServiceProvider
ServiceScopeFactoryExtensions ..> AmbientServiceProvider
ServiceScopeFactoryExtensions ..> IUnitOfWorkManager
ServiceScopeFactoryExtensions ..> IUnitOfWork
ServiceScopeFactoryExtensions ..> UnitOfWorkOptions
ServiceScopeFactoryExtensions ..> Task
ServiceScopeFactoryExtensions ..> TaskT
IServiceScopeFactory ..> AsyncServiceScope
AsyncServiceScope ..> IServiceProvider
IServiceProvider ..> IUnitOfWorkManager
IUnitOfWorkManager ..> TaskIUnitOfWork
TaskIUnitOfWork ..> IUnitOfWork
AmbientServiceProvider ..> IServiceProvider
TaskResultT ..> ResultT
TaskT ..> Task
TaskIUnitOfWork ..> Task
FuncTBool ..> ResultT
FuncTTaskBool ..> ResultT
FuncTError ..> ResultT
FuncIServiceProviderTask ..> IServiceProvider
FuncIServiceProviderTaskT ..> IServiceProvider
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Caution Review failedThe pull request is closed. Note
|
| Cohort / File(s) | Summary |
|---|---|
Dependency Injection Scope Management framework/src/BBT.Aether.Core/BBT/Aether/DependencyInjection/ServiceScopeFactoryExtensions.cs |
New extension class (160 lines) with four async methods: ExecuteInNewUnitOfWorkScopeAsync (with/without return value) and ExecuteInNewScopeAsync (with/without return value). Methods handle scope creation, ambient provider propagation, UnitOfWork lifecycle management, and context restoration. |
Result Validation Enhancements framework/src/BBT.Aether.Core/BBT/Aether/Results/ResultExtensions.cs |
Six new method overloads (78 lines) extending Ensure functionality: two sync variants with lazy Func and value-based Func<T, Error> factories; four async variants for Result and Task<Result> with matching factory signatures. |
Sequence Diagram(s)
sequenceDiagram
participant Caller
participant ScopeFactory as IServiceScopeFactory
participant Scope as AsyncServiceScope
participant Provider as IServiceProvider
participant UoW as IUnitOfWork
participant Action as User Action
Caller->>ScopeFactory: ExecuteInNewUnitOfWorkScopeAsync(action, options)
ScopeFactory->>Scope: CreateAsyncScope()
Scope->>Provider: Get IServiceProvider
Caller->>Provider: Store current ambient provider
Caller->>Provider: Set new ambient provider
Caller->>UoW: Resolve from new provider
Caller->>UoW: BeginAsync(options)
Caller->>Action: Execute user delegate
Action-->>Caller: Complete/Return result
Caller->>UoW: CommitAsync()
Caller->>Scope: DisposeAsync()
Caller->>Provider: Restore previous ambient provider
Caller-->>Caller: Return result
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~20 minutes
Poem
🐰 Hop! Hop! New scopes bloom bright,
With UnitOfWork held tight,
Results now validate with lazy grace,
Errors crafted in their place!
Ambient whispers pass and flow,
Dependencies dance—what a show! 🌿
✨ Finishing touches
- 📝 Generate docstrings
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
32-add-lazy-error-evaluation-overloads-for-ensure-and-ensureasync-methods
📜 Recent review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
framework/src/BBT.Aether.Core/BBT/Aether/DependencyInjection/ServiceScopeFactoryExtensions.csframework/src/BBT.Aether.Core/BBT/Aether/Results/ResultExtensions.cs
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
Summary of ChangesHello @yilmaztayfun, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enhances the framework by introducing new extension methods for Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey - I've found 1 issue, and left some high level feedback:
- In the new Ensure/EnsureAsync overloads that take an errorFactory using the result value, you’re calling result.Value! multiple times in the same expression; consider capturing result.Value! in a local variable after the IsSuccess check to avoid repeated access and clarify that the value is only read once.
- ServiceScopeFactoryExtensions.cs ends with an extra closing brace, resulting in three closing braces after the last method; this will cause a compile error and should be reduced to only closing the class and namespace.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In the new Ensure/EnsureAsync overloads that take an errorFactory using the result value, you’re calling result.Value! multiple times in the same expression; consider capturing result.Value! in a local variable after the IsSuccess check to avoid repeated access and clarify that the value is only read once.
- ServiceScopeFactoryExtensions.cs ends with an extra closing brace, resulting in three closing braces after the last method; this will cause a compile error and should be reduced to only closing the class and namespace.
## Individual Comments
### Comment 1
<location> `framework/src/BBT.Aether.Core/BBT/Aether/DependencyInjection/ServiceScopeFactoryExtensions.cs:48` </location>
<code_context>
+
+ // Commit UnitOfWork
+ await uow.CommitAsync(cancellationToken);
+ }
+ finally
+ {
</code_context>
<issue_to_address>
**issue (bug_risk):** There is an extra closing brace at the end of the file, which will cause a compilation error.
After the `ServiceScopeFactoryExtensions` class’s closing brace, there’s an additional `}` that attempts to close the namespace a second time. Remove the final `}` to correct the file structure.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
|
||
| // Commit UnitOfWork | ||
| await uow.CommitAsync(cancellationToken); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): There is an extra closing brace at the end of the file, which will cause a compilation error.
After the ServiceScopeFactoryExtensions class’s closing brace, there’s an additional } that attempts to close the namespace a second time. Remove the final } to correct the file structure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces useful additions. The new lazy evaluation overloads for Ensure and EnsureAsync in ResultExtensions are well-implemented and improve performance by avoiding unnecessary error object creation. The ServiceScopeFactoryExtensions provide convenient helpers for managing scopes and units of work. I've included one suggestion to refactor ServiceScopeFactoryExtensions to reduce code duplication and improve maintainability. Overall, these are solid improvements to the framework.
| /// <summary> | ||
| /// Executes the given action within a new dependency injection scope and a new unit of work. | ||
| /// Manages ambient service provider propagation. | ||
| /// </summary> | ||
| /// <param name="scopeFactory">The service scope factory.</param> | ||
| /// <param name="action">The action to execute, receiving the scoped service provider.</param> | ||
| /// <param name="options">Unit of work options. Defaults to a new UoW with default settings.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns>A task representing the asynchronous operation.</returns> | ||
| public static async Task ExecuteInNewUnitOfWorkScopeAsync( | ||
| this IServiceScopeFactory scopeFactory, | ||
| Func<IServiceProvider, Task> 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<IUnitOfWorkManager>(); | ||
|
|
||
| // 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; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Executes the given function within a new dependency injection scope and a new unit of work, returning a result. | ||
| /// Manages ambient service provider propagation. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of the result.</typeparam> | ||
| /// <param name="scopeFactory">The service scope factory.</param> | ||
| /// <param name="func">The function to execute, receiving the scoped service provider.</param> | ||
| /// <param name="options">Unit of work options. Defaults to a new UoW with default settings.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns>The result of the function execution.</returns> | ||
| public static async Task<T> ExecuteInNewUnitOfWorkScopeAsync<T>( | ||
| this IServiceScopeFactory scopeFactory, | ||
| Func<IServiceProvider, Task<T>> 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<IUnitOfWorkManager>(); | ||
|
|
||
| // 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; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Executes the given action within a new dependency injection scope without starting a unit of work. | ||
| /// Manages ambient service provider propagation. | ||
| /// </summary> | ||
| /// <param name="scopeFactory">The service scope factory.</param> | ||
| /// <param name="action">The action to execute, receiving the scoped service provider.</param> | ||
| /// <returns>A task representing the asynchronous operation.</returns> | ||
| public static async Task ExecuteInNewScopeAsync( | ||
| this IServiceScopeFactory scopeFactory, | ||
| Func<IServiceProvider, Task> 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; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Executes the given function within a new dependency injection scope without starting a unit of work, returning a result. | ||
| /// Manages ambient service provider propagation. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of the result.</typeparam> | ||
| /// <param name="scopeFactory">The service scope factory.</param> | ||
| /// <param name="func">The function to execute, receiving the scoped service provider.</param> | ||
| /// <returns>The result of the function execution.</returns> | ||
| public static async Task<T> ExecuteInNewScopeAsync<T>( | ||
| this IServiceScopeFactory scopeFactory, | ||
| Func<IServiceProvider, Task<T>> 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's significant code duplication across the four extension methods. The logic for creating a scope and managing the AmbientServiceProvider is repeated in all methods. The Unit of Work handling is also duplicated between the two ExecuteInNewUnitOfWorkScopeAsync methods.
Consider refactoring this to centralize the scope creation and AmbientServiceProvider propagation logic. The public methods can then be simplified to call this shared logic, adding the Unit of Work handling where necessary. This will improve maintainability and reduce the chance of introducing bugs in the future.
Here's a suggested refactoring that consolidates the logic and improves readability by ordering methods from most specific to most general:
/// <summary>
/// Executes the given action within a new dependency injection scope and a new unit of work.
/// Manages ambient service provider propagation.
/// </summary>
/// <param name="scopeFactory">The service scope factory.</param>
/// <param name="action">The action to execute, receiving the scoped service provider.</param>
/// <param name="options">Unit of work options. Defaults to a new UoW with default settings.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public static Task ExecuteInNewUnitOfWorkScopeAsync(
this IServiceScopeFactory scopeFactory,
Func<IServiceProvider, Task> action,
UnitOfWorkOptions? options = null,
CancellationToken cancellationToken = default)
{
return scopeFactory.ExecuteInNewUnitOfWorkScopeAsync<object?>(async sp =>
{
await action(sp);
return null;
}, options, cancellationToken);
}
/// <summary>
/// Executes the given function within a new dependency injection scope and a new unit of work, returning a result.
/// Manages ambient service provider propagation.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="scopeFactory">The service scope factory.</param>
/// <param name="func">The function to execute, receiving the scoped service provider.</param>
/// <param name="options">Unit of work options. Defaults to a new UoW with default settings.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The result of the function execution.</returns>
public static Task<T> ExecuteInNewUnitOfWorkScopeAsync<T>(
this IServiceScopeFactory scopeFactory,
Func<IServiceProvider, Task<T>> func,
UnitOfWorkOptions? options = null,
CancellationToken cancellationToken = default)
{
return scopeFactory.ExecuteInNewScopeAsync(async sp =>
{
var uowManager = sp.GetRequiredService<IUnitOfWorkManager>();
await using var uow = await uowManager.BeginAsync(options, cancellationToken);
var result = await func(sp);
await uow.CommitAsync(cancellationToken);
return result;
});
}
/// <summary>
/// Executes the given action within a new dependency injection scope without starting a unit of work.
/// Manages ambient service provider propagation.
/// </summary>
/// <param name="scopeFactory">The service scope factory.</param>
/// <param name="action">The action to execute, receiving the scoped service provider.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public static Task ExecuteInNewScopeAsync(
this IServiceScopeFactory scopeFactory,
Func<IServiceProvider, Task> action)
{
return scopeFactory.ExecuteInNewScopeAsync<object?>(async sp =>
{
await action(sp);
return null;
});
}
/// <summary>
/// Executes the given function within a new dependency injection scope without starting a unit of work, returning a result.
/// Manages ambient service provider propagation.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="scopeFactory">The service scope factory.</param>
/// <param name="func">The function to execute, receiving the scoped service provider.</param>
/// <returns>The result of the function execution.</returns>
public static async Task<T> ExecuteInNewScopeAsync<T>(
this IServiceScopeFactory scopeFactory,
Func<IServiceProvider, Task<T>> func)
{
await using var scope = scopeFactory.CreateAsyncScope();
var sp = scope.ServiceProvider;
var previousAmbient = AmbientServiceProvider.Current;
AmbientServiceProvider.Current = sp;
try
{
return await func(sp);
}
finally
{
AmbientServiceProvider.Current = previousAmbient;
}
}
|



Summary by Sourcery
Add lazy error factory overloads for Result Ensure/EnsureAsync and introduce IServiceScopeFactory extensions for scoped execution with optional unit of work and ambient service provider propagation.
New Features:
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.