Skip to content

Commit

Permalink
added dapper and category get and get all methods
Browse files Browse the repository at this point in the history
  • Loading branch information
akhadov committed Jun 23, 2024
1 parent 2a279e9 commit 20b9f28
Show file tree
Hide file tree
Showing 19 changed files with 169 additions and 59 deletions.
4 changes: 2 additions & 2 deletions src/Application/Abstractions/Data/IDbConnectionFactory.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System.Data;
using System.Data.Common;

namespace Application.Abstractions.Data;

public interface IDbConnectionFactory
{
IDbConnection GetOpenConnection();
ValueTask<DbConnection> OpenConnectionAsync();
}
32 changes: 32 additions & 0 deletions src/Application/Abstractions/Models/PaginatedList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.EntityFrameworkCore;

namespace Application.Abstractions.Models;
public class PaginatedList<T>
{
public IReadOnlyCollection<T> Items { get; }
public int PageNumber { get; }
public int TotalPages { get; }
public int TotalCount { get; }

public PaginatedList(IReadOnlyCollection<T> items, int count, int pageNumber, int pageSize)
{
PageNumber = pageNumber;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
TotalCount = count;
Items = items;
}

public bool HasPreviousPage => PageNumber > 1;

public bool HasNextPage => PageNumber < TotalPages;

public static async Task<PaginatedList<T>> CreateAsync(IQueryable<T> source, int pageNumber, int pageSize)
{
#pragma warning disable IDE0008 // Use explicit type
var count = await source.CountAsync();
var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
#pragma warning restore IDE0008 // Use explicit type

return new PaginatedList<T>(items, count, pageNumber, pageSize);
}
}
1 change: 1 addition & 0 deletions src/Application/Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<PackageReference Include="Dapper" Version="2.1.35" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.1" />
<PackageReference Include="MediatR" Version="12.3.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageReference Include="Serilog" Version="4.0.0" />
</ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions src/Application/Categories/Get/CategoryResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
namespace Application.Categories.Get;
public sealed record CategoryResponse(Guid Id, string Name);
4 changes: 4 additions & 0 deletions src/Application/Categories/Get/GetCategoryQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using Application.Abstractions.Messaging;

namespace Application.Categories.Get;
public sealed record GetCategoryQuery(Guid CategoryId) : IQuery<CategoryResponse>;
34 changes: 34 additions & 0 deletions src/Application/Categories/Get/GetCategoryQueryHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Data.Common;
using Application.Abstractions.Data;
using Application.Abstractions.Messaging;
using Dapper;
using Domain.Categories;
using SharedKernel;

namespace Application.Categories.Get;
internal sealed class GetCategoryQueryHandler(IDbConnectionFactory dbConnectionFactory)
: IQueryHandler<GetCategoryQuery, CategoryResponse>
{
public async Task<Result<CategoryResponse>> Handle(GetCategoryQuery request, CancellationToken cancellationToken)
{
await using DbConnection connection = await dbConnectionFactory.OpenConnectionAsync();

const string sql =
$"""
SELECT
id AS {nameof(CategoryResponse.Id)},
name AS {nameof(CategoryResponse.Name)}
FROM categories
WHERE id = @CategoryId
""";

CategoryResponse? category = await connection.QuerySingleOrDefaultAsync<CategoryResponse>(sql, request);

if (category is null)
{
return Result.Failure<CategoryResponse>(CategoryErrors.NotFound(request.CategoryId));
}

return category;
}
}
6 changes: 6 additions & 0 deletions src/Application/Categories/GetAll/GetCategoriesQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Application.Abstractions.Messaging;
using Application.Abstractions.Models;
using Application.Categories.Get;

namespace Application.Categories.GetAll;
public sealed record GetCategoriesQuery(int PageNumber, int PageSize) : IQuery<PaginatedList<CategoryResponse>>;
44 changes: 44 additions & 0 deletions src/Application/Categories/GetAll/GetCategoriesQueryHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Data.Common;
using Application.Abstractions.Data;
using Application.Abstractions.Messaging;
using Application.Abstractions.Models;
using Application.Categories.Get;
using Dapper;
using SharedKernel;

