Compute nearby-brewery distance in SQL instead of client-side Haversine, and add the spatial index it relies on.

This commit is contained in:
Aaron Po
2026-08-27 02:40:58 -04:00
parent dab7aa2458
commit 7330eabebe
10 changed files with 244 additions and 121 deletions

View File

@@ -32,8 +32,13 @@ public record BreweryDto(
byte[]? RowVersion,
BreweryLocationDto? Location
);
/// <param name="DistanceMetres">
/// The distance, in metres, from a query's origin point, or <see langword="null"/> for queries with no
/// origin point (e.g. <c>GET /api/brewery/locations</c>).
/// </param>
public record SimplifiedBreweryDto(
Guid BreweryPostId,
string BreweryName,
BreweryLocationDto? Location
BreweryLocationDto? Location,
double? DistanceMetres
);

View File

@@ -68,7 +68,8 @@ public static class BreweryDtoMapper
AddressLine2: brewery.Location.AddressLine2,
PostalCode: brewery.Location.PostalCode,
Coordinates: brewery.Location.Coordinates
)
),
DistanceMetres: brewery.Distance?.DistanceMetres
);
}
}

View File

@@ -134,6 +134,7 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory)
City,
StateProvince,
Country,
DistanceRow,
BreweryPost
> (
"""
@@ -154,7 +155,8 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory)
sp.ISO3166_2 AS Iso31662,
co.CountryID,
co.CountryName,
co.ISO3166_1 AS Iso31661
co.ISO3166_1 AS Iso31661,
bpl.Coordinates.STDistance(@Origin) AS DistanceMetres
FROM dbo.BreweryPost bp
INNER JOIN dbo.BreweryPostLocation bpl ON bp.BreweryPostID = bpl.BreweryPostID
LEFT JOIN dbo.City c ON bpl.CityID = c.CityID
@@ -164,14 +166,15 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory)
AND bpl.Coordinates.STDistance(@Origin) <= @RangeInMetres
ORDER BY bpl.Coordinates.STDistance(@Origin) ASC
""",
MapBreweryRow,
(post, location, city, stateProvince, country, distance) =>
MapBreweryRowWithDistance(post, location, city, stateProvince, country, distance, coords),
param: new
{
coords.Latitude,
coords.Longitude,
RangeInMetres = rangeInMetres
},
splitOn: "BreweryPostLocationID,CityID,StateProvinceID,CountryID"
splitOn: "BreweryPostLocationID,CityID,StateProvinceID,CountryID,DistanceMetres"
);
}
@@ -583,4 +586,29 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory)
post.Location = location;
return post;
}
/// <summary>Dapper multi-mapping row for the <c>DistanceMetres</c> column computed by a range query.</summary>
private sealed class DistanceRow
{
public double DistanceMetres { get; set; }
}
/// <summary>
/// Composes a joined row via <see cref="MapBreweryRow" />, then attaches the DB-computed distance
/// from <paramref name="origin" /> as the result's <see cref="BreweryPost.Distance" />.
/// </summary>
private static BreweryPost MapBreweryRowWithDistance(
BreweryPost post,
BreweryPostLocation location,
City city,
StateProvince stateProvince,
Country country,
DistanceRow distance,
CoordinateData origin
)
{
BreweryPost mapped = MapBreweryRow(post, location, city, stateProvince, country);
mapped.Distance = new DistanceInformation(origin, distance.DistanceMetres);
return mapped;
}
}