Dot.Conductor 1.2.15

There is a newer version of this package available.
See the version list below for details.
dotnet add package Dot.Conductor --version 1.2.15                
NuGet\Install-Package Dot.Conductor -Version 1.2.15                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Dot.Conductor" Version="1.2.15" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Dot.Conductor --version 1.2.15                
#r "nuget: Dot.Conductor, 1.2.15"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Dot.Conductor as a Cake Addin
#addin nuget:?package=Dot.Conductor&version=1.2.15

// Install Dot.Conductor as a Cake Tool
#tool nuget:?package=Dot.Conductor&version=1.2.15                

Unit of Work and Repository Pattern with Entity Framework Core

This project provides an implementation of the Unit of Work (UoW) and the Repository pattern using Entity Framework (EF) Core.

Setting Up

In your Startup.cs - ConfigureServices method, register the UnitOfWork and repositories using provided extension method:

public void ConfigureServices(IServiceCollection services)
{
// ...

    services.AddUnitOfWorkAndRepositories<MyDbContext>(
        Configuration.GetConnectionString("DefaultConnection")
    );

    // ...
}

Using Unit of Work

You can use it in your Controller by dependency injection in the constructor.

public class UserController : ControllerBase
{
    private readonly IUnitOfWork _unitOfWork;

    public UserController(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }
}

Use it in your methods for querying data or performing operations:

public async Task<IActionResult> GetUser(int id)
{
    var userRepository = _unitOfWork.GetRepository<User>();
    var user = await userRepository.GetByIdAsync(id);

    if (user == null)
    {
        return NotFound();
    }

    return Ok(user);
}

Perform multiple operations in a single transaction:

public async Task<IActionResult> UpdateUser(User user)
{
    var userRepository = _unitOfWork.GetRepository<User>();

    userRepository.UpdateAsync(user);
    
    var changesCount = await _unitOfWork.CommitAsync();

    return Ok(changesCount); 
}

If you want to catch exceptions:

public async Task<IActionResult> UpdateUser(User user)
{
    try
    {
        var userRepository = _unitOfWork.GetRepository<User>();

        userRepository.UpdateAsync(user);

        var changesCount = await _unitOfWork.CommitAsync();

        return Ok(changesCount);
    }
    catch (Exception e)
    {
        return BadRequest($"An error occurred when updating the data: {e.Message}");
    }
}

Using Unit of Work outside of scope

To use UnitOfWork outside of Startup file, you can use the extension method provided for IServiceScopeFactory:

public async Task<bool> PerformUnitOfWorkAsync(IServiceScopeFactory scopeFactory)
{
    var result = await scopeFactory.UseUnitOfWork<MyDbContext, bool>(connectionString, async uow =>
    {
      var userRepository = uow.GetRepository<User>();
      var user = new User { Name = "John Doe", Email = "john@example.com" };
      await userRepository.AddAsync(user);
      await uow.CommitAsync();
      return true;
    });

    return result;
}

This code will execute actions related to User repository inside a separate UnitOfWork scope.

Unit of Work with Custom Connection String On-The-Fly

First, inject UnitOfWorkFactory into the class where you want to use a UnitOfWork with a custom connection string:

public class UserController : ControllerBase
{
    private readonly UnitOfWorkFactory<MyDbContext> _unitOfWorkFactory;

    public UserController(UnitOfWorkFactory<MyDbContext> unitOfWorkFactory)
    {
      _unitOfWorkFactory = unitOfWorkFactory;
    }
}

Then, you can create a UnitOfWork with your custom connection string:

public async Task<ActionResult> Create(User user)
{
    // Use a custom connection string
    string connectionString = "YourConnectionStringHere";

    using(var unitOfWork = _unitOfWorkFactory.CreateUnitOfWork(connectionString)) 
    {
        var userRepository = unitOfWork.GetRepository<User>();
        await userRepository.AddAsync(user);
        await unitOfWork.CommitAsync();
    }

    return CreatedAtAction("GetUser", new { id = user.UserId }, user);
}

