Flowsy.Db.Repository.Sql 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Flowsy.Db.Repository.Sql --version 1.0.0                
NuGet\Install-Package Flowsy.Db.Repository.Sql -Version 1.0.0                
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="Flowsy.Db.Repository.Sql" Version="1.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Flowsy.Db.Repository.Sql --version 1.0.0                
#r "nuget: Flowsy.Db.Repository.Sql, 1.0.0"                
#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 Flowsy.Db.Repository.Sql as a Cake Addin
#addin nuget:?package=Flowsy.Db.Repository.Sql&version=1.0.0

// Install Flowsy.Db.Repository.Sql as a Cake Tool
#tool nuget:?package=Flowsy.Db.Repository.Sql&version=1.0.0                

Flowsy Db Repository Sql

This package is a wrapper for Flowsy.Db.Conventions and a flexible and easy to use implementation of Flowsy.Db.Repository.Abstractions.

DbRepository

This class implements the IRepository interface using the conventions provided by the Flowsy.Db.Conventions package.

The DbRepository class is abstract and is meant to be the base class for all repositories in your application. Any derived class must call one of the base constructors provided by the DbRepository class.

For example, you could create the following interface in your domain layer:

using Flowsy.Db.Repository.Abstractions;

namespace Example.Domain;

public interface IStudentRepository : IRepository
{
    // Define methods to interact with Student entities
}

And then create the following class in your persistence layer:

using Example.Domain;
using Flowsy.Db.Repository.Sql;

namespace Example.Persistence;

public sealed class DbStudentRepository : DbRepository, IStudentRepository
{
    public DbStudentRepository(IDbConnectionFactory connectionFactory, ILogger? logger = null) : base(connectionFactory, logger)
    {
    }
  
    public DbStudentRepository(IUnitOfWork unitOfWork) : base(unitOfWork)
    {
    }
  
    // Implement IStudentRepository methods to interact with Student entities.
    // Methods of DbStudentRepository can invoke all methods inherited from DbRepository, which are
    // wrappers for the extension methods on IDbConnection provided by the Flowsy.Db.Conventions package.
}

By using the first constructor, your repository will get connections from a connection factory, which will handle disposal of connections when going out of scope. On the other hand, by using the second constructor, your repository will work with a connection provided by a unit of work, so all operations will be executed under the context of a transaction.

DbRepository Methods

The DbRepository class provides several protected and virtual methods that are wrappers for the extension methods on IDbConnection provided by the Flowsy.Db.Conventions package. The benefit of using these methods instead of calling the extension methods directly is that DbRepository always uses its associated connection, transaction and conventions. In other words, you don't have to worry about connection, transaction and convention handling once you set it all up when your application starts.

  • Execute: Executes a query and returns the number of affected rows.
  • ExecuteAsync: Asynchronously executes a query and returns the number of affected rows.
  • Query: Executes a query and returns a collection of objects of the specified type.
  • QueryAsync: Asynchronously executes a query and returns a collection of objects of the specified type.
  • QueryFirst: Executes a query and returns the first result or throws an exception if none is found. The result is mapped to the specified type.
  • QueryFirstAsync: Asynchronously executes a query and returns the first result or throws an exception if none is found. The result is mapped to the specified type.
  • QueryFirstOrDefault: Executes a query and returns the first result or the default value if none is found. The result is mapped to the specified type.
  • QueryFirstOrDefaultAsync: Asynchronously executes a query and returns the first result or the default value if none is found. The result is mapped to the specified type.
  • QueryMultiple: Executes a query and returns multiple result sets.
  • QueryMultipleAsync: Asynchronously executes a query and returns multiple result sets.
  • QuerySingle: Executes a query and returns a single result or throws an exception if none or more than one is found. The result is mapped to the specified type.
  • QuerySingleAsync: Asynchronously executes a query and returns a single result or throws an exception if none or more than one is found. The result is mapped to the specified type.
  • QuerySingleOrDefault: Executes a query and returns a single result or the default value if none or more than one is found. The result is mapped to the specified type.
  • QuerySingleOrDefaultAsync: Asynchronously executes a query and returns a single result or the default value if none or more than one is found. The result is mapped to the specified type.