namespace Application.Categories.GetAll;
internal sealed class GetCategoriesQueryHandler(IDbConnectionFactory dbConnectionFactory)
: IQueryHandler<GetCategoriesQuery, PaginatedList<CategoryResponse>>
{
public async Task<Result<PaginatedList<CategoryResponse>>> Handle(GetCategoriesQuery request, CancellationToken cancellationToken)
{
await using DbConnection connection = await dbConnectionFactory.OpenConnectionAsync();

const string countSql = @"
SELECT COUNT(*)
FROM categories
";

const string sql = @"
SELECT
id AS Id,
name AS Name
FROM categories
ORDER BY id
OFFSET @Offset ROWS
FETCH NEXT @PageSize ROWS ONLY
";

int totalCount = await connection.ExecuteScalarAsync<int>(countSql);

#pragma warning disable IDE0008 // Use explicit type
#pragma warning disable IDE0037 // Use inferred member name
var categories = await connection.QueryAsync<CategoryResponse>(sql, new { Offset = (request.PageNumber - 1) * request.PageSize, PageSize = request.PageSize });
#pragma warning restore IDE0037 // Use inferred member name
#pragma warning restore IDE0008 // Use explicit type

var paginatedCategories = new PaginatedList<CategoryResponse>(categories.ToList(), totalCount, request.PageNumber, request.PageSize);

return Result.Success(paginatedCategories);
}
}
7 changes: 0 additions & 7 deletions src/Application/Categories/GetById/CategoryResponse.cs

This file was deleted.

4 changes: 0 additions & 4 deletions src/Application/Categories/GetById/GetCategoryByIdQuery.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Data;
using System.Data.Common;
using Application.Abstractions.Data;
using Application.Abstractions.Messaging;
using Dapper;
Expand All @@ -23,7 +23,7 @@ FROM users u
WHERE u.id = @Email
""";

using IDbConnection connection = factory.GetOpenConnection();
await using DbConnection connection = await factory.OpenConnectionAsync();

UserResponse? user = await connection.QueryFirstOrDefaultAsync<UserResponse>(
sql,
Expand Down
4 changes: 2 additions & 2 deletions src/Application/Users/GetById/GetUserByIdQueryHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Data;
using System.Data.Common;
using Application.Abstractions.Data;
using Application.Abstractions.Messaging;
using Dapper;
Expand All @@ -23,7 +23,7 @@ FROM users u
WHERE u.id = @UserId
""";

using IDbConnection connection = factory.GetOpenConnection();
await using DbConnection connection = await factory.OpenConnectionAsync();

UserResponse? user = await connection.QueryFirstOrDefaultAsync<UserResponse>(
sql,
Expand Down
9 changes: 4 additions & 5 deletions src/Infrastructure/Database/DbConnectionFactory.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
using System.Data;
using System.Data.Common;
using Application.Abstractions.Data;
using Npgsql;

namespace Infrastructure.Database;

internal sealed class DbConnectionFactory(NpgsqlDataSource dataSource) : IDbConnectionFactory
{
public IDbConnection GetOpenConnection()
public async ValueTask<DbConnection> OpenConnectionAsync()
{
NpgsqlConnection connection = dataSource.OpenConnection();

return connection;
return await dataSource.OpenConnectionAsync();
}
}

4 changes: 2 additions & 2 deletions src/Infrastructure/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ private static IServiceCollection AddDatabase(this IServiceCollection services,
options => options
.UseNpgsql(connectionString, npgsqlOptions =>
npgsqlOptions.MigrationsHistoryTable(HistoryRepository.DefaultTableName, Schemas.Default))
.UseSnakeCaseNamingConvention());
//.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
.UseSnakeCaseNamingConvention()
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));

services.AddScoped<IUnitOfWork>(sp => sp.GetRequiredService<ApplicationDbContext>());

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

This file was deleted.

8 changes: 4 additions & 4 deletions src/Web.Api/Endpoints/Categories/Get.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Application.Categories.GetById;
using Application.Categories.Get;
using MediatR;
using SharedKernel;
using Web.Api.Extensions;
Expand All @@ -15,11 +15,11 @@ public void MapEndpoint(IEndpointRouteBuilder app)
ISender sender,
CancellationToken cancellationToken) =>
{
var query = new GetCategoryByIdQuery(categoryId);
var query = new GetCategoryQuery(categoryId);
Result result = await sender.Send(query, cancellationToken);
Result<CategoryResponse> result = await sender.Send(query, cancellationToken);
return result.Match(Results.NoContent, CustomResults.Problem);
return result.Match(Results.Ok, CustomResults.Problem);
})
.WithTags(Tags.Categories);
}
Expand Down
29 changes: 29 additions & 0 deletions src/Web.Api/Endpoints/Categories/GetAll.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Application.Abstractions.Models;
using Application.Categories.Get;
using Application.Categories.GetAll;
using MediatR;
using SharedKernel;
using Web.Api.Extensions;
using Web.Api.Infrastructure;

namespace Web.Api.Endpoints.Categories;

internal sealed class GetAll : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder app)
{
app.MapGet("categories", async (
int pageNumber,
int pageSize,
ISender sender,
CancellationToken cancellationToken) =>
{
var query = new GetCategoriesQuery(pageNumber, pageSize);
Result<PaginatedList<CategoryResponse>> result = await sender.Send(query, cancellationToken);
return result.Match(Results.Ok, CustomResults.Problem);
})
.WithTags(Tags.Categories);
}
}

0 comments on commit 20b9f28

Please sign in to comment.