Skip to content

Commit 17e94f8

Browse files
committed
feat: Wire up HighNeedsService.Get() to underlying database views as override to stubbed version
Using Dapper multi mapping
1 parent 4b1d698 commit 17e94f8

File tree

10 files changed

+279
-54
lines changed

10 files changed

+279
-54
lines changed

platform/src/abstractions/Platform.Sql/DatabaseConnection.cs

+58-41
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,27 @@ public Task<IEnumerable<T>> QueryAsync<T>(string sql, object? param = null, Canc
5353
public Task<IEnumerable<T>> QueryAsync<T>(PlatformQuery query, CancellationToken cancellationToken = default)
5454
=> connection.QueryAsync<T>(new CommandDefinition(query.QueryTemplate.RawSql, query.QueryTemplate.Parameters, cancellationToken: cancellationToken));
5555

56+
public Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TReturn>(PlatformQuery query, Func<TFirst, TSecond, TReturn> map, string[] splitOn, CancellationToken cancellationToken = default) =>
57+
connection.QueryAsync(new CommandDefinition(query.QueryTemplate.RawSql, query.QueryTemplate.Parameters, cancellationToken: cancellationToken), map, string.Join(", ", splitOn));
58+
59+
public Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TReturn>(PlatformQuery query, Func<TFirst, TSecond, TThird, TReturn> map, string[] splitOn, CancellationToken cancellationToken = default) =>
60+
connection.QueryAsync(new CommandDefinition(query.QueryTemplate.RawSql, query.QueryTemplate.Parameters, cancellationToken: cancellationToken), map, string.Join(", ", splitOn));
61+
62+
public Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TReturn>(PlatformQuery query, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, string[] splitOn, CancellationToken cancellationToken = default) =>
63+
connection.QueryAsync(new CommandDefinition(query.QueryTemplate.RawSql, query.QueryTemplate.Parameters, cancellationToken: cancellationToken), map, string.Join(", ", splitOn));
64+
65+
public Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(PlatformQuery query, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, string[] splitOn, CancellationToken cancellationToken = default) =>
66+
connection.QueryAsync(new CommandDefinition(query.QueryTemplate.RawSql, query.QueryTemplate.Parameters, cancellationToken: cancellationToken), map, string.Join(", ", splitOn));
67+
68+
public Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(PlatformQuery query, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> map, string[] splitOn, CancellationToken cancellationToken = default) =>
69+
connection.QueryAsync(new CommandDefinition(query.QueryTemplate.RawSql, query.QueryTemplate.Parameters, cancellationToken: cancellationToken), map, string.Join(", ", splitOn));
70+
71+
public Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(PlatformQuery query, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> map, string[] splitOn, CancellationToken cancellationToken = default) =>
72+
connection.QueryAsync(new CommandDefinition(query.QueryTemplate.RawSql, query.QueryTemplate.Parameters, cancellationToken: cancellationToken), map, string.Join(", ", splitOn));
73+
74+
public Task<IEnumerable<TReturn>> QueryAsync<TReturn>(PlatformQuery query, Type[] types, Func<object[], TReturn> map, string[] splitOn) =>
75+
connection.QueryAsync(query.QueryTemplate.RawSql, types, map, query.QueryTemplate.Parameters, splitOn: string.Join(", ", splitOn));
76+
5677
public Task<T?> QueryFirstOrDefaultAsync<T>(string sql, object? param = null, CancellationToken cancellationToken = default)
5778
=> connection.QueryFirstOrDefaultAsync<T>(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
5879

@@ -73,58 +94,54 @@ public Task<int> InsertAsync<T>(T entityToInsert, IDbTransaction? transaction =
7394

7495
public interface IDatabaseConnection : IDbConnection
7596
{
76-
/// <summary>
77-
/// Execute a query asynchronously using Task.
78-
/// </summary>
79-
/// <typeparam name="T">The type of results to return.</typeparam>
80-
/// <param name="sql">The SQL to execute for the query.</param>
81-
/// <param name="param">The parameters to pass, if any.</param>
82-
/// <param name="cancellationToken">The cancellation token for this command.</param>
83-
/// <returns>
84-
/// A sequence of data of <typeparamref name="T" />; if a basic type (int, string, etc) is queried then the data from
85-
/// the first column in assumed, otherwise an instance is
86-
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
87-
/// </returns>
97+
/// <inheritdoc cref="Dapper.SqlMapper.QueryAsync&lt;T&gt;(IDbConnection, CommandDefinition)" />
8898
Task<IEnumerable<T>> QueryAsync<T>(string sql, object? param = null, CancellationToken cancellationToken = default);
8999

100+
/// <inheritdoc cref="Dapper.SqlMapper.QueryAsync&lt;T&gt;(IDbConnection, CommandDefinition)" />
90101
Task<IEnumerable<T>> QueryAsync<T>(PlatformQuery query, CancellationToken cancellationToken = default);
91102

92-
/// <summary>
93-
/// Execute a single-row query asynchronously using Task.
94-
/// </summary>
95-
/// <typeparam name="T">The type of result to return.</typeparam>
96-
/// <param name="sql">The SQL to execute for the query.</param>
97-
/// <param name="param">The parameters to pass, if any.</param>
98-
/// <param name="cancellationToken">The cancellation token for this command.</param>
103+
/// <inheritdoc
104+
/// cref="Dapper.SqlMapper.QueryAsync&lt;TFirst,TSecond,TReturn&gt;(IDbConnection, CommandDefinition, Func&lt;TFirst,TSecond,TReturn&gt;, string)" />
105+
Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TReturn>(PlatformQuery query, Func<TFirst, TSecond, TReturn> map, string[] splitOn, CancellationToken cancellationToken = default);
106+
107+
/// <inheritdoc
108+
/// cref="Dapper.SqlMapper.QueryAsync&lt;TFirst,TSecond,TThird,TReturn&gt;(IDbConnection, CommandDefinition, Func&lt;TFirst,TSecond,TThird,TReturn&gt;, string)" />
109+
Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TReturn>(PlatformQuery query, Func<TFirst, TSecond, TThird, TReturn> map, string[] splitOn, CancellationToken cancellationToken = default);
110+
111+
/// <inheritdoc
112+
/// cref="Dapper.SqlMapper.QueryAsync&lt;TFirst,TSecond,TThird,TFourth,TReturn&gt;(IDbConnection, CommandDefinition, Func&lt;TFirst,TSecond,TThird,TFourth,TReturn&gt;, string)" />
113+
Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TReturn>(PlatformQuery query, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, string[] splitOn, CancellationToken cancellationToken = default);
114+
115+
/// <inheritdoc
116+
/// cref="Dapper.SqlMapper.QueryAsync&lt;TFirst,TSecond,TThird,TFourth,TFifth,TReturn&gt;(IDbConnection, CommandDefinition, Func&lt;TFirst,TSecond,TThird,TFourth,TFifth,TReturn&gt;, string)" />
117+
Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(PlatformQuery query, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, string[] splitOn, CancellationToken cancellationToken = default);
118+
119+
/// <inheritdoc
120+
/// cref="Dapper.SqlMapper.QueryAsync&lt;TFirst,TSecond,TThird,TFourth,TFifth,TSixth,TReturn&gt;(IDbConnection, CommandDefinition, Func&lt;TFirst,TSecond,TThird,TFourth,TFifth,TSixth,TReturn&gt;, string)" />
121+
Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(PlatformQuery query, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> map, string[] splitOn, CancellationToken cancellationToken = default);
122+
123+
/// <inheritdoc
124+
/// cref="Dapper.SqlMapper.QueryAsync&lt;TFirst,TSecond,TThird,TFourth,TFifth,TSixth,TSeventh,TReturn&gt;(IDbConnection, CommandDefinition, Func&lt;TFirst,TSecond,TThird,TFourth,TFifth,TSixth,TSeventh,TReturn&gt;, string)" />
125+
Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(PlatformQuery query, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> map, string[] splitOn, CancellationToken cancellationToken = default);
126+
127+
/// <inheritdoc
128+
/// cref="Dapper.SqlMapper.QueryAsync&lt;TReturn&gt;(IDbConnection, string, Type[], Func&lt;object[], TReturn&gt;, object?, IDbTransaction?, bool, string, int?, CommandType?)" />
129+
/// <remarks>ℹ No <see cref="CancellationToken" /> support (see https://github.com/DapperLib/Dapper/issues/2125).</remarks>
130+
Task<IEnumerable<TReturn>> QueryAsync<TReturn>(PlatformQuery query, Type[] types, Func<object[], TReturn> map, string[] splitOn);
131+
132+
/// <inheritdoc cref="Dapper.SqlMapper.QueryFirstOrDefaultAsync&lt;T&gt;(IDbConnection, CommandDefinition)" />
99133
Task<T?> QueryFirstOrDefaultAsync<T>(string sql, object? param = null, CancellationToken cancellationToken = default);
100134

135+
/// <inheritdoc cref="Dapper.SqlMapper.QueryFirstOrDefaultAsync&lt;T&gt;(IDbConnection, CommandDefinition)" />
101136
Task<T?> QueryFirstOrDefaultAsync<T>(PlatformQuery query, CancellationToken cancellationToken = default);
102137

103-
/// <summary>
104-
/// Execute a command asynchronously using Task.
105-
/// </summary>
106-
/// <param name="sql">The SQL to execute for this query.</param>
107-
/// <param name="param">The parameters to use for this query.</param>
108-
/// <param name="transaction">The transaction to use for this query.</param>
109-
/// <param name="cancellationToken">The cancellation token for this command.</param>
110-
/// <returns>The number of rows affected.</returns>
138+
/// <inheritdoc cref="Dapper.SqlMapper.ExecuteAsync(IDbConnection, CommandDefinition)" />
111139
Task<int> ExecuteAsync(string sql, object? param = null, IDbTransaction? transaction = null, CancellationToken cancellationToken = default);
112140

113-
/// <summary>
114-
/// Execute a single-row query asynchronously using Task.
115-
/// </summary>
116-
/// <typeparam name="T">The type of result to return.</typeparam>
117-
/// <param name="sql">The SQL to execute for the query.</param>
118-
/// <param name="param">The parameters to pass, if any.</param>
119-
/// <param name="cancellationToken">The cancellation token for this command.</param>
141+
/// <inheritdoc cref="Dapper.SqlMapper.QueryFirstAsync&lt;T&gt;(IDbConnection, CommandDefinition)" />
120142
Task<T> QueryFirstAsync<T>(string sql, object? param = null, CancellationToken cancellationToken = default);
121143

122-
/// <summary>
123-
/// Inserts an entity into table "Ts" asynchronously using Task and returns identity id.
124-
/// </summary>
125-
/// <typeparam name="T">The type being inserted.</typeparam>
126-
/// <param name="entityToInsert">Entity to insert</param>
127-
/// <param name="transaction">The transaction to run under, null (the default) if none</param>
128-
/// <returns>Identity of inserted entity</returns>
144+
/// <inheritdoc
145+
/// cref="Dapper.Contrib.Extensions.SqlMapperExtensions.InsertAsync&lt;T&gt;(IDbConnection, T, IDbTransaction, int?, ISqlAdapter)" />
129146
Task<int> InsertAsync<T>(T entityToInsert, IDbTransaction? transaction = null) where T : class;
130147
}

platform/src/abstractions/Platform.Sql/QueryBuilders/LocalAuthorityQueries.cs

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace Platform.Sql.QueryBuilders;
1+
using Platform.Domain;
2+
3+
namespace Platform.Sql.QueryBuilders;
24

35
public class LocalAuthorityQuery() : PlatformQuery(Sql)
46
{
@@ -8,4 +10,26 @@ public class LocalAuthorityQuery() : PlatformQuery(Sql)
810
public class LocalAuthorityStatisticalNeighbourQuery() : PlatformQuery(Sql)
911
{
1012
private const string Sql = "SELECT * FROM VW_LocalAuthorityStatisticalNeighbours /**where**/";
13+
}
14+
15+
public class LocalAuthorityCurrentFinancialQuery : PlatformQuery
16+
{
17+
public LocalAuthorityCurrentFinancialQuery(string dimension, string[] fields) : base(GetSql(dimension, fields))
18+
{
19+
foreach (var field in fields)
20+
{
21+
Select(field);
22+
}
23+
}
24+
25+
private static string GetSql(string dimension, string[] fields)
26+
{
27+
var select = fields.Length == 0 ? "*" : "/**select**/";
28+
return dimension switch
29+
{
30+
Dimensions.HighNeeds.Actuals => $"SELECT {select} FROM VW_LocalAuthorityFinancialDefaultCurrentActual /**where**/",
31+
Dimensions.HighNeeds.PerHead => $"SELECT {select} FROM VW_LocalAuthorityFinancialDefaultCurrentPerPopulation /**where**/",
32+
_ => throw new ArgumentOutOfRangeException(nameof(dimension), "Unknown dimension")
33+
};
34+
}
1135
}

platform/src/abstractions/Platform.Sql/QueryBuilders/PlatformQuery.cs

+9
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ public PlatformQuery WhereLaCodeEqual(string laCode)
8383
return this;
8484
}
8585

86+
public PlatformQuery WhereLaCodesIn(string[] laCodes)
87+
{
88+
const string sql = "LaCode IN @LaCodes";
89+
var parameters = new { LaCodes = laCodes };
90+
91+
Where(sql, parameters);
92+
return this;
93+
}
94+
8695
public PlatformQuery WhereCodeEqual(string code)
8796
{
8897
const string sql = "Code = @Code";

platform/src/abstractions/tests/Platform.Sql.Tests/Builders/PlatformQueryTests.cs

+16
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,22 @@ public void ShouldAddLaCodeEqualParameter()
137137
Assert.Equal(expectedSql, builder.QueryTemplate.RawSql);
138138
}
139139

140+
[Fact]
141+
public void ShouldAddLaCodeInParameter()
142+
{
143+
const string expectedParam = "LaCodes";
144+
var expectedValue = new[] { "12345", "12346" };
145+
var expectedSql = BuildExpectedQuery("WHERE LaCode IN @LaCodes");
146+
147+
var builder = new MockPlatformQuery().WhereLaCodesIn(expectedValue);
148+
var parameters = builder.QueryTemplate.Parameters.GetTemplateParameters(expectedParam);
149+
150+
Assert.Single(parameters);
151+
Assert.Contains(expectedParam, parameters.Keys);
152+
Assert.Equal(expectedValue, parameters[expectedParam]);
153+
Assert.Equal(expectedSql, builder.QueryTemplate.RawSql);
154+
}
155+
140156
[Fact]
141157
public void ShouldAddOverallPhaseEqualParameter()
142158
{

platform/src/apis/Platform.Api.Establishment/Features/LocalAuthorities/Services/LocalAuthoritiesService.cs

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Linq;
44
using System.Threading.Tasks;
55
using Microsoft.Extensions.DependencyInjection;
6-
using Microsoft.Extensions.Logging;
76
using Platform.Api.Establishment.Features.LocalAuthorities.Models;
87
using Platform.Api.Establishment.Features.LocalAuthorities.Requests;
98
using Platform.Domain;

platform/src/apis/Platform.Api.LocalAuthorityFinances/Features/HighNeeds/HighNeedsFeature.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public static class HighNeedsFeature
1313
public static IServiceCollection AddHighNeedsFeature(this IServiceCollection serviceCollection)
1414
{
1515
serviceCollection
16-
.AddSingleton<IHighNeedsService, HighNeedsStubService>();
16+
.AddSingleton<IHighNeedsService, HighNeedsService>();
1717

1818
serviceCollection
1919
.AddTransient<IValidator<HighNeedsParameters>, HighNeedsParametersValidator>()

platform/src/apis/Platform.Api.LocalAuthorityFinances/Features/HighNeeds/Models/HighNeeds.cs

+9-5
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
// ReSharper disable UnusedAutoPropertyAccessor.Global
33
namespace Platform.Api.LocalAuthorityFinances.Features.HighNeeds.Models;
44

5-
public record HighNeeds
5+
public record HighNeeds : HighNeedsBase
6+
{
7+
public HighNeedsAmount? HighNeedsAmount { get; set; }
8+
public TopFunding? Maintained { get; set; }
9+
public TopFunding? NonMaintained { get; set; }
10+
public PlaceFunding? PlaceFunding { get; set; }
11+
}
12+
13+
public record HighNeedsBase
614
{
715
public decimal? Total { get; set; }
8-
public HighNeedsAmount HighNeedsAmount { get; set; } = new();
9-
public TopFunding Maintained { get; set; } = new();
10-
public TopFunding NonMaintained { get; set; } = new();
11-
public PlaceFunding PlaceFunding { get; set; } = new();
1216
}

platform/src/apis/Platform.Api.LocalAuthorityFinances/Features/HighNeeds/Models/LocalAuthority.cs

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
// ReSharper disable UnusedAutoPropertyAccessor.Global
33
namespace Platform.Api.LocalAuthorityFinances.Features.HighNeeds.Models;
44

5-
public record LocalAuthority<T>
5+
public record LocalAuthority<T> : LocalAuthorityBase
66
{
7-
public string? Code { get; set; }
8-
public string? Name { get; set; }
97
public T? Outturn { get; set; }
108
public T? Budget { get; set; }
9+
}
10+
11+
public record LocalAuthorityBase
12+
{
13+
public string? Code { get; set; }
14+
public string? Name { get; set; }
1115
}

0 commit comments

Comments
 (0)