This will create a new UnitOfWork that is connected to the database specified in YourConnectionStringHere, independent of other UnitOfWork instances. Please remember to dispose of the UnitOfWork when you've finished using it.

Using Transactions with Unit of Work

If you need to set a specific isolation level for a transaction:

public async Task<IActionResult> UpdateUser(User user)
{
    try
    {
        await _unitOfWork.BeginTransactionAsync(System.Data.IsolationLevel.Serializable);

        var userRepository = _unitOfWork.GetRepository<User>();
        await userRepository.UpdateAsync(user);
        
        var changesCount = await _unitOfWork.CommitAsync();

        return Ok(changesCount);
    }
    catch (Exception e)
    {
        await _unitOfWork.RollbackAsync();
        return BadRequest($"An error occurred when updating the data: {e.Message}");
    }
}

In this example, a transaction is started with Serializable isolation level. This isolation level offers high data consistency by preventing other users from updating or inserting rows into the dataset until the transaction is finished.

TransactionService

The TransactionService class provides a method named ExecuteInTransactionAsync. This method serves as a wrapper for executing a Func<Task<TResult>> within a database transaction.

The transaction begins before executing the provided operation and is committed immediately after its successful execution. However, if an exception occurs during the operation, the transaction is rolled back.

You can then use TransactionService within a controller or service by injecting it in your constructor:

public class SomeController : ControllerBase
{
  private readonly TransactionService _transactionService;

    public SomeController(TransactionService transactionService)
    {
        _transactionService = transactionService;
    }

    public async Task<IActionResult> SomeAction()
    {
        var result = await _transactionService.ExecuteInTransactionAsync(async () =>
        {
            var repository1 = _uow.GetRepository<EntityOne>();
            var entity1 = await repository1.GetQueryable().FirstOrDefaultAsync(); // retrieving data
            entity1.SomeProperty = "newValue";
            
            var repository2 = _uow.GetRepository<EntityTwo>();
            var entity2 = new EntityTwo { SomeProperty = "example" }; // creating new entity
            repository2.Add(entity2);
        
            await _uow.CommitAsync(); // saving changes
        
            return entity2.EntityId; // return some result
        });

        return Ok(result);
    }
}

Notes

  • Exceptions are handled by re-throwing them after a rollback. Consider wrapping them in a custom exception class to add context and improve handling downstream.
  • The implementation of UoW here is tightly coupled with Entity Framework Core. Consider abstracting DbContext away for a more flexible design.
  • Multiple threads may affect UnitOfWork. Consider synchronization for multithreaded operations.
  • The CommitAsync() function needs proper exception handling to avoid memory leaks.

Use this pattern to make your code cleaner, more organized, and easier to maintain. Happy coding!

Product Compatible and additional computed target framework versions.
.NET net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.4.0 164 10/17/2024
1.3.0 538 6/23/2024
1.2.23 351 3/11/2024
1.2.22 365 12/29/2023
1.2.21 132 12/29/2023
1.2.20 139 12/28/2023
1.2.19 141 12/19/2023
1.2.18 111 12/19/2023
1.2.17 107 12/19/2023
1.2.16 257 11/15/2023
1.2.15 157 11/5/2023
1.2.14 137 11/4/2023
1.2.13 116 11/4/2023
1.2.12 107 11/4/2023
1.2.11 116 11/4/2023
1.2.10 115 11/4/2023
1.2.9 120 11/4/2023
1.2.8 127 11/4/2023
1.2.7 122 11/2/2023
1.2.6 120 11/2/2023
1.2.5 143 11/2/2023
1.2.4 133 11/2/2023
1.2.3 130 11/1/2023
1.2.2 119 11/1/2023
1.2.1 133 10/27/2023
1.2.0 132 10/27/2023
1.1.1 121 10/25/2023
1.1.0 129 10/19/2023