Skip to content

Conversation

@yilmaztayfun
Copy link
Contributor

@yilmaztayfun yilmaztayfun commented Jan 9, 2026

Summary by Sourcery

Add lazy-evaluated error support to Result Ensure helpers and introduce DI scope utilities for running work in isolated scopes and units of work.

New Features:

  • Provide additional Ensure and EnsureAsync overloads on Result and Task<Result> that accept error factories for lazy error creation, with optional access to the result value.
  • Add IServiceScopeFactory extension methods to execute actions and functions in new dependency injection scopes, with optional unit of work handling and ambient service provider propagation.

Summary by CodeRabbit

  • New Features
    • Added async scope execution helpers for managing dependency injection contexts with automatic resource cleanup and Unit of Work support.
    • Added new validation methods for result objects with flexible error handling and async support.

✏️ Tip: You can customize this high-level summary in your review settings.

@yilmaztayfun yilmaztayfun requested review from a team as code owners January 9, 2026 09:19
@yilmaztayfun yilmaztayfun requested a review from middt January 9, 2026 09:19
@sourcery-ai
Copy link

sourcery-ai bot commented Jan 9, 2026

Reviewer's Guide

Extends the Result extensions with lazily-evaluated Ensure/EnsureAsync overloads and introduces IServiceScopeFactory helpers to execute work in new DI scopes with optional Unit of Work handling and ambient service provider propagation.

Sequence diagram for ExecuteInNewUnitOfWorkScopeAsync extension

sequenceDiagram
    actor Caller
    participant ScopeFactory as IServiceScopeFactory
    participant Scope as IAsyncServiceScope
    participant Ambient as AmbientServiceProvider
    participant SP as IServiceProvider
    participant UowManager as IUnitOfWorkManager
    participant Uow as IUnitOfWork

    Caller->>ScopeFactory: ExecuteInNewUnitOfWorkScopeAsync(action, options, cancellationToken)
    ScopeFactory->>ScopeFactory: CreateAsyncScope()
    ScopeFactory-->>Scope: IAsyncServiceScope
    Scope->>SP: get ServiceProvider
    SP-->>Caller: IServiceProvider (sp)

    Caller->>Ambient: set Current = sp

    SP->>UowManager: GetRequiredService<IUnitOfWorkManager>()
    UowManager-->>Caller: IUnitOfWorkManager

    Caller->>UowManager: BeginAsync(options, cancellationToken)
    UowManager-->>Uow: IUnitOfWork

    Caller->>Caller: await action(sp)

    Caller->>Uow: CommitAsync(cancellationToken)

    Caller->>Scope: DisposeAsync()
    Caller->>Uow: DisposeAsync()

    Caller->>Ambient: restore previous Current

    Caller-->>Caller: task completes
Loading

Class diagram for new lazy Ensure overloads in ResultExtensions

classDiagram
    class Result~T~ {
        bool IsSuccess
        T Value
        static Result~T~ Fail(Error error)
    }

    class Error {
    }

    class ResultExtensions {
        <<static>>
        +Result~T~ Ensure~T~(Result~T~ result, Func~T,bool~ predicate, Error error)
        +Result~T~ Ensure~T~(Result~T~ result, Func~T,bool~ predicate, Func Error errorFactory)
        +Result~T~ Ensure~T~(Result~T~ result, Func~T,bool~ predicate, Func~T,Error~ errorFactory)
        +Task~Result~T~~ EnsureAsync~T~(Result~T~ result, Func~T,Task~bool~~ predicate, Error error)
        +Task~Result~T~~ EnsureAsync~T~(Result~T~ result, Func~T,Task~bool~~ predicate, Func Error errorFactory)
        +Task~Result~T~~ EnsureAsync~T~(Result~T~ result, Func~T,Task~bool~~ predicate, Func~T,Error~ errorFactory)
        +Task~Result~T~~ EnsureAsync~T~(Task~Result~T~~ task, Func~T,bool~ predicate, Error error)
        +Task~Result~T~~ EnsureAsync~T~(Task~Result~T~~ task, Func~T,bool~ predicate, Func Error errorFactory)
        +Task~Result~T~~ EnsureAsync~T~(Task~Result~T~~ task, Func~T,bool~ predicate, Func~T,Error~ errorFactory)
    }

    ResultExtensions ..> Result : uses
    ResultExtensions ..> Error : creates
Loading

Class diagram for new ServiceScopeFactoryExtensions and related types

