Byndyusoft.Data.Relational.QueryBuilder 0.5.0

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

// Install Byndyusoft.Data.Relational.QueryBuilder as a Cake Tool
#tool nuget:?package=Byndyusoft.Data.Relational.QueryBuilder&version=0.5.0                

Byndyusoft.Data.Relational.QueryBuilder Nuget Downloads

This library allows to create SQL queries.

The main concept of the library is to avoid using string constants for column names in queries. This would allow to be more confident and peaceful while performing code refactoring. Additionally, this can reduce the need to check if queries are written correctly.

For standard queries that involve all columns (SELECT, UPDATE, INSERT), you won't need to worry about adding or deleting a new column.

Below are the descriptions of standard use cases.

Installing

dotnet add package Byndyusoft.Data.Relational.QueryBuilder 

Usage

Data model for examples

We will use the following data model for the examples.

public class Company : IEntity
{
  public long Id { get; set; }
  public string Name { get; set; } = default!;
  public string Inn { get; set; } = default!;
}

public class User : IEntity
{
  public long Id { get; set; }
  public string Login { get; set; } = default!;
  public string Password { get; set; } = default!;
  public long CompanyId { get; set; }
}

Any type can be used for the primary key for queries. If the IEntity interface is used for models, where the primary key is defined as long, some queries will be easier, for example, inserting all fields, updating all fields, filtering by ID.

The library will treat all public properties with existing get and set methods as table columns. Their names will correspond to the column names (SnakeCase formatting is used for PostgreSQL).

Select

Querying an entity from a table.

You need to create a SelectQuery object to create a SELECT query. Here is an example for the Company entity:

public class SelectQuery : SelectQueryBuilderBase<SelectQuery>
{
  protected override void PrepareFrom()
  {
    FromCollector.From<Company>(TableNames.Company, Aliases.Company);
  }

  protected override void PrepareSelect()
  {
    SelectCollector.To<Company>(Aliases.Company).GetAllPublicValues();
  }

  public SelectQuery ById(long id)
  {
    Conditionals.For<Company>(Aliases.Company).ById(id);
    return this;
  }

  public SelectQuery ByName(string name)
  {
    Conditionals.For<Company>(Aliases.Company).AddEquals(i => i.Name, name);
    return this;
  }
}

Example queries to retrieve a Company by ID and by name:

public class CompanyRepository : DbSessionConsumer
{
  public CompanyRepository(IDbSessionAccessor sessionAccessor) : base(sessionAccessor)
  {
  }

  public async Task<Company?> GetById(long id, CancellationToken cancellationToken)
  {
    var queryObject = new SelectQuery().ById(id).Build();
    return await DbSession.QuerySingleOrDefaultAsync(queryObject, cancellationToken: cancellationToken);
  }

  public async Task<Company[]> GetByName(string name, CancellationToken cancellationToken)
  {
    var queryObject = new SelectQuery().ByName(name).Build();
    var companies = await DbSession.QueryAsync<Company>(queryObject, cancellationToken: cancellationToken);
    return companies.ToArray();
  }
}

Query from multiple tables with data projection into DTO

Let's assume we need to obtain information about a user in the form of the following object:

public class UserDto
{
  public string Login { get; set; } = default!;
  public string Password { get; set; } = default!;
  public string CompanyName { get; set; } = default!;
}

Example of a repository and the SelectQuery object:

public class UserDtoRepository : DbSessionConsumer
{
  public UserDtoRepository(IDbSessionAccessor sessionAccessor) : base(sessionAccessor)
  {
  }

  public async Task<UserDto?> GetByIdAsync(long id, CancellationToken cancellationToken)
  {
    var queryObject = new SelectQuery().ById(id).Build();
    return await DbSession.QuerySingleOrDefaultAsync<UserDto>(queryObject,
      cancellationToken: cancellationToken);
  }

  public class SelectQuery : SelectQueryBuilderBase<SelectQuery>
  {
    protected override void PrepareFrom()
    {
      FromCollector
        .From<User>(TableNames.Users, Aliases.Users)
        .InnerJoin(TableNames.Company, Aliases.Company, i => i.CompanyId);
    }

    protected override void PrepareSelect()
    {
      SelectCollector.To<UserDto>()
        .From<User>(Aliases.Users)
          .Get(u => u.Login, dto => dto.Login)
          .Get(u => u.Password, dto => dto.Password)
        .Other<Company>(Aliases.Company)
          .Get(c => c.Name, dto => dto.CompanyName);
    }

    public SelectQuery ById(long id)
    {
      Conditionals.For<User>(Aliases.Users).ById(id);
      return this;
    }
  }
}

Query from multiple tables with aggregation and sorting

