Compare commits

...
4 Commits
Author SHA1 Message Date
Dmitrii Kollerov 165511b57d add repository working with stored procedures 2026-09-27 13:52:01 +07:00
Dmitrii Kollerov 22451db209 verbosify bad desision 2026-09-25 08:26:41 +03:00
Dmitrii Kollerov c76e42d89d add gitignore 2026-09-25 08:06:54 +03:00
Dmitrii Kollerov 68da43a673 init 2026-09-25 08:05:46 +03:00
34 changed files with 1096 additions and 2 deletions
+2
View File
@@ -0,0 +1,2 @@
bin/
obj/
+33
View File
@@ -0,0 +1,33 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/bin/Debug/net10.0/example-csharp-app.dll",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}
+41
View File
@@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/example-csharp-app.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/example-csharp-app.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/example-csharp-app.csproj"
],
"problemMatcher": "$msCompile"
}
]
}
+63
View File
@@ -0,0 +1,63 @@
using System;
using example_csharp_app.Interfaces;
using example_csharp_app.Models.Mapper;
using example_csharp_app.Models.Request;
using example_csharp_app.Models.Response;
namespace example_csharp_app.Aggregators;
public class BookAggregator : IBookAggregator
{
private readonly IBookService _bookService;
public BookAggregator(IBookService bookService)
{
_bookService = bookService;
}
public async Task<BookOperationResponse> AddBookAsync(BookCreateRequest request)
{
var result = await _bookService.AddBookAsync(request.MapToModel());
return new BookOperationResponse()
{
IsSuccess = result > 0
};
}
public async Task<BookSingleResponse> GetBookAsync(Guid bookId)
{
var book = await _bookService.GetBookAsync(bookId);
return new BookSingleResponse()
{
Book = book
};
}
public async Task<BookListResponse> GetBookListAsync(BookFilterRequest request)
{
// should make separate filter object, but feel lazy
var bookList = await _bookService.GetBooksAsync(request);
return new BookListResponse()
{
Books = bookList
};
}
public async Task<BookOperationResponse> RemoveBookAsync(Guid bookId)
{
var result = await _bookService.RemoveBookAsync(bookId);
return new BookOperationResponse()
{
IsSuccess = result > 0
};
}
public async Task<BookOperationResponse> UpdateBookAsync(BookUpdateRequest request)
{
var result = await _bookService.UpdateBookAsync(request.Id, request.MapToModel());
return new BookOperationResponse()
{
IsSuccess = result > 0
};
}
}
+60
View File
@@ -0,0 +1,60 @@
using example_csharp_app.Interfaces;
using example_csharp_app.Models.Request;
using example_csharp_app.Models.Response;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
namespace example_csharp_app.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BookController : ControllerBase
{
private readonly IBookAggregator _bookAggregator;
public BookController(IBookAggregator bookAggregator)
{
_bookAggregator = bookAggregator;
}
[SwaggerResponse(200, "Получить список книг", typeof(BookListResponse))]
[HttpGet()]
public async Task<IActionResult> GetList([FromQuery]BookFilterRequest request)
{
var bookList = await _bookAggregator.GetBookListAsync(request);
return Ok(bookList);
}
[SwaggerResponse(200, "Получить список книг", typeof(BookSingleResponse))]
[HttpGet("{bookId}")]
public async Task<IActionResult> GetBook([FromRoute] Guid bookId)
{
var result = await _bookAggregator.GetBookAsync(bookId);
return Ok(result);
}
[SwaggerResponse(200, "Добавить книгу", typeof(BookOperationResponse))]
[HttpPost()]
public async Task<IActionResult> AddBook([FromBody]BookCreateRequest request)
{
var result = await _bookAggregator.AddBookAsync(request);
return Ok(result);
}
[SwaggerResponse(200, "Обновить книгу", typeof(BookOperationResponse))]
[HttpPut()]
public async Task<IActionResult> UpdateBook([FromBody]BookUpdateRequest request)
{
var result = await _bookAggregator.UpdateBookAsync(request);
return Ok(result);
}
[SwaggerResponse(200, "Удалить книгу", typeof(BookOperationResponse))]
[HttpDelete("{bookId}")]
public async Task<IActionResult> RemoveBook([FromRoute]Guid bookId)
{
var result = await _bookAggregator.RemoveBookAsync(bookId);
return Ok(result);
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using example_csharp_app.Dto;
using Microsoft.EntityFrameworkCore;
using System;
namespace example_app.Database.Connector;
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
public DbSet<BookDto> Books { set; get; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<BookDto>()
.ToTable("Books")
.HasKey(x => x.Id);
builder.Entity<BookDto>()
.Property(x => x.Author)
.IsRequired();
builder.Entity<BookDto>()
.Property(x => x.Title)
.IsRequired();
builder.Entity<BookDto>()
.Property(x => x.YearPublished)
.IsRequired();
builder.Entity<BookDto>()
.Property(x => x.BookIndex)
.IsRequired();
builder.Entity<BookDto>()
.Property(x => x.BookIndex)
.HasColumnType("xml");
}
}
+14
View File
@@ -0,0 +1,14 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace example_csharp_app.Dto;
public class BookDto
{
// Для описания модели использовал fluent api в AppDbContext
public Guid Id { set; get; }
public string Title { set; get; } = string.Empty;
public string Author { set; get; } = string.Empty;
public DateTime YearPublished { set; get; }
public string BookIndex { set; get; } = string.Empty;
}
+28
View File
@@ -0,0 +1,28 @@
using System;
namespace example_csharp_app;
public class ErrorLogMiddleware
{
private readonly RequestDelegate _next;
public ErrorLogMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext, IConfiguration configuration)
{
try
{
await _next(httpContext);
return;
}
catch (Exception ex)
{
// todo: logme
Console.WriteLine(ex.Message);
httpContext.Response.StatusCode = 500;
}
}
}
+49
View File
@@ -0,0 +1,49 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Windows.Markup;
using System.Xml;
namespace example_csharp_app.Filters;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public class XmlAttribute : ValidationAttribute
{
public XmlAttribute(): base("Passed string is not XML")
{}
/// <summary>
/// Override of <see cref="ValidationAttribute.IsValid(object)" />
/// </summary>
/// <param name="value">The value to test</param>
/// <returns>
/// Returns <see langword="false" /> if the <paramref name="value" /> is null or an empty string.
/// If <see cref="AllowEmptyStrings" /> then <see langword="true" /> is returned for empty strings.
/// </returns>
public override bool IsValid(object? value)
{
if (value is null || value is not string)
{
return false;
}
string possiblyXml = ((string)value).Trim();
if (!possiblyXml.StartsWith('<') && !possiblyXml.EndsWith('>'))
{
return false;
}
XmlDocument xml = new XmlDocument();
try
{
xml.LoadXml(possiblyXml);
}
catch (XmlException e)
{
// logme?
return false;
}
return true;
}
}
+14
View File
@@ -0,0 +1,14 @@
using System;
using example_csharp_app.Models.Request;
using example_csharp_app.Models.Response;
namespace example_csharp_app.Interfaces;
public interface IBookAggregator
{
Task<BookListResponse> GetBookListAsync(BookFilterRequest request);
Task<BookSingleResponse> GetBookAsync(Guid bookId);
Task<BookOperationResponse> UpdateBookAsync(BookUpdateRequest request);
Task<BookOperationResponse> AddBookAsync(BookCreateRequest request);
Task<BookOperationResponse> RemoveBookAsync(Guid bookId);
}
+14
View File
@@ -0,0 +1,14 @@
using System;
using example_csharp_app.Dto;
using example_csharp_app.Models.Request;
namespace example_csharp_app.Interfaces;
public interface IBookRepository
{
Task<int> AddBookAsync(BookDto dto);
Task<int> RemoveBookAsync(BookDto dto);
Task<BookDto?> GetBookAsync(Guid bookId, bool enableEdit = true);
Task<IReadOnlyCollection<BookDto>> GetBookListAsync(BookFilterRequest request);
Task<int> UpdateBookAsync(BookDto dto);
}
+14
View File
@@ -0,0 +1,14 @@
using System;
using example_csharp_app.Models.Model;
using example_csharp_app.Models.Request;
namespace example_csharp_app.Interfaces;
public interface IBookService
{
Task<IReadOnlyCollection<BookModel>> GetBooksAsync(BookFilterRequest request);
Task<BookModel?> GetBookAsync(Guid bookId);
Task<int> UpdateBookAsync(Guid bookId, BookUpdateModel model);
Task<int> AddBookAsync(BookCreateModel model);
Task<int> RemoveBookAsync(Guid bookId);
}
+56
View File
@@ -0,0 +1,56 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using example_app.Database.Connector;
#nullable disable
namespace example_csharp_app.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260925040202_init")]
partial class init
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.12")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("example_csharp_app.Dto.BookDto", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("text");
b.Property<string>("BookIndex")
.IsRequired()
.HasColumnType("xml");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("YearPublished")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("Books", (string)null);
});
#pragma warning restore 612, 618
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace example_csharp_app.Migrations
{
/// <inheritdoc />
public partial class init : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Books",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Title = table.Column<string>(type: "text", nullable: false),
Author = table.Column<string>(type: "text", nullable: false),
YearPublished = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
BookIndex = table.Column<string>(type: "xml", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Books", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Books");
}
}
}
+53
View File
@@ -0,0 +1,53 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using example_app.Database.Connector;
#nullable disable
namespace example_csharp_app.Migrations
{
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.12")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("example_csharp_app.Dto.BookDto", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("text");
b.Property<string>("BookIndex")
.IsRequired()
.HasColumnType("xml");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("YearPublished")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("Books", (string)null);
});
#pragma warning restore 612, 618
}
}
}
+32
View File
@@ -0,0 +1,32 @@
using System;
using example_csharp_app.Dto;
using example_csharp_app.Models.Model;
namespace example_csharp_app.Models.Mapper;
public static class BookMapper
{
public static BookModel MapToModel(this BookDto dto)
{
return new BookModel()
{
Author = dto.Author,
BookIndex = dto.BookIndex,
Id = dto.Id,
Title = dto.Title,
YearPublished = dto.YearPublished
};
}
public static BookDto MapToDto(this BookCreateModel model)
{
return new BookDto()
{
Id = Guid.NewGuid(),
Author = model.Author,
BookIndex = model.BookIndex,
Title = model.Title,
YearPublished = model.YearPublished
};
}
}
+30
View File
@@ -0,0 +1,30 @@
using System;
using example_csharp_app.Models.Model;
using example_csharp_app.Models.Request;
namespace example_csharp_app.Models.Mapper;
public static class RequestMapper
{
public static BookCreateModel MapToModel(this BookCreateRequest request)
{
return new BookCreateModel()
{
Author = request.Author,
BookIndex = request.BookIndex.Trim(),
Title = request.Title,
YearPublished = request.YearPublished.Date.ToUniversalTime()
};
}
public static BookUpdateModel MapToModel(this BookUpdateRequest request)
{
return new BookUpdateModel()
{
Author = request.Author,
BookIndex = request.BookIndex.Trim(),
Title = request.Title,
YearPublished = request.YearPublished
};
}
}
+11
View File
@@ -0,0 +1,11 @@
using System;
namespace example_csharp_app.Models.Model;
public class BookCreateModel
{
public string Title { set; get; } = string.Empty;
public string Author { set; get; } = string.Empty;
public DateTime YearPublished { set; get; }
public string BookIndex { set; get; } = string.Empty;
}
+12
View File
@@ -0,0 +1,12 @@
using System;
namespace example_csharp_app.Models.Model;
public class BookModel
{
public Guid Id { set; get; }
public string Title { set; get; } = string.Empty;
public string Author { set; get; } = string.Empty;
public DateTime YearPublished { set; get; }
public string BookIndex { set; get; } = string.Empty;
}
+11
View File
@@ -0,0 +1,11 @@
using System;
namespace example_csharp_app.Models.Model;
public class BookUpdateModel
{
public string Title { set; get; } = string.Empty;
public string Author { set; get; } = string.Empty;
public DateTime YearPublished { set; get; }
public string BookIndex { set; get; } = string.Empty;
}
+17
View File
@@ -0,0 +1,17 @@
using System;
using System.ComponentModel.DataAnnotations;
using example_csharp_app.Filters;
namespace example_csharp_app.Models.Request;
public class BookCreateRequest
{
[Required]
public string Title { set; get; } = string.Empty;
[Required]
public string Author { set; get; } = string.Empty;
[Required]
public DateTime YearPublished { set; get; }
[Required, Xml]
public string BookIndex { set; get; } = string.Empty;
}
+10
View File
@@ -0,0 +1,10 @@
using System;
namespace example_csharp_app.Models.Request;
public class BookFilterRequest
{
public string? Author { set; get; }
public string? Index { set; get; }
public string? Title { set; get; }
}
+19
View File
@@ -0,0 +1,19 @@
using System;
using System.ComponentModel.DataAnnotations;
using example_csharp_app.Filters;
namespace example_csharp_app.Models.Request;
public class BookUpdateRequest
{
[Required]
public Guid Id { set; get; } = Guid.Empty;
[Required]
public string Title { set; get; } = string.Empty;
[Required]
public string Author { set; get; } = string.Empty;
[Required]
public DateTime YearPublished { set; get; }
[Required, Xml]
public string BookIndex { set; get; } = string.Empty;
}
+9
View File
@@ -0,0 +1,9 @@
using System;
using example_csharp_app.Models.Model;
namespace example_csharp_app.Models.Response;
public class BookListResponse
{
public IReadOnlyCollection<BookModel> Books { set; get; } = [];
}
+8
View File
@@ -0,0 +1,8 @@
using System;
namespace example_csharp_app.Models.Response;
public class BookOperationResponse
{
public bool IsSuccess { set; get; }
}
+9
View File
@@ -0,0 +1,9 @@
using System;
using example_csharp_app.Models.Model;
namespace example_csharp_app.Models.Response;
public class BookSingleResponse
{
public BookModel? Book { set; get; }
}
+91
View File
@@ -0,0 +1,91 @@
using example_app.Database.Connector;
using example_csharp_app;
using example_csharp_app.Aggregators;
using example_csharp_app.Interfaces;
using example_csharp_app.Repositories;
using example_csharp_app.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Program
{
public static class Program
{
private static void SetupDi(IServiceCollection services, IConfiguration configuration)
{
services.AddScoped<IBookAggregator, BookAggregator>();
services.AddScoped<IBookService, BookService>();
services.AddScoped<IBookRepository, BookRepository>();
services.AddDbContext<AppDbContext>(
(provider, builder) =>
{
//var settings = provider.GetRequiredService<IOptions<DbSettings>>(); // эта версия лучше
var connectionString = configuration.GetValue<string>("ConnectionStrings:DefaultConnection");
builder.UseNpgsql(connectionString);
},
ServiceLifetime.Transient
);
}
public static int Main(string[] args)
{
try
{
var configurationBuilder = (new ConfigurationBuilder())
.SetBasePath(Environment.CurrentDirectory)
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile("appsettings.{environmentName}", true, true)
.AddEnvironmentVariables()
.AddCommandLine(args);
var builder = WebApplication.CreateBuilder(args);
var configuration = configurationBuilder.Build();
builder.Services.AddControllers();
builder.Services.AddSwaggerGen();
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
SetupDi(builder.Services, configuration);
var app = builder.Build();
app.UseCors("AllowAll");
app.UseCors(cors => cors.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
//if(app.Environment.IsDevelopment())
//{
app.UseSwagger();
app.UseSwaggerUI();
//}
app.MapControllers();
app.UseMiddleware<ErrorLogMiddleware>();
//app.UseHttpsRedirection();
app.Run();
} catch (Exception)
{
// log
}
finally
{
// flush log
}
return 0;
}
}
}
+23
View File
@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5222",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7189;http://localhost:5222",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
+95 -2
View File
@@ -1,2 +1,95 @@
# example-csharp-app
# example-csharp-app
DB sql
// pgsql
-- Вставка
CREATE PROCEDURE "sp_InsertBook"(
Id UUID,
Title VARCHAR(255),
Author VARCHAR(255),
YearPublished TimestampTz = NULL,
BookIndex XML = NULL
)
LANGUAGE plpgsql
as $$
BEGIN
INSERT INTO "Books" ("Id", "Title", "Author", "YearPublished", "BookIndex")
VALUES (Id, Title, Author, YearPublished, BookIndex);
end
$$
-- Обновление
CREATE PROCEDURE "sp_UpdateBook"(
Id UUID,
Title VARCHAR(255),
Author VARCHAR(255),
YearPublished TimestampTz,
BookIndex xml
)
LANGUAGE plpgsql
as $$
BEGIN
UPDATE "Books"
SET
"Title" = Title,
"Author" = Author,
"YearPublished" = YearPublished,
"BookIndex" = BookIndex
WHERE "Id" = Id;
end
$$
-- удаление
CREATE PROCEDURE "sp_DeleteBook"(
Id UUID
)
LANGUAGE plpgsql
as $$
BEGIN
DELETE FROM "Books" WHERE "Id" = Id;
end
$$
-- получение данных
CREATE FUNCTION "sp_GetBookById"(BookId UUID)
returns table (
"Id" UUID,
"Author" TEXT,
"Title" TEXT,
"YearPublished" TIMESTAMPTZ,
"BookIndex" XML
)
LANGUAGE plpgsql
as $$
BEGIN
RETURN QUERY
SELECT b."Id", b."Author", b."Title", b."YearPublished", b."BookIndex"
FROM "Books" b
WHERE b."Id" = BookId;
END
$$
-- поиск по библиотеке
CREATE FUNCTION "sp_SearchBooks"(p_Title TEXT, p_Author TEXT, p_Index TEXT)
returns table (
"Id" UUID,
"Author" TEXT,
"Title" TEXT,
"YearPublished" TIMESTAMPTZ,
"BookIndex" XML
)
LANGUAGE plpgsql
as $$
BEGIN
RETURN QUERY
SELECT b."Id", b."Author", b."Title", b."YearPublished", b."BookIndex"
FROM "Books" b
WHERE
(p_Author IS NULL OR b."Author" ~* p_Author) AND
(p_Title IS NULL OR b."Title" ~* p_Title) AND
(b."BookIndex"::TEXT ILIKE CONCAT('%', p_Index, '%'));
END
$$
+100
View File
@@ -0,0 +1,100 @@
using System;
using example_app.Database.Connector;
using example_csharp_app.Dto;
using example_csharp_app.Interfaces;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Data.SqlClient;
using System.Globalization;
using example_csharp_app.Models.Request;
namespace example_csharp_app.Repositories;
public class BookRepository : IBookRepository
{
private readonly AppDbContext _dbContext;
public BookRepository(AppDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<int> AddBookAsync(BookDto dto)
{
var result = await _dbContext.Database.ExecuteSqlInterpolatedAsync(
$"CALL public.\"sp_InsertBook\" ({dto.Id}, {dto.Title}, {dto.Author}, {dto.YearPublished}, {dto.BookIndex}::XML)"
);
return 1; // Процедура в pgsql не возвращает значение - это делают функции; даже rowcount только в функции
// _dbContext.Add(dto);
// return await _dbContext.SaveChangesAsync();
}
public async Task<BookDto?> GetBookAsync(Guid bookId, bool enableEdit = true)
{
var query = _dbContext.Books.FromSqlInterpolated(
$"SELECT * FROM \"sp_GetBookById\"({bookId})"
);
return await query.FirstOrDefaultAsync();
// var query = _dbContext.Books.Where(x => x.Id == bookId);
// if(enableEdit == false)
// {
// query = query.AsNoTracking();
// }
// return await query.FirstOrDefaultAsync();
}
public async Task<IReadOnlyCollection<BookDto>> GetBookListAsync(BookFilterRequest request)
{
var books = await _dbContext.Books.FromSqlInterpolated($"SELECT * FROM \"sp_SearchBooks\" ({request.Title}, {request.Author}, {request.Index})").ToListAsync();
return books;
// IQueryable<BookDto> query = _dbContext.Books.AsNoTracking();
// if(request.Author != null)
// {
// query = query.Where(x => x.Author.Contains(request.Author));
// }
// if(request.Title != null)
// {
// query = query.Where(x => x.Title.Contains(request.Title));
// }
// if(request.Index != null)
// {
// var interminentQuery = await query.ToListAsync();
// return interminentQuery.Where(x => x.BookIndex.Contains(request.Index)).ToList();
// // решение плохое
// // xml ~~ xml - operation does not exist
// //query = query.Where(x => x.BookIndex.Contains(request.Index));
// }
// return await query.ToListAsync();
}
public async Task<int> RemoveBookAsync(BookDto dto)
{
var result = await _dbContext.Database.ExecuteSqlInterpolatedAsync(
$"CALL \"sp_DeleteBook\"({dto.Id})"
);
return 1; // Процедура в pgsql не возвращает значение - это делают функции; даже rowcount только в функции
// _dbContext.Books.Remove(dto);
// return await _dbContext.SaveChangesAsync();
}
public async Task<int> UpdateBookAsync(BookDto dto)
{
var result = await _dbContext.Database.ExecuteSqlInterpolatedAsync(
$"CALL \"sp_UpdateBook\"({dto.Id}, {dto.Title}, {dto.Author}, {dto.YearPublished.ToString()}, {dto.BookIndex})"
);
return 1; // Процедура в pgsql не возвращает значение - это делают функции; даже rowcount только в функции
// _dbContext.Books.Update(dto);
// return await _dbContext.SaveChangesAsync();
}
}
+61
View File
@@ -0,0 +1,61 @@
using System;
using example_csharp_app.Dto;
using example_csharp_app.Interfaces;
using example_csharp_app.Models.Mapper;
using example_csharp_app.Models.Model;
using example_csharp_app.Models.Request;
namespace example_csharp_app.Services;
public class BookService : IBookService
{
private readonly IBookRepository _bookRepository;
public BookService(IBookRepository bookRepository)
{
_bookRepository = bookRepository;
}
public async Task<int> AddBookAsync(BookCreateModel model)
{
var dto = model.MapToDto();
var result = await _bookRepository.AddBookAsync(dto);
return result;
}
public async Task<BookModel?> GetBookAsync(Guid bookId)
{
var result = await _bookRepository.GetBookAsync(bookId, enableEdit: false);
return result?.MapToModel();
}
public async Task<IReadOnlyCollection<BookModel>> GetBooksAsync(BookFilterRequest request)
{
var result = await _bookRepository.GetBookListAsync(request);
return result.Select(x => x.MapToModel()).ToList();
}
public async Task<int> RemoveBookAsync(Guid bookId)
{
var book = await GetBookForEdit(bookId);
return await _bookRepository.RemoveBookAsync(book);
}
public async Task<int> UpdateBookAsync(Guid bookId, BookUpdateModel model)
{
var book = await GetBookForEdit(bookId);
book.Author = model.Author;
book.BookIndex = model.BookIndex;
book.Title = model.Title;
book.YearPublished = model.YearPublished;
return await _bookRepository.UpdateBookAsync(book);
}
private async Task<BookDto> GetBookForEdit(Guid bookId)
{
var book = await _bookRepository.GetBookAsync(bookId);
return book ?? throw new ArgumentException("Book does not exist");
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Server=192.168.1.40:5455;Database=aspnet-testdata;User Id={SECRET_USER};Password={SECRET_PASS}"
}
}
+24
View File
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>example_csharp_app</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.12" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.12" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.2.3" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.12" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.12" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="10.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="10.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="10.2.3" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.1.0" />
</ItemGroup>
</Project>