classDiagram
    class IServiceScopeFactory {
        +IAsyncServiceScope CreateAsyncScope()
    }

    class IAsyncServiceScope {
        +IServiceProvider ServiceProvider
        +ValueTask DisposeAsync()
    }

    class IServiceProvider {
        +T GetService~T~()
    }

    class IUnitOfWorkManager {
        +Task~IUnitOfWork~ BeginAsync(UnitOfWorkOptions options, CancellationToken cancellationToken)
    }

    class IUnitOfWork {
        +Task CommitAsync(CancellationToken cancellationToken)
        +ValueTask DisposeAsync()
    }

    class UnitOfWorkOptions {
    }

    class AmbientServiceProvider {
        <<static>>
        +IServiceProvider Current
    }

    class ServiceScopeFactoryExtensions {
        <<static>>
        +Task ExecuteInNewUnitOfWorkScopeAsync(IServiceScopeFactory scopeFactory, Func~IServiceProvider,Task~ action, UnitOfWorkOptions options, CancellationToken cancellationToken)
        +Task~T~ ExecuteInNewUnitOfWorkScopeAsync~T~(IServiceScopeFactory scopeFactory, Func~IServiceProvider,Task~T~~ func, UnitOfWorkOptions options, CancellationToken cancellationToken)
        +Task ExecuteInNewScopeAsync(IServiceScopeFactory scopeFactory, Func~IServiceProvider,Task~ action)
        +Task~T~ ExecuteInNewScopeAsync~T~(IServiceScopeFactory scopeFactory, Func~IServiceProvider,Task~T~~ func)
    }

    ServiceScopeFactoryExtensions ..> IServiceScopeFactory : extends
    ServiceScopeFactoryExtensions ..> IAsyncServiceScope : creates
    ServiceScopeFactoryExtensions ..> IServiceProvider : uses
    ServiceScopeFactoryExtensions ..> IUnitOfWorkManager : resolves
    ServiceScopeFactoryExtensions ..> IUnitOfWork : manages
    ServiceScopeFactoryExtensions ..> UnitOfWorkOptions : configures
    ServiceScopeFactoryExtensions ..> AmbientServiceProvider : sets Current
Loading

File-Level Changes

Change Details Files
Add lazy error factory overloads to Ensure/EnsureAsync for Result and Task.
  • Introduce Ensure overload that accepts a Func to defer error creation until predicate fails.
  • Introduce Ensure overload that accepts a Func<T, Error> to build an error from the value when the predicate fails.
  • Add EnsureAsync overloads on Result with async predicates and lazy error factories (both Func and Func<T, Error>).
  • Add EnsureAsync overloads on Task<Result> with sync predicates and lazy error factories (both Func and Func<T, Error>).
framework/src/BBT.Aether.Core/BBT/Aether/Results/ResultExtensions.cs
Add IServiceScopeFactory extensions to execute logic in a new DI scope with optional Unit of Work and ambient service provider management.
  • Implement ExecuteInNewUnitOfWorkScopeAsync overloads that create an async scope, set AmbientServiceProvider.Current, begin a UnitOfWork, execute the delegate, commit, and restore the previous ambient provider.
  • Implement ExecuteInNewScopeAsync overloads that create an async scope, set AmbientServiceProvider.Current, execute the delegate without UnitOfWork, and restore the previous ambient provider.
  • Use IServiceScopeFactory.CreateAsyncScope and resolve IUnitOfWorkManager via GetRequiredService within the new scope.
framework/src/BBT.Aether.Core/BBT/Aether/DependencyInjection/ServiceScopeFactoryExtensions.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@yilmaztayfun yilmaztayfun merged commit 9c8220a into release-v1.0 Jan 9, 2026
2 of 6 checks passed
@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

Caution

Review failed

The pull request is closed.

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'review'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

Introduces two new extension utility classes: ServiceScopeFactoryExtensions provides async helpers for executing code within scoped DI containers with optional Unit of Work management and ambient context propagation; ResultExtensions adds sync and async variants of Ensure methods with lazy error factories supporting both factory function and value-dependent error creation patterns.

Changes

