From fc6d32bad78ebaacd88192da02891333de442efb Mon Sep 17 00:00:00 2001 From: HosseinJavadi Date: Mon, 1 Sep 2025 22:42:10 +0430 Subject: [PATCH] CreateLanguageCommand --- .../Exceptions/BaseException.cs | 8 +++++ .../LanguageTitleAlreadyExistsException.cs | 15 +++++++++ .../IRepositoryBase.cs | 18 ++++++----- .../TypeIdValueBase.cs | 9 ++---- .../RepositoryBase.cs | 6 ++++ .../Commands/LanguageCommands.cs | 4 +-- .../CommandHandlers/LanguageCommandHandler.cs | 18 +++++------ VizhTalk.Learning.Application/IUnitOfWork.cs | 4 ++- .../VizhTalk.Learning.Application.csproj | 4 +++ VizhTalk.Learning.Domain/Entities/Language.cs | 2 ++ .../ValueObjects/LanguageTitle.cs | 6 +++- .../Configurations/LanguageConfiguration.cs | 7 ++-- .../LearningStartup.cs | 2 +- ....cs => 20250901180950_Initial.Designer.cs} | 32 +++++++++++++++---- ...7_Initial.cs => 20250901180950_Initial.cs} | 25 ++++++++++++++- .../LearningDbContextModelSnapshot.cs | 30 ++++++++++++++--- .../UnitOfWork.cs | 10 +++--- 17 files changed, 151 insertions(+), 49 deletions(-) create mode 100644 VizhTalk.BuildingBlocks.Application/Exceptions/BaseException.cs create mode 100644 VizhTalk.BuildingBlocks.Application/Exceptions/LanguageTitleAlreadyExistsException.cs rename VizhTalk.Learning.Infrastructure/Migrations/{20250901155327_Initial.Designer.cs => 20250901180950_Initial.Designer.cs} (60%) rename VizhTalk.Learning.Infrastructure/Migrations/{20250901155327_Initial.cs => 20250901180950_Initial.cs} (58%) diff --git a/VizhTalk.BuildingBlocks.Application/Exceptions/BaseException.cs b/VizhTalk.BuildingBlocks.Application/Exceptions/BaseException.cs new file mode 100644 index 0000000..97cca39 --- /dev/null +++ b/VizhTalk.BuildingBlocks.Application/Exceptions/BaseException.cs @@ -0,0 +1,8 @@ + + +namespace VizhTalk.BuildingBlocks.Application.Exceptions +{ + public abstract class BaseException(string message) : Exception(message) + { + } +} diff --git a/VizhTalk.BuildingBlocks.Application/Exceptions/LanguageTitleAlreadyExistsException.cs b/VizhTalk.BuildingBlocks.Application/Exceptions/LanguageTitleAlreadyExistsException.cs new file mode 100644 index 0000000..4e7aa88 --- /dev/null +++ b/VizhTalk.BuildingBlocks.Application/Exceptions/LanguageTitleAlreadyExistsException.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VizhTalk.BuildingBlocks.Application.Exceptions +{ + public class LanguageTitleAlreadyExistsException : BaseException + { + public LanguageTitleAlreadyExistsException() : base("title already exists") + { + } + } +} diff --git a/VizhTalk.BuildingBlocks.Domain/IRepositoryBase.cs b/VizhTalk.BuildingBlocks.Domain/IRepositoryBase.cs index 2299cce..7771d3d 100644 --- a/VizhTalk.BuildingBlocks.Domain/IRepositoryBase.cs +++ b/VizhTalk.BuildingBlocks.Domain/IRepositoryBase.cs @@ -1,12 +1,14 @@ -namespace VizhTalk.BuildingBlocks.Domain; +using System.Linq.Expressions; -public interface IRepositoryBase +namespace VizhTalk.BuildingBlocks.Domain; + +public interface IRepositoryBase where T : Entity where TId : ITypeIdValueBase { - Task AddAsync(T entity,CancellationToken cancellationToken = default); - Task UpdateAsync(T entity,CancellationToken cancellationToken = default); - Task DeleteAsync(T entity,CancellationToken cancellationToken = default); - Task DeleteById(TId id,CancellationToken cancellationToken = default); - Task GetById(TId id,CancellationToken cancellationToken = default); - + Task AddAsync(T entity, CancellationToken cancellationToken = default); + Task UpdateAsync(T entity, CancellationToken cancellationToken = default); + Task DeleteAsync(T entity, CancellationToken cancellationToken = default); + Task DeleteById(TId id, CancellationToken cancellationToken = default); + Task GetById(TId id, CancellationToken cancellationToken = default); + Task Exists(Expression> predicate, CancellationToken cancellationToken = default); } diff --git a/VizhTalk.BuildingBlocks.Domain/TypeIdValueBase.cs b/VizhTalk.BuildingBlocks.Domain/TypeIdValueBase.cs index 5c3ea94..ce33d09 100644 --- a/VizhTalk.BuildingBlocks.Domain/TypeIdValueBase.cs +++ b/VizhTalk.BuildingBlocks.Domain/TypeIdValueBase.cs @@ -20,9 +20,7 @@ public abstract class TypedIdValueBase : IEquatable> , IT public override bool Equals(object? obj) { if (ReferenceEquals(null, obj)) - { return false; - } return obj is TypedIdValueBase other && Equals(other); } @@ -34,13 +32,12 @@ public abstract class TypedIdValueBase : IEquatable> , IT public bool Equals(TypedIdValueBase? other) { + if(other == null) + return false; + return EqualityComparer.Default.Equals(this.Value, other.Value); } - public bool Equals(ITypeIdValueBase? other) - { - throw new NotImplementedException(); - } public static bool operator ==(TypedIdValueBase? obj1, TypedIdValueBase? obj2) { diff --git a/VizhTalk.BuildingBlocks.Infrastructure/RepositoryBase.cs b/VizhTalk.BuildingBlocks.Infrastructure/RepositoryBase.cs index 85bd06c..55a911a 100644 --- a/VizhTalk.BuildingBlocks.Infrastructure/RepositoryBase.cs +++ b/VizhTalk.BuildingBlocks.Infrastructure/RepositoryBase.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; using VizhTalk.BuildingBlocks.Domain; @@ -19,6 +20,8 @@ namespace VizhTalk.BuildingBlocks.Infrastructure { this._dbContext = dbContext; } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -46,6 +49,9 @@ namespace VizhTalk.BuildingBlocks.Infrastructure entity.SetDeleted(true); } + public async Task Exists(Expression> predicate, CancellationToken cancellationToken = default) + => await _dbContext.Set().Where(predicate).AnyAsync(cancellationToken); + public async Task GetById(TId id, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/VizhTalk.Learning.Application/CommandHandlers/Commands/LanguageCommands.cs b/VizhTalk.Learning.Application/CommandHandlers/Commands/LanguageCommands.cs index 1f55360..8d216fc 100644 --- a/VizhTalk.Learning.Application/CommandHandlers/Commands/LanguageCommands.cs +++ b/VizhTalk.Learning.Application/CommandHandlers/Commands/LanguageCommands.cs @@ -3,5 +3,5 @@ using VizhTalk.Grpc.Learning.Language; namespace VizhTalk.Learning.Application.CommandHandlers.Commands; -public record CreateLanguageCommand(CreateLanguageRequest model) : ICommand; -public record UpdateLanguageCommand(UpdateLanguageRequest model) : ICommand; +public record CreateLanguageCommand(CreateLanguageRequest Model) : ICommand; +public record UpdateLanguageCommand(UpdateLanguageRequest Model) : ICommand; diff --git a/VizhTalk.Learning.Application/CommandHandlers/LanguageCommandHandler.cs b/VizhTalk.Learning.Application/CommandHandlers/LanguageCommandHandler.cs index f2257e6..2786e90 100644 --- a/VizhTalk.Learning.Application/CommandHandlers/LanguageCommandHandler.cs +++ b/VizhTalk.Learning.Application/CommandHandlers/LanguageCommandHandler.cs @@ -1,5 +1,6 @@ using Mapster; using VizhTalk.BuildingBlocks.Application.Configuration.Commnad; +using VizhTalk.BuildingBlocks.Application.Exceptions; using VizhTalk.Grpc.Learning.Language; using VizhTalk.Learning.Application.CommandHandlers.Commands; using VizhTalk.Learning.Domain.Entities; @@ -11,17 +12,14 @@ namespace VizhTalk.Learning.Application.CommandHandlers { public async Task Handle(CreateLanguageCommand request, CancellationToken cancellationToken) { - try - { - var language = Language.CreateNew(new LanguageTitle(request.model.Title)); - await unitOfWork.Language.AddAsync(language, cancellationToken); - return language.Adapt(); - } - catch (Exception ex) - { + var title = request.Model.Title.Trim(); + if (await unitOfWork.Language.Exists(x => x.Title.Value == title)) + throw new LanguageTitleAlreadyExistsException(); - throw; - } + var language = Language.CreateNew(new LanguageTitle(title)); + await unitOfWork.Language.AddAsync(language, cancellationToken); + await unitOfWork.SaveChangeAsync(cancellationToken); + return language.Adapt(); } } diff --git a/VizhTalk.Learning.Application/IUnitOfWork.cs b/VizhTalk.Learning.Application/IUnitOfWork.cs index 1ad0f85..a58b289 100644 --- a/VizhTalk.Learning.Application/IUnitOfWork.cs +++ b/VizhTalk.Learning.Application/IUnitOfWork.cs @@ -5,5 +5,7 @@ namespace VizhTalk.Learning.Application; public interface IUnitOfWork : IUnitOfWorkBase { - public ILanguageRepository Language { get; } + ILanguageRepository Language { get; } + + Task SaveChangeAsync(CancellationToken cancellationToken = default); } diff --git a/VizhTalk.Learning.Application/VizhTalk.Learning.Application.csproj b/VizhTalk.Learning.Application/VizhTalk.Learning.Application.csproj index 2d76b47..c1cfea1 100644 --- a/VizhTalk.Learning.Application/VizhTalk.Learning.Application.csproj +++ b/VizhTalk.Learning.Application/VizhTalk.Learning.Application.csproj @@ -12,4 +12,8 @@ + + + + diff --git a/VizhTalk.Learning.Domain/Entities/Language.cs b/VizhTalk.Learning.Domain/Entities/Language.cs index e29e084..ab49c7d 100644 --- a/VizhTalk.Learning.Domain/Entities/Language.cs +++ b/VizhTalk.Learning.Domain/Entities/Language.cs @@ -18,4 +18,6 @@ public class Language : Entity, IAggregateRoot public static Language CreateNew(LanguageTitle title) => new(title); + + } diff --git a/VizhTalk.Learning.Domain/ValueObjects/LanguageTitle.cs b/VizhTalk.Learning.Domain/ValueObjects/LanguageTitle.cs index 87cf82b..02c7ee5 100644 --- a/VizhTalk.Learning.Domain/ValueObjects/LanguageTitle.cs +++ b/VizhTalk.Learning.Domain/ValueObjects/LanguageTitle.cs @@ -5,6 +5,10 @@ namespace VizhTalk.Learning.Domain.ValueObjects { public class LanguageTitle : ValueObject { + public LanguageTitle() + { + + } public LanguageTitle(string title) { Guard.NullOrEmpty(title, nameof(LanguageTitle)); @@ -12,6 +16,6 @@ namespace VizhTalk.Learning.Domain.ValueObjects Value = title; } - public string Value { get; set; } + public string Value { get; private set; } } } diff --git a/VizhTalk.Learning.Infrastructure/EF/Configurations/LanguageConfiguration.cs b/VizhTalk.Learning.Infrastructure/EF/Configurations/LanguageConfiguration.cs index 85ce3e3..b7c1c29 100644 --- a/VizhTalk.Learning.Infrastructure/EF/Configurations/LanguageConfiguration.cs +++ b/VizhTalk.Learning.Infrastructure/EF/Configurations/LanguageConfiguration.cs @@ -20,9 +20,12 @@ namespace VizhTalk.Learning.Infrastructure.EF.Configurations .ValueGeneratedOnAdd() .HasConversion(x => x.Value, x => new Domain.TypeIds.LanguageId(x)); - builder.Property(x => x.Title) - .HasConversion(s => s.Value, s => new Domain.ValueObjects.LanguageTitle(s)) + builder.OwnsOne(x => x.Title, op => + { + op.Property(x => x.Value) + .HasColumnName("Title") .HasMaxLength(60); + }); } } diff --git a/VizhTalk.Learning.Infrastructure/LearningStartup.cs b/VizhTalk.Learning.Infrastructure/LearningStartup.cs index 63977b3..d24736b 100644 --- a/VizhTalk.Learning.Infrastructure/LearningStartup.cs +++ b/VizhTalk.Learning.Infrastructure/LearningStartup.cs @@ -27,7 +27,7 @@ public static class LearningStartup { op.UseSqlServer(connectionString); }); - services.AddScoped(); + services.AddScoped(sp => sp.GetService()); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/VizhTalk.Learning.Infrastructure/Migrations/20250901155327_Initial.Designer.cs b/VizhTalk.Learning.Infrastructure/Migrations/20250901180950_Initial.Designer.cs similarity index 60% rename from VizhTalk.Learning.Infrastructure/Migrations/20250901155327_Initial.Designer.cs rename to VizhTalk.Learning.Infrastructure/Migrations/20250901180950_Initial.Designer.cs index f87a73f..37683bd 100644 --- a/VizhTalk.Learning.Infrastructure/Migrations/20250901155327_Initial.Designer.cs +++ b/VizhTalk.Learning.Infrastructure/Migrations/20250901180950_Initial.Designer.cs @@ -11,7 +11,7 @@ using VizhTalk.Learning.Infrastructure.EF; namespace VizhTalk.Learning.Infrastructure.Migrations { [DbContext(typeof(LearningDbContext))] - [Migration("20250901155327_Initial")] + [Migration("20250901180950_Initial")] partial class Initial { /// @@ -39,15 +39,35 @@ namespace VizhTalk.Learning.Infrastructure.Migrations b.Property("IsDeleted") .HasColumnType("bit"); - b.Property("Title") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - b.HasKey("Id"); b.ToTable("Languages", "Learning"); }); + + modelBuilder.Entity("VizhTalk.Learning.Domain.Entities.Language", b => + { + b.OwnsOne("VizhTalk.Learning.Domain.ValueObjects.LanguageTitle", "Title", b1 => + { + b1.Property("LanguageId") + .HasColumnType("bigint"); + + b1.Property("Value") + .IsRequired() + .HasMaxLength(60) + .HasColumnType("nvarchar(60)") + .HasColumnName("Title"); + + b1.HasKey("LanguageId"); + + b1.ToTable("LanguageTitles", "Learning"); + + b1.WithOwner() + .HasForeignKey("LanguageId"); + }); + + b.Navigation("Title") + .IsRequired(); + }); #pragma warning restore 612, 618 } } diff --git a/VizhTalk.Learning.Infrastructure/Migrations/20250901155327_Initial.cs b/VizhTalk.Learning.Infrastructure/Migrations/20250901180950_Initial.cs similarity index 58% rename from VizhTalk.Learning.Infrastructure/Migrations/20250901155327_Initial.cs rename to VizhTalk.Learning.Infrastructure/Migrations/20250901180950_Initial.cs index 902938a..3034b25 100644 --- a/VizhTalk.Learning.Infrastructure/Migrations/20250901155327_Initial.cs +++ b/VizhTalk.Learning.Infrastructure/Migrations/20250901180950_Initial.cs @@ -20,7 +20,6 @@ namespace VizhTalk.Learning.Infrastructure.Migrations { Id = table.Column(type: "bigint", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), - Title = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: false), IsDeleted = table.Column(type: "bit", nullable: false), IsActive = table.Column(type: "bit", nullable: false) }, @@ -28,11 +27,35 @@ namespace VizhTalk.Learning.Infrastructure.Migrations { table.PrimaryKey("PK_Languages", x => x.Id); }); + + migrationBuilder.CreateTable( + name: "LanguageTitles", + schema: "Learning", + columns: table => new + { + LanguageId = table.Column(type: "bigint", nullable: false), + Title = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LanguageTitles", x => x.LanguageId); + table.ForeignKey( + name: "FK_LanguageTitles_Languages_LanguageId", + column: x => x.LanguageId, + principalSchema: "Learning", + principalTable: "Languages", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); } /// protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropTable( + name: "LanguageTitles", + schema: "Learning"); + migrationBuilder.DropTable( name: "Languages", schema: "Learning"); diff --git a/VizhTalk.Learning.Infrastructure/Migrations/LearningDbContextModelSnapshot.cs b/VizhTalk.Learning.Infrastructure/Migrations/LearningDbContextModelSnapshot.cs index 24114a4..51e4d35 100644 --- a/VizhTalk.Learning.Infrastructure/Migrations/LearningDbContextModelSnapshot.cs +++ b/VizhTalk.Learning.Infrastructure/Migrations/LearningDbContextModelSnapshot.cs @@ -36,15 +36,35 @@ namespace VizhTalk.Learning.Infrastructure.Migrations b.Property("IsDeleted") .HasColumnType("bit"); - b.Property("Title") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - b.HasKey("Id"); b.ToTable("Languages", "Learning"); }); + + modelBuilder.Entity("VizhTalk.Learning.Domain.Entities.Language", b => + { + b.OwnsOne("VizhTalk.Learning.Domain.ValueObjects.LanguageTitle", "Title", b1 => + { + b1.Property("LanguageId") + .HasColumnType("bigint"); + + b1.Property("Value") + .IsRequired() + .HasMaxLength(60) + .HasColumnType("nvarchar(60)") + .HasColumnName("Title"); + + b1.HasKey("LanguageId"); + + b1.ToTable("LanguageTitles", "Learning"); + + b1.WithOwner() + .HasForeignKey("LanguageId"); + }); + + b.Navigation("Title") + .IsRequired(); + }); #pragma warning restore 612, 618 } } diff --git a/VizhTalk.Learning.Infrastructure/UnitOfWork.cs b/VizhTalk.Learning.Infrastructure/UnitOfWork.cs index 0499d8f..252e320 100644 --- a/VizhTalk.Learning.Infrastructure/UnitOfWork.cs +++ b/VizhTalk.Learning.Infrastructure/UnitOfWork.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using VizhTalk.BuildingBlocks.Infrastructure; +using VizhTalk.BuildingBlocks.Infrastructure; using VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching; using VizhTalk.Learning.Application; using VizhTalk.Learning.Domain.Repositories; @@ -26,5 +21,8 @@ namespace VizhTalk.Learning.Infrastructure return _language; } } + + public async Task SaveChangeAsync(CancellationToken cancellationToken = default) + => await learningDbContext.SaveChangesAsync(cancellationToken); } }