mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-14 17:54:42 +00:00
refactor(database): update seed pipeline to use dapper (#277)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Commands.CreateCity;
|
||||
|
||||
public record CreateCityCommand(string CityName, Guid StateProvinceId) : IRequest<Guid>;
|
||||
@@ -0,0 +1,11 @@
|
||||
using Features.Locations.Repository;
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Commands.CreateCity;
|
||||
|
||||
public class CreateCityHandler(ILocationRepository repository)
|
||||
: IRequestHandler<CreateCityCommand, Guid>
|
||||
{
|
||||
public Task<Guid> Handle(CreateCityCommand request, CancellationToken cancellationToken) =>
|
||||
repository.CreateCityAsync(request.CityName, request.StateProvinceId);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Commands.CreateCountry;
|
||||
|
||||
public record CreateCountryCommand(string CountryName, string IsoCode) : IRequest<Guid>;
|
||||
@@ -0,0 +1,11 @@
|
||||
using Features.Locations.Repository;
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Commands.CreateCountry;
|
||||
|
||||
public class CreateCountryHandler(ILocationRepository repository)
|
||||
: IRequestHandler<CreateCountryCommand, Guid>
|
||||
{
|
||||
public Task<Guid> Handle(CreateCountryCommand request, CancellationToken cancellationToken) =>
|
||||
repository.CreateCountryAsync(request.CountryName, request.IsoCode);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Commands.CreateStateProvince;
|
||||
|
||||
public record CreateStateProvinceCommand(string StateProvinceName, string IsoCode, Guid CountryId)
|
||||
: IRequest<Guid>;
|
||||
@@ -0,0 +1,18 @@
|
||||
using Features.Locations.Repository;
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Commands.CreateStateProvince;
|
||||
|
||||
public class CreateStateProvinceHandler(ILocationRepository repository)
|
||||
: IRequestHandler<CreateStateProvinceCommand, Guid>
|
||||
{
|
||||
public Task<Guid> Handle(
|
||||
CreateStateProvinceCommand request,
|
||||
CancellationToken cancellationToken
|
||||
) =>
|
||||
repository.CreateStateProvinceAsync(
|
||||
request.StateProvinceName,
|
||||
request.IsoCode,
|
||||
request.CountryId
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using Features.Locations.Dtos;
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Commands.GetOrCreateCity;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves <see cref="City" /> to a City ID, creating the Country, StateProvince, and/or City rows
|
||||
/// if any part of the chain doesn't exist yet. Sent by other features' handlers (e.g. brewery
|
||||
/// creation) rather than bound from a request directly, since this feature exposes no controller of
|
||||
/// its own.
|
||||
/// </summary>
|
||||
public record GetOrCreateCityCommand(CityLocation City) : IRequest<Guid>;
|
||||
@@ -1,12 +0,0 @@
|
||||
using Features.Locations.Repository;
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Commands.GetOrCreateCity;
|
||||
|
||||
/// <summary>Handles <see cref="GetOrCreateCityCommand" /> by delegating to <see cref="ILocationRepository" />.</summary>
|
||||
public class GetOrCreateCityHandler(ILocationRepository repository)
|
||||
: IRequestHandler<GetOrCreateCityCommand, Guid>
|
||||
{
|
||||
public Task<Guid> Handle(GetOrCreateCityCommand request, CancellationToken cancellationToken) =>
|
||||
repository.GetOrCreateCityIdAsync(request.City);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using Features.Locations.Dtos;
|
||||
using Features.Locations.Queries.GetAllCities;
|
||||
using Features.Locations.Queries.GetCityById;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Shared.Contracts;
|
||||
|
||||
namespace Features.Locations.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize(AuthenticationSchemes = "JWT")]
|
||||
public class CityController(IMediator mediator) : ControllerBase
|
||||
{
|
||||
[AllowAnonymous]
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<ActionResult<ResponseBody<CityDto>>> GetById(Guid id)
|
||||
{
|
||||
CityDto? city = await mediator.Send(new GetCityByIdQuery(id));
|
||||
if (city is null)
|
||||
return NotFound(new ResponseBody { Message = $"City with ID {id} not found." });
|
||||
|
||||
return Ok(
|
||||
new ResponseBody<CityDto> { Message = "City retrieved successfully.", Payload = city }
|
||||
);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<ResponseBody<IEnumerable<CityDto>>>> GetAll(
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] int? offset
|
||||
)
|
||||
{
|
||||
IEnumerable<CityDto> cities = await mediator.Send(new GetAllCitiesQuery(limit, offset));
|
||||
return Ok(
|
||||
new ResponseBody<IEnumerable<CityDto>>
|
||||
{
|
||||
Message = "Cities retrieved successfully.",
|
||||
Payload = cities,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,8 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Features.Locations.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Registers the services used by the Features.Locations slice.
|
||||
/// </summary>
|
||||
public static class FeaturesLocationsServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers <see cref="ILocationRepository" /> and its Dapper-based implementation.
|
||||
/// </summary>
|
||||
/// <returns>The same <paramref name="services" /> instance, for chaining.</returns>
|
||||
public static IServiceCollection AddFeaturesLocations(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<ILocationRepository, LocationRepository>();
|
||||
|
||||
11
web/backend/Features/Features.Locations/Dtos/CityDto.cs
Normal file
11
web/backend/Features/Features.Locations/Dtos/CityDto.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Features.Locations.Dtos;
|
||||
|
||||
public record CityDto(
|
||||
Guid CityId,
|
||||
string CityName,
|
||||
string StateProvinceName,
|
||||
string StateProvinceCode,
|
||||
string CountryName,
|
||||
string CountryCode,
|
||||
int BreweryCount
|
||||
);
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace Features.Locations.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// Describes a city's location for resolution by <c>ILocationRepository</c>, including the
|
||||
/// state/province and country it belongs to.
|
||||
/// </summary>
|
||||
public sealed record CityLocation(
|
||||
string CityName,
|
||||
string StateProvinceName,
|
||||
string StateProvinceIsoCode,
|
||||
string CountryName,
|
||||
string CountryIsoCode
|
||||
);
|
||||
@@ -0,0 +1,14 @@
|
||||
using Features.Locations.Dtos;
|
||||
using Features.Locations.Repository;
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Queries.GetAllCities;
|
||||
|
||||
public class GetAllCitiesHandler(ILocationRepository repository)
|
||||
: IRequestHandler<GetAllCitiesQuery, IEnumerable<CityDto>>
|
||||
{
|
||||
public Task<IEnumerable<CityDto>> Handle(
|
||||
GetAllCitiesQuery request,
|
||||
CancellationToken cancellationToken
|
||||
) => repository.GetAllCitiesAsync(request.Limit, request.Offset);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using Features.Locations.Dtos;
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Queries.GetAllCities;
|
||||
|
||||
public record GetAllCitiesQuery(int? Limit, int? Offset) : IRequest<IEnumerable<CityDto>>;
|
||||
@@ -0,0 +1,10 @@
|
||||
using Features.Locations.Repository;
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Queries.GetCity;
|
||||
|
||||
public class GetCityHandler(ILocationRepository repository) : IRequestHandler<GetCityQuery, Guid?>
|
||||
{
|
||||
public Task<Guid?> Handle(GetCityQuery request, CancellationToken cancellationToken) =>
|
||||
repository.GetCityIdAsync(request.CityName, request.StateProvinceIsoCode);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Queries.GetCity;
|
||||
|
||||
public record GetCityQuery(string CityName, string StateProvinceIsoCode) : IRequest<Guid?>;
|
||||
@@ -0,0 +1,12 @@
|
||||
using Features.Locations.Dtos;
|
||||
using Features.Locations.Repository;
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Queries.GetCityById;
|
||||
|
||||
public class GetCityByIdHandler(ILocationRepository repository)
|
||||
: IRequestHandler<GetCityByIdQuery, CityDto?>
|
||||
{
|
||||
public Task<CityDto?> Handle(GetCityByIdQuery request, CancellationToken cancellationToken) =>
|
||||
repository.GetCityByIdAsync(request.CityId);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using Features.Locations.Dtos;
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Queries.GetCityById;
|
||||
|
||||
public record GetCityByIdQuery(Guid CityId) : IRequest<CityDto?>;
|
||||
@@ -0,0 +1,11 @@
|
||||
using Features.Locations.Repository;
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Queries.GetCountry;
|
||||
|
||||
public class GetCountryHandler(ILocationRepository repository)
|
||||
: IRequestHandler<GetCountryQuery, Guid?>
|
||||
{
|
||||
public Task<Guid?> Handle(GetCountryQuery request, CancellationToken cancellationToken) =>
|
||||
repository.GetCountryIdAsync(request.IsoCode);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Queries.GetCountry;
|
||||
|
||||
public record GetCountryQuery(string IsoCode) : IRequest<Guid?>;
|
||||
@@ -0,0 +1,11 @@
|
||||
using Features.Locations.Repository;
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Queries.GetStateProvince;
|
||||
|
||||
public class GetStateProvinceHandler(ILocationRepository repository)
|
||||
: IRequestHandler<GetStateProvinceQuery, Guid?>
|
||||
{
|
||||
public Task<Guid?> Handle(GetStateProvinceQuery request, CancellationToken cancellationToken) =>
|
||||
repository.GetStateProvinceIdAsync(request.IsoCode);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Locations.Queries.GetStateProvince;
|
||||
|
||||
public record GetStateProvinceQuery(string IsoCode) : IRequest<Guid?>;
|
||||
@@ -2,15 +2,21 @@ using Features.Locations.Dtos;
|
||||
|
||||
namespace Features.Locations.Repository;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the Country/StateProvince/City location hierarchy shared by features that attach a
|
||||
/// location to a record (e.g. brewery posts, and eventually user accounts).
|
||||
/// </summary>
|
||||
public interface ILocationRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves <paramref name="location" /> to a City ID, creating the Country, StateProvince,
|
||||
/// and/or City rows if any part of the chain doesn't exist yet.
|
||||
/// </summary>
|
||||
Task<Guid> GetOrCreateCityIdAsync(CityLocation location);
|
||||
Task<CityDto?> GetCityByIdAsync(Guid cityId);
|
||||
|
||||
Task<IEnumerable<CityDto>> GetAllCitiesAsync(int? limit, int? offset);
|
||||
|
||||
Task<Guid?> GetCountryIdAsync(string isoCode);
|
||||
|
||||
Task<Guid> CreateCountryAsync(string countryName, string isoCode);
|
||||
|
||||
Task<Guid?> GetStateProvinceIdAsync(string isoCode);
|
||||
|
||||
Task<Guid> CreateStateProvinceAsync(string stateProvinceName, string isoCode, Guid countryId);
|
||||
|
||||
Task<Guid?> GetCityIdAsync(string cityName, string stateProvinceIsoCode);
|
||||
|
||||
Task<Guid> CreateCityAsync(string cityName, Guid stateProvinceId);
|
||||
}
|
||||
|
||||
@@ -1,153 +1,61 @@
|
||||
using System.Data.Common;
|
||||
using Dapper;
|
||||
using Database.Connection;
|
||||
using Domain.Exceptions;
|
||||
using Features.Locations.Dtos;
|
||||
|
||||
namespace Features.Locations.Repository;
|
||||
|
||||
/// <summary>
|
||||
/// Dapper-based implementation of <see cref="ILocationRepository" />.
|
||||
/// </summary>
|
||||
public class LocationRepository(ISqlConnectionFactory connectionFactory)
|
||||
: DapperRepository(connectionFactory),
|
||||
ILocationRepository
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// Not fully race-safe under concurrent callers for the same new city (neither was the stored
|
||||
/// procedure this replaces, since <c>City</c> has no unique constraint on name+state): a
|
||||
/// concurrent duplicate insert can succeed rather than being rejected. Country/StateProvince
|
||||
/// creation guards against this the same way the original stored procedures did, via an
|
||||
/// existence pre-check immediately before the insert.
|
||||
/// </remarks>
|
||||
public async Task<Guid> GetOrCreateCityIdAsync(CityLocation location)
|
||||
private const string CityDtoSelect = """
|
||||
SELECT
|
||||
c.CityID AS CityId,
|
||||
c.CityName,
|
||||
sp.StateProvinceName,
|
||||
sp.ISO3166_2 AS StateProvinceCode,
|
||||
co.CountryName,
|
||||
co.ISO3166_1 AS CountryCode,
|
||||
COUNT(bpl.BreweryPostLocationID) AS BreweryCount
|
||||
FROM Geolocation.City c
|
||||
INNER JOIN Geolocation.StateProvince sp ON sp.StateProvinceID = c.StateProvinceID
|
||||
INNER JOIN Geolocation.Country co ON co.CountryID = sp.CountryID
|
||||
LEFT JOIN Brewery.BreweryPostLocation bpl ON bpl.CityID = c.CityID
|
||||
""";
|
||||
|
||||
private const string CityDtoGroupBy = """
|
||||
GROUP BY c.CityID, c.CityName, sp.StateProvinceName, sp.ISO3166_2, co.CountryName, co.ISO3166_1
|
||||
""";
|
||||
|
||||
public async Task<CityDto?> GetCityByIdAsync(Guid cityId)
|
||||
{
|
||||
await EnsureCountryExistsAsync(location.CountryName, location.CountryIsoCode);
|
||||
await EnsureStateProvinceExistsAsync(
|
||||
location.StateProvinceName,
|
||||
location.StateProvinceIsoCode,
|
||||
location.CountryIsoCode
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
return await connection.QuerySingleOrDefaultAsync<CityDto>(
|
||||
$"""
|
||||
{CityDtoSelect}
|
||||
WHERE c.CityID = @CityId
|
||||
{CityDtoGroupBy}
|
||||
""",
|
||||
new { CityId = cityId }
|
||||
);
|
||||
|
||||
Guid? cityId = await GetCityIdAsync(location.CityName, location.StateProvinceIsoCode);
|
||||
if (cityId is not null)
|
||||
return cityId.Value;
|
||||
|
||||
Guid stateProvinceId =
|
||||
await GetStateProvinceIdAsync(location.StateProvinceIsoCode)
|
||||
?? throw new NotFoundException(
|
||||
$"State/province '{location.StateProvinceIsoCode}' not found."
|
||||
);
|
||||
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
|
||||
bool cityExists =
|
||||
await connection.ExecuteScalarAsync<int?>(
|
||||
"""
|
||||
SELECT 1
|
||||
FROM Geolocation.City
|
||||
WHERE CityName = @CityName AND StateProvinceID = @StateProvinceId
|
||||
""",
|
||||
new { location.CityName, StateProvinceId = stateProvinceId }
|
||||
)
|
||||
is not null;
|
||||
|
||||
if (!cityExists)
|
||||
await connection.ExecuteAsync(
|
||||
"""
|
||||
INSERT INTO Geolocation.City (StateProvinceID, CityName)
|
||||
VALUES (@StateProvinceId, @CityName)
|
||||
""",
|
||||
new { StateProvinceId = stateProvinceId, location.CityName }
|
||||
);
|
||||
|
||||
return await GetCityIdAsync(location.CityName, location.StateProvinceIsoCode)
|
||||
?? throw new InvalidOperationException(
|
||||
$"City '{location.CityName}' was not found after creation."
|
||||
);
|
||||
}
|
||||
|
||||
private async Task EnsureCountryExistsAsync(string countryName, string isoCode)
|
||||
public async Task<IEnumerable<CityDto>> GetAllCitiesAsync(int? limit, int? offset)
|
||||
{
|
||||
if (await GetCountryIdAsync(isoCode) is not null)
|
||||
return;
|
||||
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
|
||||
bool exists =
|
||||
await connection.ExecuteScalarAsync<int?>(
|
||||
"""
|
||||
SELECT 1
|
||||
FROM Geolocation.Country
|
||||
WHERE ISO3166_1 = @ISO3166_1
|
||||
""",
|
||||
new { ISO3166_1 = isoCode }
|
||||
)
|
||||
is not null;
|
||||
|
||||
if (!exists)
|
||||
// A concurrent caller may create this country between the check above and here; that
|
||||
// race is tolerated (the resulting duplicate row, if any, is out of scope for this method).
|
||||
await connection.ExecuteAsync(
|
||||
"""
|
||||
INSERT INTO Geolocation.Country (CountryName, ISO3166_1)
|
||||
VALUES (@CountryName, @ISO3166_1)
|
||||
""",
|
||||
new { CountryName = countryName, ISO3166_1 = isoCode }
|
||||
);
|
||||
return await connection.QueryAsync<CityDto>(
|
||||
$"""
|
||||
{CityDtoSelect}
|
||||
{CityDtoGroupBy}
|
||||
ORDER BY c.CityName
|
||||
OFFSET @Offset ROWS FETCH NEXT @Limit ROWS ONLY
|
||||
""",
|
||||
new { Offset = offset ?? 0, Limit = limit ?? int.MaxValue }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures a StateProvince row exists for <paramref name="isoCode" />.
|
||||
/// </summary>
|
||||
/// <exception cref="NotFoundException">
|
||||
/// Thrown when no Country exists for <paramref name="countryIsoCode" />. Not expected in normal
|
||||
/// operation, since callers ensure the Country exists first — retained as a defensive translation
|
||||
/// in case of a race between concurrent callers.
|
||||
/// </exception>
|
||||
private async Task EnsureStateProvinceExistsAsync(
|
||||
string stateProvinceName,
|
||||
string isoCode,
|
||||
string countryIsoCode
|
||||
)
|
||||
{
|
||||
if (await GetStateProvinceIdAsync(isoCode) is not null)
|
||||
return;
|
||||
|
||||
Guid countryId =
|
||||
await GetCountryIdAsync(countryIsoCode)
|
||||
?? throw new NotFoundException($"Country '{countryIsoCode}' not found.");
|
||||
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
|
||||
bool exists =
|
||||
await connection.ExecuteScalarAsync<int?>(
|
||||
"""
|
||||
SELECT 1
|
||||
FROM Geolocation.StateProvince
|
||||
WHERE ISO3166_2 = @ISO3166_2
|
||||
""",
|
||||
new { ISO3166_2 = isoCode }
|
||||
)
|
||||
is not null;
|
||||
|
||||
if (!exists)
|
||||
await connection.ExecuteAsync(
|
||||
"""
|
||||
INSERT INTO Geolocation.StateProvince (StateProvinceName, ISO3166_2, CountryID)
|
||||
VALUES (@StateProvinceName, @ISO3166_2, @CountryId)
|
||||
""",
|
||||
new
|
||||
{
|
||||
StateProvinceName = stateProvinceName,
|
||||
ISO3166_2 = isoCode,
|
||||
CountryId = countryId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async Task<Guid?> GetCountryIdAsync(string isoCode)
|
||||
public async Task<Guid?> GetCountryIdAsync(string isoCode)
|
||||
{
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
return await connection.ExecuteScalarAsync<Guid?>(
|
||||
@@ -160,7 +68,20 @@ public class LocationRepository(ISqlConnectionFactory connectionFactory)
|
||||
);
|
||||
}
|
||||
|
||||
private async Task<Guid?> GetStateProvinceIdAsync(string isoCode)
|
||||
public async Task<Guid> CreateCountryAsync(string countryName, string isoCode)
|
||||
{
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
return await connection.ExecuteScalarAsync<Guid>(
|
||||
"""
|
||||
INSERT INTO Geolocation.Country (CountryName, ISO3166_1)
|
||||
OUTPUT INSERTED.CountryID
|
||||
VALUES (@CountryName, @ISO3166_1)
|
||||
""",
|
||||
new { CountryName = countryName, ISO3166_1 = isoCode }
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<Guid?> GetStateProvinceIdAsync(string isoCode)
|
||||
{
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
return await connection.ExecuteScalarAsync<Guid?>(
|
||||
@@ -173,7 +94,29 @@ public class LocationRepository(ISqlConnectionFactory connectionFactory)
|
||||
);
|
||||
}
|
||||
|
||||
private async Task<Guid?> GetCityIdAsync(string cityName, string stateProvinceIsoCode)
|
||||
public async Task<Guid> CreateStateProvinceAsync(
|
||||
string stateProvinceName,
|
||||
string isoCode,
|
||||
Guid countryId
|
||||
)
|
||||
{
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
return await connection.ExecuteScalarAsync<Guid>(
|
||||
"""
|
||||
INSERT INTO Geolocation.StateProvince (StateProvinceName, ISO3166_2, CountryID)
|
||||
OUTPUT INSERTED.StateProvinceID
|
||||
VALUES (@StateProvinceName, @ISO3166_2, @CountryId)
|
||||
""",
|
||||
new
|
||||
{
|
||||
StateProvinceName = stateProvinceName,
|
||||
ISO3166_2 = isoCode,
|
||||
CountryId = countryId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<Guid?> GetCityIdAsync(string cityName, string stateProvinceIsoCode)
|
||||
{
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
return await connection.ExecuteScalarAsync<Guid?>(
|
||||
@@ -186,4 +129,17 @@ public class LocationRepository(ISqlConnectionFactory connectionFactory)
|
||||
new { CityName = cityName, StateProvinceCode = stateProvinceIsoCode }
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<Guid> CreateCityAsync(string cityName, Guid stateProvinceId)
|
||||
{
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
return await connection.ExecuteScalarAsync<Guid>(
|
||||
"""
|
||||
INSERT INTO Geolocation.City (StateProvinceID, CityName)
|
||||
OUTPUT INSERTED.CityID
|
||||
VALUES (@StateProvinceId, @CityName)
|
||||
""",
|
||||
new { StateProvinceId = stateProvinceId, CityName = cityName }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user