Cohort / File(s) Summary
Dependency Injection Scope Execution
framework/src/BBT.Aether.Core/BBT/Aether/DependencyInjection/ServiceScopeFactoryExtensions.cs
New extension class with 4 methods: ExecuteInNewUnitOfWorkScopeAsync (with/without generic return) manages scope creation, ambient provider propagation, Unit of Work lifecycle (begin/commit), and context restoration; ExecuteInNewScopeAsync (with/without generic return) provides same pattern without UoW management. All methods handle cancellation and ensure proper disposal.
Result Validation Enhancement
framework/src/BBT.Aether.Core/BBT/Aether/Results/ResultExtensions.cs
Adds 6 new Ensure overloads: 2 sync variants on Result<T> with lazy error factories (accepting either no arguments or the value); 4 async variants on Result<T> and Task<Result<T>> with same error factory patterns. All implement predicate-based validation with lazy error creation, preserving successful results and producing failed states on predicate failure.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • ukaratas

Poem

🐰 Hops of joy through scopes so new,
With UnitOfWork and results true,
Ambient whispers flow from place to place,
Error factories craft responses with grace,
Extensions blooming—let code run free!

✨ 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 master

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 504173e and 8a9b0cd.

📒 Files selected for processing (2)
  • framework/src/BBT.Aether.Core/BBT/Aether/DependencyInjection/ServiceScopeFactoryExtensions.cs
  • framework/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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @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 providing robust mechanisms for managing dependency injection scopes and Unit of Work patterns through new IServiceScopeFactory extension methods. Additionally, it improves the Result pattern's error handling capabilities by introducing lazy error evaluation in Ensure and EnsureAsync methods, allowing for more efficient and context-aware error generation.

Highlights

  • Dependency Injection Scope Management: Added new extension methods to IServiceScopeFactory for executing code within new dependency injection scopes, with or without a Unit of Work, and handling ambient service provider propagation.
  • Enhanced Result Pattern Error Handling: Introduced lazy error evaluation to Result<T>.Ensure methods, allowing error creation functions (Func<Error> or Func<T, Error>) to be passed instead of pre-instantiated errors, improving efficiency and context-awareness.
  • Asynchronous Lazy Error Evaluation: Extended Result<T>.EnsureAsync methods to support lazy error evaluation for both synchronous Result<T> with asynchronous predicates and asynchronous Task<Result<T>> with synchronous predicates.

🧠 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 Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@sourcery-ai sourcery-ai bot left a 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 ServiceScopeFactoryExtensions, there is an extra closing brace at the end of the file, which results in three closing braces instead of the expected two (class and namespace); this will prevent the file from compiling and should be removed.
  • The four ExecuteIn*ScopeAsync methods duplicate the same ambient service provider scoping pattern; consider extracting the common ambient context handling into a shared helper to reduce duplication and the risk of inconsistencies on future changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In ServiceScopeFactoryExtensions, there is an extra closing brace at the end of the file, which results in three closing braces instead of the expected two (class and namespace); this will prevent the file from compiling and should be removed.
- The four ExecuteIn*ScopeAsync methods duplicate the same ambient service provider scoping pattern; consider extracting the common ambient context handling into a shared helper to reduce duplication and the risk of inconsistencies on future changes.

## 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 compile error.

The class and namespace are already closed, so the final `}` leaves the file with unbalanced braces. Remove that last brace at the end of the file.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
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);
}
Copy link

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 compile error.

The class and namespace are already closed, so the final } leaves the file with unbalanced braces. Remove that last brace at the end of the file.

Copy link

@gemini-code-assist gemini-code-assist bot left a 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 valuable additions to the framework. The new Ensure and EnsureAsync overloads with lazy error evaluation for the Result type are a great performance enhancement. The ServiceScopeFactoryExtensions also provide useful helpers for managing dependency injection scopes and units of work. My review includes a couple of suggestions to refactor the new service scope extensions to reduce code duplication, which will improve long-term maintainability. Overall, the changes are well-implemented and beneficial.

Comment on lines +23 to +54
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;
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This method duplicates the logic for creating a scope and managing the AmbientServiceProvider, which is already handled by ExecuteInNewScopeAsync. To improve maintainability and reduce code duplication, you can refactor this method to delegate the scope management to ExecuteInNewScopeAsync and wrap the unit of work logic within it.

    public static Task ExecuteInNewUnitOfWorkScopeAsync(
        this IServiceScopeFactory scopeFactory,
        Func<IServiceProvider, Task> action,
        UnitOfWorkOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        return scopeFactory.ExecuteInNewScopeAsync(async sp =>
        {
            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);
        });
    }

Comment on lines +66 to +99
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;
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the non-generic version, this method duplicates the scope creation and AmbientServiceProvider management logic from ExecuteInNewScopeAsync<T>. Refactoring to use ExecuteInNewScopeAsync<T> will make the code more concise and easier to maintain by avoiding redundant code.

    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>();

            // 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;
        });
    }

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 9, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants