Executing query via nested inner class query handler
I would say that we should be able to execute different query handlers given the same query parameters – for testing purposes. Simple data transfer object (query) being passed to the repository interface allows us to do it in a well-known and understandable way. 

The same time the following works well for me (SSAS MDX queries)…

To define MDX I use a partial class in multiple files (example):

// SalesSummary.cs
public static partial class MdxQueries
{
    public static IEnumerable&lt;TradeSummary&gt; SalesSummary(DateTime day, string department) =&gt;
        new MdxQuery($@"MDX SCRIPT WITH PARAMETER={day} AND PARAMETER={department}")
            .Select(r =&gt; new TradeSummary
            {
                Product = r["Product"],
                Total = r["Total"]
            });
}

// PurchaseSummary.cs
public static partial class MdxQueries
{
    public static IEnumerable&lt;TradeSummary&gt; PurchaseSummary(DateTime day, string department) =&gt;
        new MdxQuery($@"MDX SCRIPT WITH PARAMETER={day} AND PARAMETER={department}")
            .Select(r =&gt; new TradeSummary
            {
                Product = r["Product"],
                Total = r["Total"]
            });
}


Where library classes are:

public class MdxQuery : IEnumerable&lt;MdxRow&gt;
{
    static string ConnectionString =&gt; ConfigurationManager.ConnectionStrings["SSAS"]
        .ConnectionString;

    public MdxQuery(string mdx)
        : this(mdx, ConnectionString)
    {
    }

    public MdxQuery(string mdx, string connectionString)
    {
        _mdx = mdx;
        _connectionString = connectionString;
    }

    readonly string _mdx;
    readonly string _connectionString;

    public IEnumerator&lt;MdxRow&gt; GetEnumerator()
    {
        using (var connection = new AdomdConnection(_connectionString))
        {
            connection.Open();
            var command = connection.CreateCommand();
            command.CommandText = _mdx;
            var reader = command.ExecuteReader();
            while (reader.Read())
                yield return new MdxRow(reader);
        }
    }

    IEnumerator IEnumerable.GetEnumerator() =&gt; GetEnumerator();

    public static implicit operator MdxQuery(string mdx) =&gt;
        new MdxQuery(mdx);
}


Where:

public struct MdxRow
{
    internal MdxRow(AdomdDataReader reader)
    {
        Values = Enumerable.Range(0, reader.FieldCount)
            .ToDictionary(i =&gt; reader.GetName(i), i =&gt; new MdxValue(reader[i]));

        ValuesByIndex = Enumerable.Range(0, reader.FieldCount)
            .ToDictionary(i =&gt; i, i =&gt; new MdxValue(reader[i]));
    }

    public MdxValue this[string column] =&gt; Values[$"[Measures].[{column}]"];
    public MdxValue this[int column] =&gt; ValuesByIndex[column];
    Dictionary&lt;string, MdxValue&gt; Values { get; }
    Dictionary&lt;int, MdxValue&gt; ValuesByIndex { get; }
}


Where:

public struct MdxValue
{
    public MdxValue(object value)
    {
        Value = value;
    }

    object Value { get; }

    public static implicit operator string(MdxValue value) =&gt;
        Convert.ToString(value.Value);

    public static implicit operator int(MdxValue value) =&gt;
        Convert.ToInt32(value.Value ?? 0);

    public static implicit operator double(MdxValue value) =&gt;
        Convert.ToDouble(value.Value ?? 0);

    public static implicit operator int? (MdxValue value) =&gt;
        value.IsNull ? (int?)null : (int)value;

    public static implicit operator double? (MdxValue value) =&gt;
        value.IsNull ? (double?)null : (double)value;

    public static implicit operator DateTime(MdxValue value) =&gt;
        DateTime.Parse((string)value ?? DateTime.MinValue.ToString());

    public static implicit operator DateTime? (MdxValue value) =&gt;
        value.IsNull ? (DateTime?)null : (DateTime)value;

    public bool IsNull =&gt; Value == null;
}


All this stuff is used to implement repositories, which depend on DTO.