DbUnitOfWork

This class implements the IUnitOfWork interface and allows consumers to perform operations which will be automatically undone if an exception is thrown before calling the SaveWork or SaveWorkAsync methods.

This way, when you're working in the domain and application layers, you don't need to think in terms of database transactions, instead you can think in terms of a higher level of abstraction: a unit of work or set of operations that will be fully saved or fully undone.

For example, you could create the following interface in your domain layer:

using Flowsy.Db.Repository.Abstractions;

namespace Example.Domain;

public interface IAcademicAssessmentUnitOfWork : IUnitOfWork
{
    // Repositories involved in the academic assessment context
    IStudentRepository StudentRepository { get; }
    ICourseRepository CourseRepository { get; }
}

And then create the following class in your persistence layer:

using Example.Domain;
using Flowsy.Db.Repository.Sql;

namespace Example.Persistence;

public class DbAcademicAssessmentUnitOfWork : DbUnitOfWork, IAcademicAssessmentUnitOfWork
{
    public DbAcademicAssessmentUnitOfWork(IDbConnection connection, ILogger? logger = null) : base(connection, logger)
    {
    }
  
    private IStudentRepository? _studentRepository;
    public IStudentRepository StudentRepository => _studentRepository ??= CreateRepository<IStudentRepository, DbStudentRepository>();
  
    private ICourseRepository? _courseRepository;
    public ICourseRepository CourseRepository => _courseRepository ??= CreateRepository<ICourseRepository, DbCourseRepository>();
}

This way, all operations performed by the IStudentRepository and ICourseRepository objects will be executed under the same transaction, and if an exception is thrown before calling the SaveWork or SaveWorkAsync methods, all operations will be undone.

DbUnitOfWork Methods

  • BeginWork: Starts a new transaction.
  • SaveWork: Commits the transaction.
  • SaveWorkAsync: Asynchronously commits the transaction.
  • DiscardWork: Rolls back the transaction.
  • DiscardWorkAsync: Asynchronously rolls back the transaction.
  • OnWorkBegun (protected virtual): Called when a transaction is started.
  • OnWorkSaved (protected virtual): Called when a transaction is committed.
  • OnWorkDiscarded (protected virtual): Called when a transaction is rolled back.

DbUnitOfWork Events

  • WorkBegun: Event that is raised when a transaction is started.
  • WorkSaved: Event that is raised when a transaction is committed.
  • WorkDiscarded: Event that is raised when a transaction is rolled back.

Dependency Injection

For non-trivial applications, the recommended way of using repositories and units of work is by registering them in the dependency injection container. This package provides extension methods for IServiceCollection to register repositories and units of work.

In any application that uses the Microsoft.Extensions.DependencyInjection package (like Web APIs), you can register repositories and units of work as follows:

appsettings.json

Configure connections for every database required by your application in the appsettings.json file.

{
  "Databases": {
    "MyDatabase": {
      "ProviderInvariantName": "Npgsql",
      "ConnectionString": "Server=localhost;Port=5432;Database=my_database;User Id=my_user;Password=mysecret;Include Error Detail=True;"
    }
  }
}

Program.cs

var builder = WebApplication.CreateBuilder(args);

// ...
// Register services
// ...

// Register the required database providers.
// Flowsy.Db.Conventions and Flowsy.Db.Repository.Sql won't perform any registration of database providers,
// to be decoupled from a specific database provider and let your application decide which one to use.
// This example uses Npgsql as the database provider.
DbProviderFactories.RegisterFactory("Npgsql", NpgsqlFactory.Instance);

