Appy.CommandQuery 3.0.1-preview.0.2

This is a prerelease version of Appy.CommandQuery.
dotnet add package Appy.CommandQuery --version 3.0.1-preview.0.2
                    
NuGet\Install-Package Appy.CommandQuery -Version 3.0.1-preview.0.2
                    
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="Appy.CommandQuery" Version="3.0.1-preview.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Appy.CommandQuery" Version="3.0.1-preview.0.2" />
                    
Directory.Packages.props
<PackageReference Include="Appy.CommandQuery" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Appy.CommandQuery --version 3.0.1-preview.0.2
                    
#r "nuget: Appy.CommandQuery, 3.0.1-preview.0.2"
                    
#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.
#:package Appy.CommandQuery@3.0.1-preview.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Appy.CommandQuery&version=3.0.1-preview.0.2&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Appy.CommandQuery&version=3.0.1-preview.0.2&prerelease
                    
Install as a Cake Tool

Appy.CommandQuery

AppyWay logo

What is Appy.CommandQuery?

Appy.CommandQuery is a package that allows you to separate data sources from the calling code by using messages as a mode of transport, rather than direct method calls.

Packages

Package Latest Stable
Appy.CommandQuery Nuget Package

Table of Contents

Supported frameworks

Appy.CommandQuery targets net10.0 and net8.0; apps on net9.0 use the net8.0 build. It depends on Polly 8.

Version 3.0 drops net461, netstandard2.0 and net6.0. Projects on those frameworks can stay on 2.1.0.

Installing

Install using the Appy.CommandQuery NuGet package:

PM> Install-Package Appy.CommandQuery

Usage

Creating a query
public class UserById : IQuery<IDbConnection, User>
{
  public string UserId {get; }
  public GetUserById(string userId) =>
    UserId = userId;

  public async Task<User> Get(IDbConnection connection)
  {
    using (var cmd = new SqlCommand("SELECT * FROM Users WHERE ID = @Id", connection))
    {
      cmd.Parameters.Add("@ID", SqlDbType.Int);
      cmd.Parameters["@ID"].Value = UserId;
    
      connection.Open();
      using (var reader = await cmd.ExecuteReaderAsync())
      {
        if (!reader.HasRows) return null;
        
        reader.Read();
        return new User
        {
          Id = UserId,
          Name = reader.GetString(reader.GetOrdinal("Name"))
          // ...
        }
      }
    }
  }
}
Executing a query
  var dataSource = new DataSource(() => new SqlConnection("connectionstring"));

  var user = await dataSource.Get(new UserById(myUserId));

Decoupling datasource

In the above example, you're still required to instantiate the datasource and ensure you provide the correct callback in the constructor of the data source. The goal is to inject the data source, so your calling code can be independent.

Here's an example using the Ninject DI container:

Composition root

  Kernel.Bind<IDataSource>
        .ToConstant(ctx => new DataSource(type => Kernel.GetInstance(type)));

Client

public class SomeClient
{
  IDataSource _dataSource;
  public SomeClient(IDataSource dataSource) => 
    _dataSource = dataSource;

  public async Task SomeMethod()
  {
    var myUserId = "123";
    var user = await dataSource.Get(new UserById(myUserId));
    // ...
  }
}

This effectively decouples the client class from the underlying datasource, because all the details will be handled in the composition root. Even if you have multiple data sources (SQL database, network calls, azure table storage, ...), you only need a reference to an appropriately configured IDataSource to handle all underlying setup:

Note: naming of queries is for clarity. I'd advise against putting the type of the datasource in the class name as that would leak details to the client

class UserFromSql : IQuery<IDbConnection, User>
{
  public Task<User> Get(IDbConnection connection)
  {
    // Use IDbConnection to retrieve user
  }

}
class ProductFromApi : IQuery<HttpClient, Product>
{
  public Task<Product> Get(HttpClient client)
  {
    // Use HttpClient to get product
  }
}
class BlobFromAzure : IQuery<CloudBlobContainer, Stream>
{
  public Task<Strean> Get(CloudBlobContainer blobContainer)
  {
    // Use CloudBlobContainer to get stream
  }
}

Now, you can use all three queries to connect to different data sources without being dependent on the underlying data sources:

public class SomeClient
{
  IDataSource _dataSource;
  public SomeClient(IDataSource dataSource) => 
    _dataSource = dataSource;

  public async Task SomeMethod()
  {
    var user = await dataSource.Get(new UserFromSql());
    var product = await dataSource.Get(new ProductFromApi());
    var blob = await dataSource.Get(new BlobFromAzure());
    // ...
  }
}

Commands

Commands work in the same way as queries, except that they don't have a return type:

public class UpdateUserName : IQuery<IDbConnection, User>
{
  public string UserId {get; }
  public string Name {get; }
  
  public UpdateUserName(string userId, string name)
  {
    UserId = userId;
    Name = name;
  }

  public async Task Execute(IDbConnection connection)
  {
    using (var cmd = new SqlCommand("UPDATE Users SET Name = @Name WHERE ID = @Id", connection))
    {
      cmd.Parameters.Add("@ID", SqlDbType.Int);
      cmd.Parameters["@ID"].Value = UserId;

      cmd.Parameters.Add("@Name", SqlDbType.NVarChar);
      cmd.Parameters["@Name"].Value = Name;
    
      connection.Open();
      await cmd.ExecuteNonQueryAsync();
    }
  }
}
public class SomeClient
{
  IDataSource _dataSource;
  public SomeClient(IDataSource dataSource) => 
    _dataSource = dataSource;

  public async Task SomeMethod()
  {
    await dataSource.Execute(new UpdateUserName("123", "new name"));
    // ...
  }
}

NOTE: there's also an ICommand interface available which does have a return type. It is functionally equivalent to the IQuery interface, but changes the semantics. This can be used in cases where you want to return the result of executing a command (eg: Inserting a user and returning the ID).

Testing

Rather than having to mock a myriad of interfaces to swap out all external dependencies you can mock just the IDataSource and verify that the correct queries have been executed.

Example using Moq, verifying whether all queries were correctly executed:

var dataSourceMock = new Mock<IDataSource>();

dataSourceMock.Verify(dataSource => dataSource.Execute(It.IsAny<UpdateUsername>()));
dataSourceMock.Verify(dataSource => dataSource.Get(It.Is<UserById>(q => q.UserId == "123")));
dataSourceMock.Verify(dataSource => dataSource.Execute(It.IsAny<ProductFromApi>()));
dataSourceMock.Verify(dataSource => dataSource.Execute(It.IsAny<BlobFromAzure>()));

Or, you can also setup the mock to return expected results:

dataSourceMock.Setup(dataSource => dataSource.Execute(It.IsAny<ProductFromApi>()))
              .Returns(Task.FromResult(new Product()));

Documentation

Contribute

It would be awesome if you would like to contribute code or help with bugs. Just follow the guidelines CONTRIBUTING

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.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

  • net8.0

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
3.0.1-preview.0.2 32 10/1/2026
3.0.0 71 10/1/2026
3.0.0-preview.0.27 44 10/1/2026
2.1.0 4,219 7/22/2022
2.0.0 7,095 5/7/2019
1.1.0 6,333 12/11/2017
0.0.0-preview.0.3 294 7/22/2022