From 7328e38de42a892ee0c6397b9fcec0ec1a653736 Mon Sep 17 00:00:00 2001 From: Goodman74 Date: Fri, 10 Apr 2026 21:37:32 +0300 Subject: [PATCH] feat(HW03): setup EF Core, repositories, controllers and seeding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Общие изменения: - Настроен локальный EF Core (dotnet ef) - Подключён и сконфигурирован SQLite - Добавлены конфигурации таблиц (Fluent API) - Созданы и применены миграции (migration + update) - Repository слой: - Добавлен обобщённый EfRepository - Реализованы методы Add, GetAll и остальные базовые операции - Проведён рефакторинг IRepository и EfRepository - Unit of Work: - Добавлены IUnitOfWork и EfUnitOfWork - Seeding: - Включено заполнение таблиц начальными данными - Проведён рефакторинг механизма seeding - Controllers: - Реализованы CRUD actions для: - CustomersController - PreferencesController - PromoCodesController --- Homeworks/.gitattributes | 1 + .../Abstractions/Repositories/IRepository.cs" | 42 ++- .../Abstractions/Repositories/IUnitOfWork.cs" | 9 + .../Domain/BaseEntity.cs" | 2 +- .../.editorconfig" | 3 + .../DependencyInjection.cs" | 20 +- .../HostExtensions.cs" | 38 +-- .../20260405020749_InitialCreate.Designer.cs" | 272 +++++++++++++++++ .../20260405020749_InitialCreate.cs" | 211 ++++++++++++++ ...635_AddIdxToCustomerPromoCode.Designer.cs" | 273 ++++++++++++++++++ ...260407040635_AddIdxToCustomerPromoCode.cs" | 37 +++ ...PromoCodeFactoryDbContextModelSnapshot.cs" | 270 +++++++++++++++++ .../PromoCodeFactory.DataAccess.csproj" | 2 +- .../PromoCodeFactoryDbContext.cs" | 38 ++- .../Repositories/DebugContext.cs" | 22 ++ .../Repositories/EfRepository.cs" | 62 ++-- .../Repositories/EfUnitOfWork.cs" | 11 + .../Repositories/InMemoryRepository.cs" | 3 +- .../Repositories/PreferenceEfRepository.cs" | 13 + .../CustomerPromoCodeTableConfig.cs" | 38 +++ .../TableConfig/CustomerTableConfig.cs" | 32 ++ .../TableConfig/EmployeeTableConfig.cs" | 33 +++ .../TableConfig/PreferenceTableConfig.cs" | 18 ++ .../TableConfig/PromoCodeTableConfig.cs" | 43 +++ .../TableConfig/RoleTableConfig.cs" | 21 ++ .../Controllers/CustomersController.cs" | 98 ++++++- .../Controllers/EmployeesController.cs" | 53 ++-- .../Controllers/PreferencesController.cs" | 10 +- .../Controllers/PromoCodesController.cs" | 98 ++++++- .../Mapping/CustomerMapper.cs" | 68 +++++ .../Mapping/CustomerPromoCodeMapper.cs" | 17 ++ .../Mapping/PromoCodesMapper.cs" | 15 + .../src/PromoCodeFactory.WebHost/Program.cs" | 48 +-- Homeworks/dotnet-tools.json | 13 + 34 files changed, 1821 insertions(+), 113 deletions(-) create mode 100644 Homeworks/.gitattributes create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Abstractions/Repositories/IUnitOfWork.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/.editorconfig" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260405020749_InitialCreate.Designer.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260405020749_InitialCreate.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260407040635_AddIdxToCustomerPromoCode.Designer.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260407040635_AddIdxToCustomerPromoCode.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/PromoCodeFactoryDbContextModelSnapshot.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/DebugContext.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/EfUnitOfWork.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/PreferenceEfRepository.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/CustomerPromoCodeTableConfig.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/CustomerTableConfig.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/EmployeeTableConfig.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/PreferenceTableConfig.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/PromoCodeTableConfig.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/RoleTableConfig.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/CustomerMapper.cs" create mode 100644 "Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/CustomerPromoCodeMapper.cs" create mode 100644 Homeworks/dotnet-tools.json diff --git a/Homeworks/.gitattributes b/Homeworks/.gitattributes new file mode 100644 index 000000000..94f480de9 --- /dev/null +++ b/Homeworks/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs" index 778473901..a9f419670 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs" @@ -8,23 +8,47 @@ public interface IRepository where T : BaseEntity { Task> GetAll(bool withIncludes = false, CancellationToken ct = default); + /// The entity tracked by the current DbContext if found; otherwise, . Task GetById(Guid id, bool withIncludes = false, CancellationToken ct = default); - Task> GetByRangeId( - IEnumerable ids, - bool withIncludes = false, + /// + /// Collection of entities matching the specified ids. + /// Entities are tracked by the current DbContext. + /// + Task> GetByRangeId(IEnumerable ids, bool withIncludes = false, CancellationToken ct = default); - Task> GetWhere( - Expression> predicate, - bool withIncludes = false, + /// + /// Collection of entities matching the specified predicate. + /// Entities are tracked by the current DbContext. + /// + Task> GetWhere(Expression> predicate, bool withIncludes = false, CancellationToken ct = default); - Task Add(T entity, CancellationToken ct); + /// + /// Adds a new entity to the current DbContext. Use IUnitOfWork to SaveChangesAsync + /// + /// + /// A new entity with tracked references to existing related entities. + /// + void Add(T entity); - /// - Task Update(T entity, CancellationToken ct); + /// + /// Updates a detached entity by applying its values to the existing entity in the DbContext and saving changes. + /// + /// Thrown if the entity with the given Id is not found in the database. + Task UpdateDetached(T entity, CancellationToken ct); /// Task Delete(Guid id, CancellationToken ct); + + /// + /// Adds new entities to the current DbContext. Use IUnitOfWork to SaveChangesAsync. + /// + /// + /// New entities with tracked references to existing related entities. + /// + void AddRange(IEnumerable entities); + + Task IsNotEmptyAsync(CancellationToken ct); } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Abstractions/Repositories/IUnitOfWork.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Abstractions/Repositories/IUnitOfWork.cs" new file mode 100644 index 000000000..064a31d18 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Abstractions/Repositories/IUnitOfWork.cs" @@ -0,0 +1,9 @@ +namespace PromoCodeFactory.Core.Abstractions.Repositories; + +public interface IUnitOfWork +{ + /// + /// Persists changes for all modified entities currently tracked by the DbContext. + /// + Task SaveChangesAsync(CancellationToken ct); +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Domain/BaseEntity.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Domain/BaseEntity.cs" index 0b4dc48c9..ff90f1b4d 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Domain/BaseEntity.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Domain/BaseEntity.cs" @@ -2,5 +2,5 @@ namespace PromoCodeFactory.Core.Domain; public abstract class BaseEntity { - public Guid Id { get; set; } + public Guid Id { get; set; } = Guid.NewGuid(); } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/.editorconfig" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/.editorconfig" new file mode 100644 index 000000000..adb3daf5a --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/.editorconfig" @@ -0,0 +1,3 @@ + +[*.cs] +csharp_style_namespace_declarations = block_scoped:silent diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/DependencyInjection.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/DependencyInjection.cs" index c6e906393..ba417904c 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/DependencyInjection.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/DependencyInjection.cs" @@ -9,7 +9,7 @@ namespace PromoCodeFactory.DataAccess; public static class DependencyInjection { - public static void AddInMemoryDataAccess(this IServiceCollection services) +/* public static void AddInMemoryDataAccess(this IServiceCollection services) { services.AddSingleton>(_ => new InMemoryRepository(SeedData.Employees)); @@ -23,18 +23,28 @@ public static void AddInMemoryDataAccess(this IServiceCollection services) new InMemoryRepository(SeedData.PromoCodes)); services.AddSingleton>(_ => new InMemoryRepository(SeedData.CustomerPromoCodes)); - } + }*/ - public static void AddEfDataAccess(this IServiceCollection services) + public static void AddEfDataAccess(this IServiceCollection services, string contentRootPath) { + var dbPath = Path.Combine(contentRootPath, "PromoCodeFactory.sqlite"); + Console.WriteLine($"dbPath[{dbPath}]"); + services.AddDbContext(builder => - builder.UseSqlite("Filename=PromoCodeFactory.sqlite")); + builder.UseSqlite( + $"Data Source={dbPath}", + // EfRepository auto Include => therefore EF warns about multiple collection include + // SplitQuery => fixes execution strategy for that auto Include + opt => opt.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery) + ) + ); + services.AddScoped(); services.AddScoped, EmployeeEfRepository>(); services.AddScoped, EfRepository>(); services.AddScoped, CustomerEfRepository>(); services.AddScoped, PromoCodeEfRepository>(); - services.AddScoped, EfRepository>(); + services.AddScoped, PreferenceEfRepository>(); services.AddScoped, EfRepository>(); } } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/HostExtensions.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/HostExtensions.cs" index b0ef9f086..1dc0cf2f0 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/HostExtensions.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/HostExtensions.cs" @@ -14,18 +14,18 @@ public static class HostExtensions public static async Task SeedDatabase(this IHost host, CancellationToken ct = default) { using var scope = host.Services.CreateScope(); - var logger = scope.ServiceProvider - .GetRequiredService() - .CreateLogger("SeedDatabase"); + IServiceProvider sp = scope.ServiceProvider; + var logger = sp.GetRequiredService().CreateLogger("SeedDatabase"); logger.LogInformation("Starting database seed..."); + var unitOfWork = sp.GetRequiredService(); - await SeedEntity(scope.ServiceProvider, SeedData.Roles, ct); - await SeedEntity(scope.ServiceProvider, SeedData.Preferences, ct); - await SeedEntity(scope.ServiceProvider, SeedData.Employees, ct); - await SeedEntity(scope.ServiceProvider, SeedData.Customers, ct); - await SeedEntity(scope.ServiceProvider, SeedData.PromoCodes, ct); - await SeedEntity(scope.ServiceProvider, SeedData.CustomerPromoCodes, ct); + await SeedEntity(sp, SeedData.Roles, unitOfWork, ct); + await SeedEntity(sp, SeedData.Preferences, unitOfWork, ct); + await SeedEntity(sp, SeedData.Employees, unitOfWork, ct); + await SeedEntity(sp, SeedData.Customers, unitOfWork, ct); + await SeedEntity(sp, SeedData.PromoCodes, unitOfWork, ct); + await SeedEntity(sp, SeedData.CustomerPromoCodes, unitOfWork, ct); logger.LogInformation("Database seed completed."); } @@ -52,18 +52,22 @@ private static IHost MigrateDatabase(this IHost host) where TDbConte return host; } - public static async Task SeedEntity( - IServiceProvider serviceProvider, - IReadOnlyCollection entities, - CancellationToken ct) - where T : BaseEntity + public static async Task SeedEntity(IServiceProvider serviceProvider, IReadOnlyCollection entities, + IUnitOfWork unitOfWork, CancellationToken ct) where T : BaseEntity { var repository = serviceProvider.GetRequiredService>(); - if ((await repository.GetAll()).Count > 0) - return; + var ids = entities.Select(x => x.Id); + var existing = await repository.GetByRangeId(ids, ct: ct); + + var existingIds = existing.Select(x => x.Id).ToHashSet(); foreach (var entity in entities) - await repository.Add(entity, ct); + { + if (!existingIds.Contains(entity.Id)) + repository.Add(entity); + } + + await unitOfWork.SaveChangesAsync(ct); } } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260405020749_InitialCreate.Designer.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260405020749_InitialCreate.Designer.cs" new file mode 100644 index 000000000..e0968fad0 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260405020749_InitialCreate.Designer.cs" @@ -0,0 +1,272 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using PromoCodeFactory.DataAccess; + +#nullable disable + +namespace PromoCodeFactory.DataAccess.Migrations +{ + [DbContext(typeof(PromoCodeFactoryDbContext))] + [Migration("20260405020749_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.3"); + + modelBuilder.Entity("CustomerPreference", b => + { + b.Property("CustomersId") + .HasColumnType("TEXT"); + + b.Property("PreferencesId") + .HasColumnType("TEXT"); + + b.HasKey("CustomersId", "PreferencesId"); + + b.HasIndex("PreferencesId"); + + b.ToTable("JoinCustomerPreference", (string)null); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("Employees"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.CustomerPromoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppliedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("PromoCodeId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("PromoCodeId"); + + b.ToTable("CustomerPromoCodes"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BeginDate") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("PartnerManagerId") + .HasColumnType("TEXT"); + + b.Property("PartnerName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PreferenceId") + .HasColumnType("TEXT"); + + b.Property("ServiceInfo") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PartnerManagerId"); + + b.HasIndex("PreferenceId"); + + b.ToTable("PromoCodes"); + }); + + modelBuilder.Entity("CustomerPreference", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", null) + .WithMany() + .HasForeignKey("PreferencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.Administration.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.CustomerPromoCode", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", null) + .WithMany("CustomerPromoCodes") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", null) + .WithMany("CustomerPromoCodes") + .HasForeignKey("PromoCodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.Administration.Employee", "PartnerManager") + .WithMany() + .HasForeignKey("PartnerManagerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", "Preference") + .WithMany() + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PartnerManager"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", b => + { + b.Navigation("CustomerPromoCodes"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.Navigation("CustomerPromoCodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260405020749_InitialCreate.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260405020749_InitialCreate.cs" new file mode 100644 index 000000000..cb57417c3 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260405020749_InitialCreate.cs" @@ -0,0 +1,211 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PromoCodeFactory.DataAccess.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Customers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + FirstName = table.Column(type: "TEXT", maxLength: 50, nullable: false), + LastName = table.Column(type: "TEXT", maxLength: 50, nullable: false), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Customers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Preferences", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Preferences", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Roles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 100, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 500, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Roles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "JoinCustomerPreference", + columns: table => new + { + CustomersId = table.Column(type: "TEXT", nullable: false), + PreferencesId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_JoinCustomerPreference", x => new { x.CustomersId, x.PreferencesId }); + table.ForeignKey( + name: "FK_JoinCustomerPreference_Customers_CustomersId", + column: x => x.CustomersId, + principalTable: "Customers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_JoinCustomerPreference_Preferences_PreferencesId", + column: x => x.PreferencesId, + principalTable: "Preferences", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Employees", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + FirstName = table.Column(type: "TEXT", maxLength: 50, nullable: false), + LastName = table.Column(type: "TEXT", maxLength: 50, nullable: false), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Employees", x => x.Id); + table.ForeignKey( + name: "FK_Employees_Roles_RoleId", + column: x => x.RoleId, + principalTable: "Roles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PromoCodes", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Code = table.Column(type: "TEXT", maxLength: 100, nullable: false), + ServiceInfo = table.Column(type: "TEXT", maxLength: 150, nullable: false), + BeginDate = table.Column(type: "TEXT", nullable: false), + EndDate = table.Column(type: "TEXT", nullable: false), + PartnerName = table.Column(type: "TEXT", maxLength: 256, nullable: false), + PartnerManagerId = table.Column(type: "TEXT", nullable: false), + PreferenceId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PromoCodes", x => x.Id); + table.ForeignKey( + name: "FK_PromoCodes_Employees_PartnerManagerId", + column: x => x.PartnerManagerId, + principalTable: "Employees", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PromoCodes_Preferences_PreferenceId", + column: x => x.PreferenceId, + principalTable: "Preferences", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CustomerPromoCodes", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + CustomerId = table.Column(type: "TEXT", nullable: false), + PromoCodeId = table.Column(type: "TEXT", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + AppliedAt = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CustomerPromoCodes", x => x.Id); + table.ForeignKey( + name: "FK_CustomerPromoCodes_Customers_CustomerId", + column: x => x.CustomerId, + principalTable: "Customers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CustomerPromoCodes_PromoCodes_PromoCodeId", + column: x => x.PromoCodeId, + principalTable: "PromoCodes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CustomerPromoCodes_CustomerId", + table: "CustomerPromoCodes", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_CustomerPromoCodes_PromoCodeId", + table: "CustomerPromoCodes", + column: "PromoCodeId"); + + migrationBuilder.CreateIndex( + name: "IX_Employees_RoleId", + table: "Employees", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_JoinCustomerPreference_PreferencesId", + table: "JoinCustomerPreference", + column: "PreferencesId"); + + migrationBuilder.CreateIndex( + name: "IX_PromoCodes_PartnerManagerId", + table: "PromoCodes", + column: "PartnerManagerId"); + + migrationBuilder.CreateIndex( + name: "IX_PromoCodes_PreferenceId", + table: "PromoCodes", + column: "PreferenceId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CustomerPromoCodes"); + + migrationBuilder.DropTable( + name: "JoinCustomerPreference"); + + migrationBuilder.DropTable( + name: "PromoCodes"); + + migrationBuilder.DropTable( + name: "Customers"); + + migrationBuilder.DropTable( + name: "Employees"); + + migrationBuilder.DropTable( + name: "Preferences"); + + migrationBuilder.DropTable( + name: "Roles"); + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260407040635_AddIdxToCustomerPromoCode.Designer.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260407040635_AddIdxToCustomerPromoCode.Designer.cs" new file mode 100644 index 000000000..943454ccd --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260407040635_AddIdxToCustomerPromoCode.Designer.cs" @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using PromoCodeFactory.DataAccess; + +#nullable disable + +namespace PromoCodeFactory.DataAccess.Migrations +{ + [DbContext(typeof(PromoCodeFactoryDbContext))] + [Migration("20260407040635_AddIdxToCustomerPromoCode")] + partial class AddIdxToCustomerPromoCode + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.3"); + + modelBuilder.Entity("CustomerPreference", b => + { + b.Property("CustomersId") + .HasColumnType("TEXT"); + + b.Property("PreferencesId") + .HasColumnType("TEXT"); + + b.HasKey("CustomersId", "PreferencesId"); + + b.HasIndex("PreferencesId"); + + b.ToTable("JoinCustomerPreference", (string)null); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("Employees"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.CustomerPromoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppliedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("PromoCodeId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PromoCodeId"); + + b.HasIndex("CustomerId", "PromoCodeId") + .IsUnique(); + + b.ToTable("CustomerPromoCodes"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BeginDate") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("PartnerManagerId") + .HasColumnType("TEXT"); + + b.Property("PartnerName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PreferenceId") + .HasColumnType("TEXT"); + + b.Property("ServiceInfo") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PartnerManagerId"); + + b.HasIndex("PreferenceId"); + + b.ToTable("PromoCodes"); + }); + + modelBuilder.Entity("CustomerPreference", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", null) + .WithMany() + .HasForeignKey("PreferencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.Administration.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.CustomerPromoCode", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", null) + .WithMany("CustomerPromoCodes") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", null) + .WithMany("CustomerPromoCodes") + .HasForeignKey("PromoCodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.Administration.Employee", "PartnerManager") + .WithMany() + .HasForeignKey("PartnerManagerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", "Preference") + .WithMany() + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PartnerManager"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", b => + { + b.Navigation("CustomerPromoCodes"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.Navigation("CustomerPromoCodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260407040635_AddIdxToCustomerPromoCode.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260407040635_AddIdxToCustomerPromoCode.cs" new file mode 100644 index 000000000..683668829 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260407040635_AddIdxToCustomerPromoCode.cs" @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PromoCodeFactory.DataAccess.Migrations +{ + /// + public partial class AddIdxToCustomerPromoCode : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_CustomerPromoCodes_CustomerId", + table: "CustomerPromoCodes"); + + migrationBuilder.CreateIndex( + name: "IX_CustomerPromoCodes_CustomerId_PromoCodeId", + table: "CustomerPromoCodes", + columns: new[] { "CustomerId", "PromoCodeId" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_CustomerPromoCodes_CustomerId_PromoCodeId", + table: "CustomerPromoCodes"); + + migrationBuilder.CreateIndex( + name: "IX_CustomerPromoCodes_CustomerId", + table: "CustomerPromoCodes", + column: "CustomerId"); + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/PromoCodeFactoryDbContextModelSnapshot.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/PromoCodeFactoryDbContextModelSnapshot.cs" new file mode 100644 index 000000000..d8a6b23c5 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/PromoCodeFactoryDbContextModelSnapshot.cs" @@ -0,0 +1,270 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using PromoCodeFactory.DataAccess; + +#nullable disable + +namespace PromoCodeFactory.DataAccess.Migrations +{ + [DbContext(typeof(PromoCodeFactoryDbContext))] + partial class PromoCodeFactoryDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.3"); + + modelBuilder.Entity("CustomerPreference", b => + { + b.Property("CustomersId") + .HasColumnType("TEXT"); + + b.Property("PreferencesId") + .HasColumnType("TEXT"); + + b.HasKey("CustomersId", "PreferencesId"); + + b.HasIndex("PreferencesId"); + + b.ToTable("JoinCustomerPreference", (string)null); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("Employees"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.CustomerPromoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppliedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("PromoCodeId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PromoCodeId"); + + b.HasIndex("CustomerId", "PromoCodeId") + .IsUnique(); + + b.ToTable("CustomerPromoCodes"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BeginDate") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("PartnerManagerId") + .HasColumnType("TEXT"); + + b.Property("PartnerName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PreferenceId") + .HasColumnType("TEXT"); + + b.Property("ServiceInfo") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PartnerManagerId"); + + b.HasIndex("PreferenceId"); + + b.ToTable("PromoCodes"); + }); + + modelBuilder.Entity("CustomerPreference", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", null) + .WithMany() + .HasForeignKey("PreferencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.Administration.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.CustomerPromoCode", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", null) + .WithMany("CustomerPromoCodes") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", null) + .WithMany("CustomerPromoCodes") + .HasForeignKey("PromoCodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.Administration.Employee", "PartnerManager") + .WithMany() + .HasForeignKey("PartnerManagerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", "Preference") + .WithMany() + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PartnerManager"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", b => + { + b.Navigation("CustomerPromoCodes"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.Navigation("CustomerPromoCodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj" index 4e3fe870e..386f31119 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj" @@ -1,4 +1,4 @@ - + net10.0 diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/PromoCodeFactoryDbContext.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/PromoCodeFactoryDbContext.cs" index 6b123ea6c..20d8301d2 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/PromoCodeFactoryDbContext.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/PromoCodeFactoryDbContext.cs" @@ -1,20 +1,42 @@ using Microsoft.EntityFrameworkCore; +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.DataAccess.TableConfig; namespace PromoCodeFactory.DataAccess; public class PromoCodeFactoryDbContext : DbContext { - public PromoCodeFactoryDbContext(DbContextOptions options) - : base(options) - { - } + public PromoCodeFactoryDbContext(DbContextOptions options) : base(options) { } + + public DbSet Employees => Set(); + + public DbSet Roles => Set(); + + public DbSet Customers => Set(); + + public DbSet CustomerPromoCodes => Set(); + + public DbSet Preferences => Set(); - //TODO: Добавить DbSet сущности + public DbSet PromoCodes => Set(); - protected override void OnModelCreating(ModelBuilder modelBuilder) + protected override void OnModelCreating(ModelBuilder builder) { - //TODO: Добавить маппинг моделей + base.OnModelCreating(builder); + + builder.ApplyConfigurationsFromAssembly(typeof(EmployeeTableConfig).Assembly); + + builder.ApplyConfigurationsFromAssembly(typeof(RoleTableConfig).Assembly); + + builder.ApplyConfigurationsFromAssembly(typeof(CustomerPromoCodeTableConfig).Assembly); + + builder.ApplyConfigurationsFromAssembly(typeof(CustomerTableConfig).Assembly); + + builder.ApplyConfigurationsFromAssembly(typeof(EmployeeTableConfig).Assembly); + + builder.ApplyConfigurationsFromAssembly(typeof(PreferenceTableConfig).Assembly); - base.OnModelCreating(modelBuilder); + builder.ApplyConfigurationsFromAssembly(typeof(PromoCodeTableConfig).Assembly); } } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/DebugContext.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/DebugContext.cs" new file mode 100644 index 000000000..eb116d944 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/DebugContext.cs" @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; + +namespace PromoCodeFactory.DataAccess.Repositories; + +public static class DebugContext +{ + public static void ShowChangeTacker(this DbContext context, string msg) + { + Console.WriteLine($"[{msg}]"); + var entries = context.ChangeTracker.Entries() + .Select(x => new + { + Entity = x.Entity.GetType().Name, + x.State + }) + .ToList(); + foreach (var item in entries) + { + Console.WriteLine($"[{item.Entity}] - [{item.State}]"); + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs" index 748b927b3..b5a3bb205 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs" @@ -1,6 +1,8 @@ -using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain; +using PromoCodeFactory.Core.Exceptions; +using System.Linq.Expressions; namespace PromoCodeFactory.DataAccess.Repositories; @@ -8,39 +10,65 @@ internal class EfRepository(PromoCodeFactoryDbContext context) : IRepository< { protected virtual IQueryable ApplyIncludes(IQueryable query) => query; - public Task Add(T entity, CancellationToken ct) - { - throw new NotImplementedException(); - } + public void Add(T entity) => context.Set().Add(entity); - public Task Delete(Guid id, CancellationToken ct) + public async Task Delete(Guid id, CancellationToken ct) { - throw new NotImplementedException(); + var rowDeleted = await context.Set() + .Where(x => x.Id == id) + .ExecuteDeleteAsync(ct); + + if (rowDeleted == 0) + throw new EntityNotFoundException(typeof(T), id); } - public Task> GetAll(bool withIncludes = false, CancellationToken ct = default) + public async Task> GetAll(bool withIncludes = false, CancellationToken ct = default) { - throw new NotImplementedException(); + IQueryable query = context.Set().AsNoTracking(); + + return await (withIncludes ? ApplyIncludes(query) : query).ToListAsync(ct); } - public Task GetById(Guid id, bool withIncludes = false, CancellationToken ct = default) + public async Task GetById(Guid id, bool withIncludes = false, CancellationToken ct = default) { - throw new NotImplementedException(); + IQueryable query = context.Set() + .Where(x => x.Id == id); + + return await (withIncludes ? ApplyIncludes(query) : query).SingleOrDefaultAsync(ct); } - public Task> GetByRangeId(IEnumerable ids, bool withIncludes = false, CancellationToken ct = default) + public async Task> GetByRangeId(IEnumerable ids, bool withIncludes = false, + CancellationToken ct = default) { - throw new NotImplementedException(); + Guid[] idArray = ids.Distinct().ToArray(); + + IQueryable query = context.Set() + .Where(e => idArray.Contains(e.Id)); + + return await (withIncludes ? ApplyIncludes(query) : query).ToListAsync(ct); } - public Task> GetWhere(Expression> predicate, bool withIncludes = false, CancellationToken ct = default) + public async Task> GetWhere(Expression> predicate, bool withIncludes = false, + CancellationToken ct = default) { - throw new NotImplementedException(); + IQueryable query = context.Set() + .Where(predicate); + return await (withIncludes ? ApplyIncludes(query) : query).ToListAsync(ct); } - public Task Update(T entity, CancellationToken ct) + public async Task UpdateDetached(T entity, CancellationToken ct) { - throw new NotImplementedException(); + var row = await context.Set() + .Where(x => x.Id == entity.Id) + .SingleOrDefaultAsync(ct); + + if (row == null) + throw new EntityNotFoundException(typeof(T), entity.Id); + + context.Entry(row).CurrentValues.SetValues(entity); } + public void AddRange(IEnumerable entities) => context.Set().AddRange(entities); + + public async Task IsNotEmptyAsync(CancellationToken ct) => await context.Set().AnyAsync(ct); } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/EfUnitOfWork.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/EfUnitOfWork.cs" new file mode 100644 index 000000000..552167fb9 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/EfUnitOfWork.cs" @@ -0,0 +1,11 @@ +using PromoCodeFactory.Core.Abstractions.Repositories; + +namespace PromoCodeFactory.DataAccess.Repositories; + +public sealed class EfUnitOfWork(PromoCodeFactoryDbContext _dbContext) : IUnitOfWork +{ + public Task SaveChangesAsync(CancellationToken ct) + { + return _dbContext.SaveChangesAsync(ct); + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs" index 3a93c83b7..919a15499 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs" @@ -5,7 +5,7 @@ using System.Linq.Expressions; namespace PromoCodeFactory.DataAccess.Repositories; - +/* internal class InMemoryRepository : IRepository where T : BaseEntity { private readonly ConcurrentDictionary _data; @@ -77,3 +77,4 @@ public Task Delete(Guid id, CancellationToken ct) return Task.CompletedTask; } } +*/ diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/PreferenceEfRepository.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/PreferenceEfRepository.cs" new file mode 100644 index 000000000..723edc7c5 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/PreferenceEfRepository.cs" @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; + +namespace PromoCodeFactory.DataAccess.Repositories; + +internal class PreferenceEfRepository(PromoCodeFactoryDbContext context) : EfRepository(context) +{ + protected override IQueryable ApplyIncludes(IQueryable query) + { + return query + .Include(p => p.Customers); + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/CustomerPromoCodeTableConfig.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/CustomerPromoCodeTableConfig.cs" new file mode 100644 index 000000000..1a094c03b --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/CustomerPromoCodeTableConfig.cs" @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; + +namespace PromoCodeFactory.DataAccess.TableConfig +{ + internal class CustomerPromoCodeTableConfig : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder entity) + { + entity.HasKey(x => x.Id); + + entity.Property(x => x.CustomerId) + .IsRequired(); + + entity.Property(x => x.PromoCodeId) + .IsRequired(); + + entity.Property(x => x.CreatedAt) + .IsRequired(); + + entity.Property(x => x.AppliedAt); + + entity.HasOne() + .WithMany(x => x.CustomerPromoCodes) + .HasForeignKey(x => x.CustomerId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne() + .WithMany(x => x.CustomerPromoCodes) + .HasForeignKey(x => x.PromoCodeId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasIndex(x => new { x.CustomerId, x.PromoCodeId }) + .IsUnique(); + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/CustomerTableConfig.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/CustomerTableConfig.cs" new file mode 100644 index 000000000..826991291 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/CustomerTableConfig.cs" @@ -0,0 +1,32 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; + +namespace PromoCodeFactory.DataAccess.TableConfig +{ + internal class CustomerTableConfig : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder entity) + { + entity.HasKey(x => x.Id); + + entity.Property(x => x.FirstName) + .IsRequired() + .HasMaxLength(50); + + entity.Property(x => x.LastName) + .IsRequired() + .HasMaxLength(50); + + entity.Ignore(x => x.FullName); + + entity.Property(x => x.Email) + .IsRequired() + .HasMaxLength(256); + + entity.HasMany(x => x.Preferences) + .WithMany(p => p.Customers) + .UsingEntity(j => j.ToTable("JoinCustomerPreference")); + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/EmployeeTableConfig.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/EmployeeTableConfig.cs" new file mode 100644 index 000000000..a1b2079bf --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/EmployeeTableConfig.cs" @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using PromoCodeFactory.Core.Domain.Administration; + +namespace PromoCodeFactory.DataAccess.TableConfig +{ + internal class EmployeeTableConfig : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder entity) + { + entity.HasKey(x => x.Id); + + entity.Property(x => x.FirstName) + .IsRequired() + .HasMaxLength(50); + + entity.Property(x => x.LastName) + .IsRequired() + .HasMaxLength(50); + + entity.Ignore(x => x.FullName); + + entity.Property(x => x.Email) + .IsRequired() + .HasMaxLength(256); + + entity.HasOne(x => x.Role) + .WithMany() + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/PreferenceTableConfig.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/PreferenceTableConfig.cs" new file mode 100644 index 000000000..50efae634 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/PreferenceTableConfig.cs" @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; + +namespace PromoCodeFactory.DataAccess.TableConfig +{ + internal class PreferenceTableConfig : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder entity) + { + entity.HasKey(x => x.Id); + + entity.Property(x => x.Name) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/PromoCodeTableConfig.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/PromoCodeTableConfig.cs" new file mode 100644 index 000000000..0d004836d --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/PromoCodeTableConfig.cs" @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; + +namespace PromoCodeFactory.DataAccess.TableConfig +{ + internal class PromoCodeTableConfig : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder entity) + { + entity.HasKey(x => x.Id); + + entity.Property(x => x.Code) + .IsRequired() + .HasMaxLength(100); + + entity.Property(x => x.ServiceInfo) + .IsRequired() + .HasMaxLength(150); + + entity.Property(x => x.BeginDate) + .IsRequired(); + + entity.Property(x => x.EndDate) + .IsRequired(); + + entity.Property(x => x.PartnerName) + .IsRequired() + .HasMaxLength(256); + + entity.HasOne(x => x.PartnerManager) + .WithMany() + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + entity.HasOne(x => x.Preference) + .WithMany() + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/RoleTableConfig.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/RoleTableConfig.cs" new file mode 100644 index 000000000..e1ee6cf74 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/TableConfig/RoleTableConfig.cs" @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using PromoCodeFactory.Core.Domain.Administration; + +namespace PromoCodeFactory.DataAccess.TableConfig +{ + internal class RoleTableConfig : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder entity) + { + entity.HasKey(x => x.Id); + + entity.Property(x => x.Name) + .IsRequired() + .HasMaxLength(100); + + entity.Property(x => x.Description) + .HasMaxLength(500); + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs" index 1fbcd4f8f..0bb579526 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs" @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Mvc; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Mapping; using PromoCodeFactory.WebHost.Models.Customers; namespace PromoCodeFactory.WebHost.Controllers; @@ -6,7 +8,8 @@ namespace PromoCodeFactory.WebHost.Controllers; /// /// Клиенты /// -public class CustomersController : BaseController +public class CustomersController(IRepository customerRepository, IUnitOfWork unitOfWork, + IRepository promoCodeRepository, IRepository preferenceRepository) : BaseController { /// /// Получить данные всех клиентов @@ -15,7 +18,11 @@ public class CustomersController : BaseController [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task>> Get(CancellationToken ct) { - throw new NotImplementedException(); + var customers = await customerRepository.GetAll(withIncludes: true, ct); + + var responses = customers.Select(CustomerMapper.ToCustomerShortResponse).ToList(); + + return Ok(responses); } /// @@ -26,7 +33,24 @@ public async Task>> Get(Cancella [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task> GetById(Guid id, CancellationToken ct) { - throw new NotImplementedException(); + var customer = await customerRepository.GetById(id, withIncludes: true, ct); + + if (customer is null) + return NotFound(new ProblemDetails + { + Title = "GetById Customer request canceled", + Detail = $"Customer with Id {id} not found." + }); + + var promoCodeIds = customer.CustomerPromoCodes + .Select(x => x.PromoCodeId) + .ToList(); + + var promoCodes = await promoCodeRepository.GetByRangeId(promoCodeIds, withIncludes: true, ct); + + var promoCodesById = promoCodes.ToDictionary(x => x.Id); + + return Ok(CustomerMapper.ToCustomerResponse(customer, promoCodesById)); } /// @@ -37,7 +61,24 @@ public async Task> GetById(Guid id, CancellationT [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] public async Task> Create([FromBody] CustomerCreateRequest request, CancellationToken ct) { - throw new NotImplementedException(); + var preferenceDistinctIds = request.PreferenceIds.Distinct().ToList(); + + var preferences = (ICollection)await preferenceRepository.GetByRangeId(preferenceDistinctIds, ct: ct); + + if (preferences.Count != preferenceDistinctIds.Count) + { + return BadRequest(new ProblemDetails + { + Title = "Create Customer request canceled", + Detail = "One or more preference IDs are invalid." + }); + } + + var customer = CustomerMapper.ToCustomer(request, preferences); + customerRepository.Add(customer); + await unitOfWork.SaveChangesAsync(ct); + + return CreatedAtAction(nameof(GetById), new { id = customer.Id }, CustomerMapper.ToCustomerShortResponse(customer)); } /// @@ -52,7 +93,39 @@ public async Task> Update( [FromBody] CustomerUpdateRequest request, CancellationToken ct) { - throw new NotImplementedException(); + var customer = await customerRepository.GetById(id, withIncludes: true, ct: ct); + if (customer is null) + return NotFound(new ProblemDetails + { + Title = "Update Customer request canceled", + Detail = $"Employee with Id {id} not found." + }); + + var preferenceDistinctIds = request.PreferenceIds.Distinct().ToList(); + + var preferences = (ICollection)await preferenceRepository.GetByRangeId(preferenceDistinctIds, ct: ct); + + if (preferences.Count != preferenceDistinctIds.Count) + { + return BadRequest(new ProblemDetails + { + Title = "Update Customer request canceled", + Detail = "One or more preference IDs are invalid." + }); + } + + customer.FirstName = request.FirstName; + customer.LastName = request.LastName; + customer.Email = request.Email; + + // EF expects Add / Remove on navigation collections to track relationship changes correctly. + customer.Preferences.Clear(); + foreach (var p in preferences) + customer.Preferences.Add(p); + + await unitOfWork.SaveChangesAsync(ct); + + return Ok(CustomerMapper.ToCustomerShortResponse(customer)); } /// @@ -63,6 +136,19 @@ public async Task> Update( [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task Delete(Guid id, CancellationToken ct) { - throw new NotImplementedException(); + try + { + await customerRepository.Delete(id, ct); + } + catch (EntityNotFoundException) + { + return NotFound(new ProblemDetails + { + Title = "Delete Customer request canceled", + Detail = $"Customer with Id {id} not found." + }); + } + + return NoContent(); } } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs" index 6d6238a3a..46075ffa2 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs" @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using PromoCodeFactory.DataAccess.Repositories; using PromoCodeFactory.WebHost.Mapping; using PromoCodeFactory.WebHost.Models.Employees; @@ -7,10 +8,8 @@ namespace PromoCodeFactory.WebHost.Controllers; /// /// Сотрудники /// -public class EmployeesController( - IRepository employeeRepository, - IRepository roleRepository - ) : BaseController +public class EmployeesController(IRepository employeeRepository, IRepository roleRepository, + IUnitOfWork unitOfWork) : BaseController { /// /// Получить данные всех сотрудников @@ -37,7 +36,11 @@ public async Task> GetById([FromRoute] Guid id, C var employee = await employeeRepository.GetById(id, true, ct); if (employee is null) - return NotFound(); + return NotFound(new ProblemDetails + { + Title = "GetById Employee request canceled", + Detail = $"Employee with Id {id} not found." + }); return Ok(EmployeesMapper.ToEmployeeResponse(employee)); } @@ -48,14 +51,21 @@ public async Task> GetById([FromRoute] Guid id, C [HttpPost] [ProducesResponseType(typeof(EmployeeResponse), StatusCodes.Status201Created)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] - public async Task> Create([FromBody] EmployeeCreateRequest request, CancellationToken ct) + public async Task> Create([FromBody] EmployeeCreateRequest request, + CancellationToken ct) { var role = await roleRepository.GetById(request.RoleId, ct: ct); + if (role is null) - return BadRequest(new ProblemDetails { Title = "Invalid role", Detail = $"Role with Id {request.RoleId} not found." }); + return BadRequest(new ProblemDetails + { + Title = "Create Employee request canceled", + Detail = $"Role with Id {request.RoleId} not found." + }); var employee = EmployeesMapper.ToEmployee(request, role); - await employeeRepository.Add(employee, ct); + employeeRepository.Add(employee); + await unitOfWork.SaveChangesAsync(ct); return CreatedAtAction(nameof(GetById), new { id = employee.Id }, employee); } @@ -74,25 +84,26 @@ public async Task> Update( { var employee = await employeeRepository.GetById(id, ct: ct); if (employee is null) - return NotFound(); + return NotFound(new ProblemDetails + { + Title = "Update Employee request canceled", + Detail = $"Employee with Id {id} not found." + }); var role = await roleRepository.GetById(request.RoleId, ct: ct); if (role is null) - return BadRequest(new ProblemDetails { Title = "Invalid role", Detail = $"Role with Id {request.RoleId} not found." }); + return BadRequest(new ProblemDetails + { + Title = "Update Employee request canceled", + Detail = $"Role with Id {request.RoleId} not found." + }); employee.FirstName = request.FirstName; employee.LastName = request.LastName; employee.Email = request.Email; employee.Role = role; - try - { - await employeeRepository.Update(employee, ct); - } - catch (EntityNotFoundException) - { - return NotFound(); - } + await unitOfWork.SaveChangesAsync(ct); return Ok(EmployeesMapper.ToEmployeeResponse(employee)); } @@ -113,7 +124,11 @@ public async Task Delete( } catch (EntityNotFoundException) { - return NotFound(); + return NotFound(new ProblemDetails + { + Title = "Delete Employee request canceled", + Detail = $"Employee with Id {id} not found." + }); } return NoContent(); diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs" index 17f8880a2..d0e34d497 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs" @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Mvc; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Mapping; using PromoCodeFactory.WebHost.Models.Preferences; namespace PromoCodeFactory.WebHost.Controllers; @@ -6,7 +8,7 @@ namespace PromoCodeFactory.WebHost.Controllers; /// /// Предпочтения /// -public class PreferencesController : BaseController +public class PreferencesController (IRepository preferenceRepository) : BaseController { /// /// Получить все доступные предпочтения @@ -15,6 +17,10 @@ public class PreferencesController : BaseController [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task>> Get(CancellationToken ct) { - throw new NotImplementedException(); + var preferences = await preferenceRepository.GetAll(ct: ct); + + var responses = preferences.Select(PreferencesMapper.ToPreferenceShortResponse).ToList(); + + return Ok(responses); } } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PromoCodesController.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PromoCodesController.cs" index e55d5b0e8..d63d4b73f 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PromoCodesController.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PromoCodesController.cs" @@ -1,12 +1,17 @@ using Microsoft.AspNetCore.Mvc; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Mapping; using PromoCodeFactory.WebHost.Models.PromoCodes; +using System.Linq.Expressions; namespace PromoCodeFactory.WebHost.Controllers; /// /// Промокоды /// -public class PromoCodesController : BaseController +public class PromoCodesController(IRepository promoCodeRepository, + IRepository preferenceRepository, IRepository employeeRepository, + IRepository customerPromoCodeRepo, IUnitOfWork unitOfWork) : BaseController { /// /// Получить все промокоды @@ -15,7 +20,11 @@ public class PromoCodesController : BaseController [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task>> Get(CancellationToken ct) { - throw new NotImplementedException(); + var promoCodes = await promoCodeRepository.GetAll(withIncludes: true, ct); + + var responses = promoCodes.Select(PromoCodesMapper.ToPromoCodeShortResponse).ToList(); + + return Ok(responses); } /// @@ -26,7 +35,16 @@ public async Task>> Get(Cancell [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task> GetById(Guid id, CancellationToken ct) { - throw new NotImplementedException(); + var promoCode = await promoCodeRepository.GetById(id, withIncludes: true, ct); + + if (promoCode is null) + return NotFound(new ProblemDetails + { + Title = "GetById PromoCode request canceled", + Detail = $"PromoCode with Id {id} not found." + }); + + return Ok(PromoCodesMapper.ToPromoCodeShortResponse(promoCode)); } /// @@ -38,21 +56,85 @@ public async Task> GetById(Guid id, Cancell [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task> Create(PromoCodeCreateRequest request, CancellationToken ct) { - throw new NotImplementedException(); + var preference = await preferenceRepository.GetById(request.PreferenceId, withIncludes: true, ct); + + if (preference is null) + return BadRequest(new ProblemDetails + { + Title = "Create PromoCode request canceled", + Detail = $"No Preference with Id {request.PreferenceId}." + }); + + var customerIdsWithPreference = preference.Customers.Select(c => c.Id).ToList(); + + if (customerIdsWithPreference.Count == 0) + return NotFound(new ProblemDetails + { + Title = "Create PromoCode request canceled", + Detail = $"Customers with Preference Id {request.PreferenceId} not found." + }); + + var partnerManager = await employeeRepository.GetById(request.PartnerManagerId, withIncludes: true, ct); + + if (partnerManager is null) + return NotFound(new ProblemDetails + { + Title = "Create PromoCode request canceled", + Detail = $"PartnerManager with Id {request.PartnerManagerId} not found." + }); + + var promoNewCode = PromoCodesMapper.ToPromoCode(request, partnerManager, preference); + + promoCodeRepository.Add(promoNewCode); + //await unitOfWork.SaveChangesAsync(ct); + + var entities = CustomerPromoCodeMapper.ToListCustomerPromoCode(promoNewCode.Id, customerIdsWithPreference, + DateTimeOffset.Now); + + customerPromoCodeRepo.AddRange(entities); + + await unitOfWork.SaveChangesAsync(ct); + + return CreatedAtAction(nameof(GetById), new { id = promoNewCode.Id }, PromoCodesMapper.ToPromoCodeShortResponse(promoNewCode)); } /// - /// Применить промокод (отметить, что клиент использовал промокод) + /// Зарегистрировать использование промокода клиентом /// - [HttpPost("{id:guid}/apply")] + [HttpPost("{id:guid}/usages")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - public async Task Apply( + public async Task RegisterUsage( [FromRoute] Guid id, [FromBody] PromoCodeApplyRequest request, CancellationToken ct) { - throw new NotImplementedException(); + Expression> predicate = (rec) => + rec.PromoCodeId == id && rec.CustomerId == request.CustomerId; + + var response = await customerPromoCodeRepo.GetWhere(predicate, ct: ct); + + if (response.Count == 0) + { + return NotFound(new ProblemDetails + { + Title = "RegisterUsage request failed.", + Detail = "Usage for PromoCode [{id}] and Customer [{request.CustomerId}] not found." + }); + } + + if (response.Count > 1) + return BadRequest(new ProblemDetails + { + Title = "RegisterUsage request failed.", + Detail = $"Multiple usages found for PromoCode [{id}] and Customer [{request.CustomerId}]." + }); + + response.ElementAt(0).AppliedAt = DateTimeOffset.Now; + + await unitOfWork.SaveChangesAsync(ct); + + return NoContent(); } } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/CustomerMapper.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/CustomerMapper.cs" new file mode 100644 index 000000000..16c8c6e6b --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/CustomerMapper.cs" @@ -0,0 +1,68 @@ +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Models.Customers; +using PromoCodeFactory.WebHost.Models.PromoCodes; + +namespace PromoCodeFactory.WebHost.Mapping; + +public static class CustomerMapper +{ + public static CustomerResponse ToCustomerResponse(Customer customer, Dictionary promoCodesById) + { + var preferences = customer.Preferences + .Select(pref => PreferencesMapper.ToPreferenceShortResponse(pref)) + .ToList(); + + var promoCodes = customer.CustomerPromoCodes + .Select(custPrCode => ToCustomerPromoCodeResponse(custPrCode, promoCodesById[custPrCode.PromoCodeId])) + .ToList(); + + return new CustomerResponse( + customer.Id, + customer.FirstName, + customer.LastName, + customer.Email, + preferences, + promoCodes); + } + + public static CustomerShortResponse ToCustomerShortResponse(Customer customer) + { + var preferences = customer.Preferences + .Select(pref => PreferencesMapper.ToPreferenceShortResponse(pref)) + .ToList(); + + return new CustomerShortResponse( + customer.Id, + customer.FirstName, + customer.LastName, + customer.Email, + preferences); + } + + public static CustomerPromoCodeResponse ToCustomerPromoCodeResponse(CustomerPromoCode custPrCode, PromoCode prCode) + { + return new CustomerPromoCodeResponse( + prCode.Id, + prCode.Code, + prCode.ServiceInfo, + prCode.PartnerName, + prCode.BeginDate, + prCode.EndDate, + prCode.PartnerManager.Id, + prCode.Preference.Id, + custPrCode.CreatedAt, + custPrCode.AppliedAt); + } + + public static Customer ToCustomer(CustomerCreateRequest request, ICollection preferences) + { + return new Customer + { + Id = Guid.NewGuid(), + FirstName = request.FirstName, + LastName = request.LastName, + Email = request.Email, + Preferences = preferences + }; + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/CustomerPromoCodeMapper.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/CustomerPromoCodeMapper.cs" new file mode 100644 index 000000000..00a5357fb --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/CustomerPromoCodeMapper.cs" @@ -0,0 +1,17 @@ +using PromoCodeFactory.Core.Domain.PromoCodeManagement; + +namespace PromoCodeFactory.WebHost.Mapping; + +public static class CustomerPromoCodeMapper +{ + public static IReadOnlyList ToListCustomerPromoCode(Guid promoCodeId, IReadOnlyList customerIds, + DateTimeOffset createdAt) + { + return customerIds.Select(customerId => new CustomerPromoCode + { + CustomerId = customerId, + PromoCodeId = promoCodeId, + CreatedAt = createdAt + }).ToList(); + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/PromoCodesMapper.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/PromoCodesMapper.cs" index 44f8ce5d3..e88035bd2 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/PromoCodesMapper.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/PromoCodesMapper.cs" @@ -17,4 +17,19 @@ public static PromoCodeShortResponse ToPromoCodeShortResponse(PromoCode promoCod promoCode.PartnerManager.Id, promoCode.Preference.Id); } + + public static PromoCode ToPromoCode(PromoCodeCreateRequest createRequest, Employee partnerManager, + Preference preference) + { + return new PromoCode() { + Id = Guid.NewGuid(), + Code = createRequest.Code, + ServiceInfo = createRequest.ServiceInfo, + PartnerName = createRequest.PartnerName, + BeginDate = createRequest.BeginDate, + EndDate = createRequest.EndDate, + PartnerManager = partnerManager, + Preference = preference + }; + } } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Program.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Program.cs" index 7e082cd68..46b3d6f81 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Program.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Program.cs" @@ -1,32 +1,42 @@ using PromoCodeFactory.DataAccess; -var builder = WebApplication.CreateBuilder(); +public class Program +{ + public static async Task Main(string[] args) + { + var builder = WebApplication.CreateBuilder(); -builder.Services.AddEfDataAccess(); + var services = builder.Services; + var environment = builder.Environment; + + services.AddEfDataAccess(environment.ContentRootPath); + + services.AddProblemDetails(); + services.AddRouting(options => + { + options.LowercaseUrls = true; + }); + services.AddControllers(); -builder.Services.AddProblemDetails(); -builder.Services.AddRouting(options => -{ - options.LowercaseUrls = true; -}); -builder.Services.AddControllers(); -builder.Services.AddOpenApi(builder.Environment); + services.AddOpenApi(environment); -var app = builder.Build(); + var app = builder.Build(); -app.UseExceptionHandler(); + app.UseExceptionHandler(); -app.MapOpenApi(); -app.MapSwaggerUI(); + app.MapOpenApi(); + app.MapSwaggerUI(); -app.UseHttpsRedirection(); + app.UseHttpsRedirection(); -app.MapControllers(); + app.MapControllers(); -app.MigrateDatabase(); + app.MigrateDatabase(); -if (app.Environment.IsDevelopment()) - await app.SeedDatabase(); + if (app.Environment.IsDevelopment()) + await app.SeedDatabase(); -app.Run(); + app.Run(); + } +} diff --git a/Homeworks/dotnet-tools.json b/Homeworks/dotnet-tools.json new file mode 100644 index 000000000..b6f4c2f6c --- /dev/null +++ b/Homeworks/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "10.0.5", + "commands": [ + "dotnet-ef" + ], + "rollForward": false + } + } +} \ No newline at end of file