// Register the IDbConnectionFactory (from Flowsy.Db.Conventions)
builder.Services.AddConnectionFactory((options, serviceProvider) =>
        {
            var connectionOptions = new Dictionary<string, DbConnectionOptions>();
            
            var config = serviceProvider.GetRequiredService<IConfiguration>();
            var connectionConfigurations = config
                .GetSection("Databases")
                .GetChildren();
            
            foreach (var connectionConfiguration in connectionConfigurations)
            {
                var connectionKey = connectionConfiguration.Key;
                var providerInvariantName = connectionConfiguration["ProviderInvariantName"];
                var connectionString = connectionConfiguration["ConnectionString"];
                
                var provider = providerInvariantName switch
                {
                    "Npgsql" => DbProvider.PostgreSql,
                    _ => throw new NotSupportedException($"The provider '{providerInvariantName}' is not supported.")
                };;
                
                connectionOptions[connectionKey] = new DbConnectionOptions(provider, connectionString);
            }
            
            options.ConnectionOptions = connectionOptions;
        });

// Register the default conventions
builder.Services
    .AddRepositories(options => 
    {
        // Log level for SQL commands executed by all repositories
        options.LogLevel = LogLevel.Debug;
        
        // Resolve the types of the objects that need to be mapped using the conventions
        var types = Assembly
            .GetExecutingAssembly()
            .GetTypes()
            .Where(t => t.Namespace?.EndsWith(".Domain") ?? false)
            .ToArray();
        
        options.Conventions
            .ForConnections("MyDatabase") // Configuration key from appsettings.json
            .ForSchemas("my_schema")
            .ForTables(CaseStyle.LowerSnakeCase)
            .ForColumns(CaseStyle.LowerSnakeCase, types)
            .ForRoutines(DbRoutineType.StoredFunction, CaseStyle.LowerSnakeCase)
            .ForParameters(CaseStyle.LowerSnakeCase, "p_", useNamedParameters: true)
            .ForEnums(DbEnumFormat.Name, CaseStyle.PascalCase, customTypeMapping: new Dictionary<Type, string>
            {
                { typeof(MyEnum), "my_schema.my_enum" }
            });
    })
    .UsingRepository<IStudentRepository, DbStudentRepository>(options => {
        // Optionally configure options for each repository
        options.Conventions.ForSchema("another_schema");
    })
    .UsingRepository<ICourseRepository, DbCourseRepository>(); // Use the default conventions.

// The UsingRepository method will look for a constructor  that
// receives an IDbConnectionFactory instance to create the repository. 

// Register the unit of work
// IAcademicAssessmentUnitOfWork must inherit from IUnitOfWork.
// DbAcademicAssessmentUnitOfWork must inherit from DbUnitOfWork
// and have a constructor that receives an IDbConnection instance.
// The connection name must match the name of the corresponding section in the appsettings.json file.
builder.Services.AddUnitOfWork<IAcademicAssessmentUnitOfWork, DbAcademicAssessmentUnitOfWork>(
    "MyDatabase",
    (connection, serviceProvider) => 
    {
        var logger = serviceProvider.GetRequiredService<ILogger<DbAcademicAssessmentUnitOfWork>>();
        return new DbAcademicAssessmentUnitOfWork(connection, logger);
    });

var app = builder.Build();

// ...

app.Run();

Both IDbConnectionFactory and IUnitOfWork will be registered as scoped services, so their associated connections will live only within the corresponding scope, for instance the scope of a web request.

Using the Services

You can use your repositories and units of work just by injecting them into the constructor of your services.

using Example.Domain;

namespace Example.Application;

public class EvaluateStudentCommandHandler
{
    private readonly IAcademicAssessmentUnitOfWork _unitOfWork;

    public EvaluateStudentCommandHandler(IAcademicAssessmentUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public async Task HandleAsync(EvaluateStudentCommand command, CancellationToken cancellationToken)
    {
        var student = new Student();;
  
        // Perform operations on the student entity
        // using values provided by the command
        // ...
  
        _unitOfWork.BeginWork();
  
        // Fictitious method that will save operations performed on the student entity
        await _unitOfWork.StudentRepository.SaveAsync(student, cancellationToken);
  
        // Call methods on other repositories of the unit of work
        // await _unitOfWork.CourseRepository.SomeOperationAsync(...);
  
        await _unitOfWork.SaveWorkAsync();
  
        // If an exception is thrown before calling SaveWorkAsync, all operations will be automatically undone
    }
}
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  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. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos 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.0.1 61 11/24/2024
1.0.0 70 11/21/2024
0.1.0 111 9/15/2024