Let's suppose we need to obtain information about a list of companies with the number of users:

public class CompanyReportDto
{
  public string CompanyName { get; set; } = default!;
  public long UserCount { get; set; }
}

Example of a repository and the SelectQuery object:

public class CompanyReportDtoRepository : DbSessionConsumer
{
  public CompanyReportDtoRepository(IDbSessionAccessor sessionAccessor) : base(sessionAccessor)
  {
  }

  public async Task<CompanyReportDto[]> GetAsync(CancellationToken cancellationToken)
  {
    var queryObject = new SelectQuery().Build();
    var companyReportDtos = await DbSession.QueryAsync<CompanyReportDto>(queryObject, cancellationToken: cancellationToken);
    return companyReportDtos.ToArray();
  }

  public class SelectQuery : SelectQueryBuilderBase<SelectQuery>
  {
    public SelectQuery()
    {
      GroupBy.For<Company>(Aliases.Company).Add(i => i.Name);
      OrderBy.Add<Company, string>(i => i.Name, isDescending: false, Aliases.Company);
    }

    protected override void PrepareFrom()
    {
      FromCollector
        .From<Company>(TableNames.Company, Aliases.Company)
        .LeftJoin<User>(TableNames.Users, Aliases.Users, (c, u) => $"{c.Id} = {u.CompanyId}");
    }

    protected override void PrepareSelect()
    {
      SelectCollector.To<CompanyReportDto>()
        .Get<User, long>(
          dto => dto.UserCount,
          u => $"SUM(CASE WHEN {u.Id} IS NULL THEN 0 ELSE 1 END)",
          Aliases.Users)
        .From<Company>(Aliases.Company)
          .Get(c => c.Name, dto => dto.CompanyName);
    }
  }
}

Insert

Example of inserting a Company:

public async Task InsertAsync(Company company, CancellationToken cancellationToken)
{
  var queryObject = InsertQueryBuilder<Company>
    .For(company, TableNames.Company)
    .InsertAllPublicValues()
    .Build();
  var id = await DbSession.ExecuteScalarAsync<long>(queryObject, cancellationToken: cancellationToken);
  company.Id = id;
}

Update

Updating all fields

Example of updating all Company fields:

public async Task UpdateAsync(Company company, CancellationToken cancellationToken)
{
  var queryObject = UpdateItemQueryBuilder<Company>
    .For(company, TableNames.Company)
    .UpdateAllPublicValues()
    .ById()
    .Build();
  await DbSession.ExecuteAsync(queryObject, cancellationToken: cancellationToken);
}

Updating a single field

Example of updating the Company's tax identification number (INN):

public async Task UpdateInnAsync(long id, string inn, CancellationToken cancellationToken)
{
  var queryObject = UpdateQueryBuilder<Company>
    .For(TableNames.Company)
    .Set(i => i.Inn, inn)
    .ById(id)
    .Build();
  await DbSession.ExecuteAsync(queryObject, cancellationToken: cancellationToken);
}

Delete

Example of deleting a Company:

public async Task DeleteByIdAsync(long id, CancellationToken cancellationToken)
{
  var queryObject = DeleteQueryBuilder<Company>
    .For(TableNames.Company)
    .ById(id)
    .Build();
  await DbSession.ExecuteAsync(queryObject, cancellationToken: cancellationToken);
}

Custom queries using ColumnConverter

An alternative example of fetching the UserDto, as written above:

public async Task<UserDto?> GetByIdAlternativelyAsync(long id, CancellationToken cancellationToken)
{
  var columnConverter = new ColumnConverter(true);
  var sql = columnConverter.Map<User, Company>((user, company) => $@"
SELECT
  {user.Login} AS {nameof(UserDto.Login)},
  {user.Password} AS {nameof(UserDto.Password)},
  {company.Name} AS {nameof(UserDto.CompanyName)}
FROM
  {TableNames.Users} u
  INNER JOIN {TableNames.Company} c on {company.Id} = {user.CompanyId}
WHERE
  {user.Id} = @UserId", "u", "c");

  var queryObject = new QueryObject(sql, new { UserId = id });
  return await DbSession.QuerySingleOrDefaultAsync<UserDto>(queryObject,
    cancellationToken: cancellationToken);
}
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
2.2.0 305 6/6/2024
2.1.0 190 3/25/2024
2.0.0 129 3/7/2024
1.0.0 1,112 2/12/2024
0.5.0 351 1/9/2024
0.4.1 175 8/17/2023
0.4.0 1,967 1/20/2023
0.3.0 274 1/17/2023
0.2.0 281 1/16/2023
0.2.0-aplpha 149 1/16/2023
0.1.0 1,103 9/9/2022