Syrx 2.2.0

dotnet add package Syrx --version 2.2.0                
NuGet\Install-Package Syrx -Version 2.2.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="Syrx" Version="2.2.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Syrx --version 2.2.0                
#r "nuget: Syrx, 2.2.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 Syrx as a Cake Addin
#addin nuget:?package=Syrx&version=2.2.0

// Install Syrx as a Cake Tool
#tool nuget:?package=Syrx&version=2.2.0                

Syrx

Syrx allows you to write the same repository code to target multiple different databases. It is a framework which decouples your .NET repository code from the underlying data store, allowing you to shield your repository code from the underlying storage provider. It's not quite an ORM although it uses a micro-ORM to materialize and persist your objects.

Syrx places emphasis on:

  • Control: You should be in control of your data and execution.
  • Speed: Performance is a feature. Syrx inherits its speed from Dapper.
  • Flexibilty: Your choice of database technology should be as easy to change as any other component in your solution.
  • Testability: We believe strongly in the power of testing at all levels of your applications. Syrx is fully testable and fully tested.
  • Extensibility: Syrx is granular and componentised, allowing you two swap out components for your own needs.
  • Readability: Syrx aims to keep the intent of your code clear and concise.

Quick Look

Syrx is predicated on the concept that all operations against a data store are fundamentally either a read or write operation. The central construct in Syrx is the ICommander interface. This interface exposes overloads of only 2 methods, Query and Execute (as well as their async implementations QueryAsync and ExecuteAsync.

  • Query methods are used to retrieve data.
  • Execute methods are used to create/modify/delete data.

Query

Syrx allows you to retrieve both primitive and complex types from the underlying database very simply. There are no special attribute declarations, no special inheritance structures. Very often, complex types can be retrieved with a single line of code. More complex types can be composed using predicates. Syrx supports Func<> predicate with up to 16 inputs for composition.

Example

Let's assume your app needs to retrieve a list of countries. With Syrx, this is a single line of C# code.

// retrieving a collection of countries
public IEnumerable<Country> RetrieveAll() => _commander.Query<Country>();

// retrieving a collection of countries asynchronously
public async Task<IEnumerable<Country>> RetrieveAllAsync() => await _commander.QueryAsync<Country>();

// with parameters
public IEnumerable<Country> RetrieveAll(string languageSpoken) => _commander.Query<Country>(new { languageSpoken });

// with separate mapping predicate (assume mapping Language and Currency to a Country type)
public IEnumerable<Country> RetrieveAll() => _commander.Query(Compose.ToCountry);

// composition predicate stored in a separate class, keeping your repository
// cleaner and more readable. 
private static class Compose
{
    // composition predicate providing you with full control over how your complex type is 
    // constructed. in this example, Country, Language and Country are all complex types.     
    static Func<Country, Language, Currency, Country> ToCountry(country, language, currency) => 
    {
        country.Language = language;
        country.Currency = currency;
        return country;
    };
}

Execute

Persistence with Syrx follows a similarly simple signature. Syrx implementations against RDBMS vendors are transactional by default. Any exceptions thrown during the Execute method are bubbled back up the stack with their stack trace preserved.

Execute methods return bool (or Task<bool> for async calls) so returning the peristed instance is done using a conditional operator.

As Update/Delete are essentially variants of a write operation, they can use the exact same signature as Create. The differentiation is made by the SQL supplied for the method.

Example
// persisting the country type
public Country Create(Country country) => _commander.Execute(country) ? country : null;

// persisting the country type asynchronously
public async Task<Country> Create(Country country) => await _commander.ExecuteAsync(country) ? country : null;

// update
public Country Update(Country country) => _commander.Execute(country) ? country : null;

// delete
public Country Delete(Country country) => _commander.Execute(country) ? country : null;

Configuration

Syrx offers a unique configuration structure in that it mimics the structure of your code. In most scenarios where you need to execute code against a database, that SQL statement will be executed from a specific method. Syrx uses the fully qualified type and method names from your code to resolve the command to be executed against the underlying datastore.

There's a wide set of configration extensions to simplify and support configuration.

Example

Using our Country example from earlier, let's assume we we have the repository definition below. Our repository retrieves a list of countries stored in our database. Using a very simple select statement:

select * from [dbo].[country];

We have a very simple repository type which we wrote to handle the various CRUD operation for this domain model. The expectation is that the RetrieveAllAsync method will execute that very simple SQL statement.

namespace Syrx.Samples
{
    public class CountryRepository(ICommander<CountryRepository> commander) : ICountryRepository
    {
        // instance of our ICommander passed in via dependency injection. 
        private readonly ICommander<CountryRepository> _commander = commander;

        // the method responsible for executing our select statement. 
        public async Task<IEnumerable<Country>> RetrieveAllAsync(CancellationToken cancellationToken = default)
        {
            return await _commander.QueryAsync<Country>(cancellationToken: cancellationToken);
        }
    }    
}

Looking at this code, we can see that the we want the RetrieveAllAsync method of the CountryRepository to execute our SQL query against an underlying data store. Structurally, you could think of it as being part of a hierarchy:

Syrx.Samples                                // <-- the namespace
    CountryRepository                       // <-- the type
        RetrieveAllAsync                    // <-- the method
            select * from [dbo].[country]   // <-- the SQL

And it's in considering that hierarchy that we see how Syrx configuration mimics your code structure.

To wire this up to a SQL Server database:

  • First install the Syrx.SqlServer.Extensions package install-package Syrx.SqlServer.Extensions.
  • Then start wiring up the SQL to be executed against the method which will execute it.
  • This declarative approach employs the builder pattern and several different builders to make the wiring up simple and less open to error.
namespace Syrx.Samples
{
    public static class SyrxInstaller
    {
        public static  IServiceCollection Install(this IServiceCollection services)
        {   
            return services.UseSyrx(builder =>                                                  // call the UseSyrx extension method on IServiceCollection. 
                builder.UseSqlServer(sqlServer =>                                               // add support for the relevant provider. in this case, SQL Server. 
                    sqlServer.AddCommand(types => types.ForType<CountryRepository>(             // start adding commands per repository type. 
                                methods => methods.ForMethod(nameof(CountryRepository.RetrieveAllAsync),  // reference the method that will execute the command. 
                                command => command // start building up the command
                                        .UseConnectionAlias("Example")                          // reference a connection string by an alias provided separately. 
                                        .UseCommandText("select * from [dbo].[country];")       // supply the SQL that you want to be executed.  
                                        )))));
        }
    }
}

Support Runtimes and Vendors

Syrx is cross-platforn and currently supports up to .NET 8.

Syrx currently supports the RDBMS vendors vendors below and this list is growing. Implementing support for a new ADO.NET provider is easily accomplished.

Vendor Package Extensions Package
SQL Server Syrx.SqlServer Syrx.SqlServer.Extensions
MySql Syrx.MySql Syrx.MySql.Extensions
PostgreSql Syrx.Npgsql Syrx.Npgsql.Extensions
Oracle Syrx.Oracle Syrx.Oracle.Extensions

In fact, any vendor that has a DbProviderFactory can be supported.

Credits and Caveats

Syrx inspired by and in part based on Dapper. Syrx will not generate SQL for you (although this feature may be added in the future).

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  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.
  • net8.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Syrx:

Package Downloads
Syrx.Commanders.Databases

This package hosts the implementation of the Syrx Database Commander which is a wrapper over Dapper's Query and Execute methods.

Syrx.Extensions

This package hosts extension methods to simplify wiring up Syrx.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.2.0 401 11/21/2024
2.1.0 235 11/15/2024
2.0.0 258 7/5/2024
1.1.0 9,604 10/2/2017
1.0.0 1,749 10/2/2017

Updated to .NET8.0.