From 57afe213e4e202b9c85f09eaf8cab7dd97b2251a Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Sat, 28 Feb 2026 03:12:42 +0700 Subject: [PATCH] fix(merchant-service): fix EF Core unmapped property errors in repositories - Changed repository LINQ queries to use EF.Property() for backing fields - Expression-bodied properties cannot be auto-mapped by EF Core - Fixed StatusId comparison in CreateShopCommandHandler (Status nav is null) - Updated EntityTypeConfiguration comments explaining Ignore pattern --- .../Commands/Shops/CreateShopCommandHandler.cs | 8 ++++---- .../MerchantEntityTypeConfiguration.cs | 6 ++++-- .../MerchantStaffEntityTypeConfiguration.cs | 13 +++++++------ .../ShopEntityTypeConfiguration.cs | 17 +++++++++-------- .../Repositories/MerchantRepository.cs | 14 +++++++------- .../Repositories/ShopRepository.cs | 18 +++++++++--------- 6 files changed, 40 insertions(+), 36 deletions(-) diff --git a/services/merchant-service-net/src/MerchantService.API/Application/Commands/Shops/CreateShopCommandHandler.cs b/services/merchant-service-net/src/MerchantService.API/Application/Commands/Shops/CreateShopCommandHandler.cs index 69bfb125..6bc99f44 100644 --- a/services/merchant-service-net/src/MerchantService.API/Application/Commands/Shops/CreateShopCommandHandler.cs +++ b/services/merchant-service-net/src/MerchantService.API/Application/Commands/Shops/CreateShopCommandHandler.cs @@ -51,9 +51,9 @@ public class CreateShopCommandHandler : IRequestHandler m.StatusId).HasDatabaseName("ix_merchants_status"); - // EN: Ignore navigation properties - // VI: Bỏ qua navigation properties + // EN: Ignore expression-bodied properties (EF Core can't map them) + // EN: Repository queries use EF.Property() to access backing fields directly. + // VI: Ignore expression-bodied properties (EF Core không thể map chúng) + // VI: Repository queries sử dụng EF.Property() để truy cập backing fields trực tiếp. builder.Ignore(m => m.Type); builder.Ignore(m => m.Status); builder.Ignore(m => m.VerificationStatus); diff --git a/services/merchant-service-net/src/MerchantService.Infrastructure/EntityConfigurations/MerchantStaffEntityTypeConfiguration.cs b/services/merchant-service-net/src/MerchantService.Infrastructure/EntityConfigurations/MerchantStaffEntityTypeConfiguration.cs index eaf02648..ceff6379 100644 --- a/services/merchant-service-net/src/MerchantService.Infrastructure/EntityConfigurations/MerchantStaffEntityTypeConfiguration.cs +++ b/services/merchant-service-net/src/MerchantService.Infrastructure/EntityConfigurations/MerchantStaffEntityTypeConfiguration.cs @@ -91,8 +91,9 @@ public class MerchantStaffEntityTypeConfiguration : IEntityTypeConfiguration() to access backing fields directly. + // VI: Ignore expression-bodied properties (EF Core không thể map chúng) builder.Ignore(s => s.Role); builder.Ignore(s => s.Status); builder.Ignore(s => s.Permissions); @@ -159,8 +160,8 @@ public class DeviceTokenEntityTypeConfiguration : IEntityTypeConfiguration d.StaffId).HasDatabaseName("ix_device_tokens_staff_id"); builder.HasIndex("_deviceId").HasDatabaseName("ix_device_tokens_device_id"); - // EN: Ignore navigation properties - // VI: Bỏ qua navigation properties + // EN: Ignore expression-bodied properties + // VI: Ignore expression-bodied properties builder.Ignore(d => d.DeviceId); builder.Ignore(d => d.DeviceName); builder.Ignore(d => d.FcmToken); @@ -218,8 +219,8 @@ public class ShopMemberEntityTypeConfiguration : IEntityTypeConfiguration m.StaffId).HasDatabaseName("ix_shop_members_staff_id"); builder.HasIndex("_shopId").HasDatabaseName("ix_shop_members_shop_id"); - // EN: Ignore navigation properties - // VI: Bỏ qua navigation properties + // EN: Ignore expression-bodied properties + // VI: Ignore expression-bodied properties builder.Ignore(m => m.Role); builder.Ignore(m => m.CustomPermissions); builder.Ignore(m => m.ShopId); diff --git a/services/merchant-service-net/src/MerchantService.Infrastructure/EntityConfigurations/ShopEntityTypeConfiguration.cs b/services/merchant-service-net/src/MerchantService.Infrastructure/EntityConfigurations/ShopEntityTypeConfiguration.cs index bb67f135..99324db2 100644 --- a/services/merchant-service-net/src/MerchantService.Infrastructure/EntityConfigurations/ShopEntityTypeConfiguration.cs +++ b/services/merchant-service-net/src/MerchantService.Infrastructure/EntityConfigurations/ShopEntityTypeConfiguration.cs @@ -128,13 +128,15 @@ public class ShopEntityTypeConfiguration : IEntityTypeConfiguration builder.HasIndex(s => s.StatusId).HasDatabaseName("ix_shops_status"); builder.HasIndex(s => s.CategoryId).HasDatabaseName("ix_shops_category"); - // EN: Ignore calculated properties - // VI: Bỏ qua các properties được tính toán + // EN: Ignore expression-bodied properties (EF Core can't map them) + // EN: Repository queries use EF.Property() to access backing fields directly. + // VI: Ignore expression-bodied properties (EF Core không thể map chúng) builder.Ignore(s => s.Type); builder.Ignore(s => s.Category); builder.Ignore(s => s.Status); builder.Ignore(s => s.ContactInfo); builder.Ignore(s => s.OperatingHours); + builder.Ignore(s => s.Features); builder.Ignore(s => s.Name); builder.Ignore(s => s.Slug); builder.Ignore(s => s.Description); @@ -144,7 +146,6 @@ public class ShopEntityTypeConfiguration : IEntityTypeConfiguration builder.Ignore(s => s.CreatedAt); builder.Ignore(s => s.UpdatedAt); builder.Ignore(s => s.IsDeleted); - builder.Ignore(s => s.Features); } } @@ -262,14 +263,14 @@ public class ShopBranchEntityTypeConfiguration : IEntityTypeConfiguration b.ShopId).HasDatabaseName("ix_shop_branches_shop_id"); - // EN: Ignore navigation properties - // VI: Bỏ qua navigation properties - builder.Ignore(b => b.Name); - builder.Ignore(b => b.Code); - builder.Ignore(b => b.Phone); + // EN: Ignore expression-bodied properties (repository queries use EF.Property) + // VI: Ignore expression-bodied properties (repository queries dùng EF.Property) builder.Ignore(b => b.Address); builder.Ignore(b => b.Location); builder.Ignore(b => b.OperatingHours); + builder.Ignore(b => b.Name); + builder.Ignore(b => b.Code); + builder.Ignore(b => b.Phone); builder.Ignore(b => b.IsActive); builder.Ignore(b => b.CreatedAt); builder.Ignore(b => b.UpdatedAt); diff --git a/services/merchant-service-net/src/MerchantService.Infrastructure/Repositories/MerchantRepository.cs b/services/merchant-service-net/src/MerchantService.Infrastructure/Repositories/MerchantRepository.cs index e2619e00..40337f4f 100644 --- a/services/merchant-service-net/src/MerchantService.Infrastructure/Repositories/MerchantRepository.cs +++ b/services/merchant-service-net/src/MerchantService.Infrastructure/Repositories/MerchantRepository.cs @@ -26,29 +26,29 @@ public class MerchantRepository : IMerchantRepository public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) { return await _context.Merchants - .FirstOrDefaultAsync(m => m.Id == id && !m.IsDeleted, cancellationToken); + .FirstOrDefaultAsync(m => m.Id == id && !EF.Property(m, "_isDeleted"), cancellationToken); } /// public async Task GetByUserIdAsync(Guid userId, CancellationToken cancellationToken = default) { return await _context.Merchants - .FirstOrDefaultAsync(m => m.UserId == userId && !m.IsDeleted, cancellationToken); + .FirstOrDefaultAsync(m => EF.Property(m, "_userId") == userId && !EF.Property(m, "_isDeleted"), cancellationToken); } /// public async Task ExistsByUserIdAsync(Guid userId, CancellationToken cancellationToken = default) { return await _context.Merchants - .AnyAsync(m => m.UserId == userId && !m.IsDeleted, cancellationToken); + .AnyAsync(m => EF.Property(m, "_userId") == userId && !EF.Property(m, "_isDeleted"), cancellationToken); } /// public async Task> GetAllAsync(int pageNumber, int pageSize, CancellationToken cancellationToken = default) { return await _context.Merchants - .Where(m => !m.IsDeleted) - .OrderByDescending(m => m.CreatedAt) + .Where(m => !EF.Property(m, "_isDeleted")) + .OrderByDescending(m => EF.Property(m, "_createdAt")) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToListAsync(cancellationToken); @@ -58,8 +58,8 @@ public class MerchantRepository : IMerchantRepository public async Task> GetByStatusAsync(MerchantStatus status, CancellationToken cancellationToken = default) { return await _context.Merchants - .Where(m => m.StatusId == status.Id && !m.IsDeleted) - .OrderByDescending(m => m.CreatedAt) + .Where(m => m.StatusId == status.Id && !EF.Property(m, "_isDeleted")) + .OrderByDescending(m => EF.Property(m, "_createdAt")) .ToListAsync(cancellationToken); } diff --git a/services/merchant-service-net/src/MerchantService.Infrastructure/Repositories/ShopRepository.cs b/services/merchant-service-net/src/MerchantService.Infrastructure/Repositories/ShopRepository.cs index 3af9cc68..91ed1296 100644 --- a/services/merchant-service-net/src/MerchantService.Infrastructure/Repositories/ShopRepository.cs +++ b/services/merchant-service-net/src/MerchantService.Infrastructure/Repositories/ShopRepository.cs @@ -26,7 +26,7 @@ public class ShopRepository : IShopRepository public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) { return await _context.Shops - .FirstOrDefaultAsync(s => s.Id == id && !s.IsDeleted, cancellationToken); + .FirstOrDefaultAsync(s => s.Id == id && !EF.Property(s, "_isDeleted"), cancellationToken); } /// @@ -34,29 +34,29 @@ public class ShopRepository : IShopRepository { return await _context.Shops .Include(s => s.Branches) - .FirstOrDefaultAsync(s => s.Id == id && !s.IsDeleted, cancellationToken); + .FirstOrDefaultAsync(s => s.Id == id && !EF.Property(s, "_isDeleted"), cancellationToken); } /// public async Task GetBySlugAsync(string slug, CancellationToken cancellationToken = default) { return await _context.Shops - .FirstOrDefaultAsync(s => s.Slug == slug.ToLowerInvariant() && !s.IsDeleted, cancellationToken); + .FirstOrDefaultAsync(s => EF.Property(s, "_slug") == slug.ToLowerInvariant() && !EF.Property(s, "_isDeleted"), cancellationToken); } /// public async Task SlugExistsAsync(string slug, CancellationToken cancellationToken = default) { return await _context.Shops - .AnyAsync(s => s.Slug == slug.ToLowerInvariant(), cancellationToken); + .AnyAsync(s => EF.Property(s, "_slug") == slug.ToLowerInvariant(), cancellationToken); } /// public async Task> GetByMerchantIdAsync(Guid merchantId, CancellationToken cancellationToken = default) { return await _context.Shops - .Where(s => s.MerchantId == merchantId && !s.IsDeleted) - .OrderByDescending(s => s.CreatedAt) + .Where(s => EF.Property(s, "_merchantId") == merchantId && !EF.Property(s, "_isDeleted")) + .OrderByDescending(s => EF.Property(s, "_createdAt")) .ToListAsync(cancellationToken); } @@ -64,8 +64,8 @@ public class ShopRepository : IShopRepository public async Task> GetByCategoryAsync(BusinessCategory category, int pageNumber, int pageSize, CancellationToken cancellationToken = default) { return await _context.Shops - .Where(s => s.CategoryId == category.Id && !s.IsDeleted && s.StatusId == ShopStatus.Active.Id) - .OrderByDescending(s => s.CreatedAt) + .Where(s => s.CategoryId == category.Id && !EF.Property(s, "_isDeleted") && s.StatusId == ShopStatus.Active.Id) + .OrderByDescending(s => EF.Property(s, "_createdAt")) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToListAsync(cancellationToken); @@ -88,7 +88,7 @@ public class ShopRepository : IShopRepository { return await _context.Shops .Include(s => s.Branches) - .Where(s => !s.IsDeleted && s.StatusId == ShopStatus.Active.Id) + .Where(s => !EF.Property(s, "_isDeleted") && s.StatusId == ShopStatus.Active.Id) .ToListAsync(cancellationToken); } }