fix(merchant-service): fix EF Core unmapped property errors in repositories
- Changed repository LINQ queries to use EF.Property<T>() 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
This commit is contained in:
@@ -51,9 +51,9 @@ public class CreateShopCommandHandler : IRequestHandler<CreateShopCommand, Creat
|
||||
throw new DomainException("Merchant not found. Please register as a merchant first.");
|
||||
}
|
||||
|
||||
// EN: Check if merchant is active
|
||||
// VI: Kiểm tra merchant có active không
|
||||
if (merchant.Status != MerchantStatus.Active)
|
||||
// EN: Check if merchant is active (compare StatusId since Status navigation is not loaded by EF)
|
||||
// VI: Kiểm tra merchant có active không (compare StatusId vì Status navigation không được load bởi EF)
|
||||
if (merchant.StatusId != MerchantStatus.Active.Id)
|
||||
{
|
||||
throw new DomainException("Only active merchants can create shops. Please wait for approval.");
|
||||
}
|
||||
@@ -130,7 +130,7 @@ public class CreateShopCommandHandler : IRequestHandler<CreateShopCommand, Creat
|
||||
shop.Id,
|
||||
shop.Name,
|
||||
shop.Slug,
|
||||
shop.Status.Name
|
||||
"Draft" // EN: New shops are always Draft / VI: Shop mới luôn là Draft
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,8 +120,10 @@ public class MerchantEntityTypeConfiguration : IEntityTypeConfiguration<Merchant
|
||||
builder.HasIndex("_userId").HasDatabaseName("ix_merchants_user_id");
|
||||
builder.HasIndex(m => 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<T>() 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<T>() để truy cập backing fields trực tiếp.
|
||||
builder.Ignore(m => m.Type);
|
||||
builder.Ignore(m => m.Status);
|
||||
builder.Ignore(m => m.VerificationStatus);
|
||||
|
||||
@@ -91,8 +91,9 @@ public class MerchantStaffEntityTypeConfiguration : IEntityTypeConfiguration<Mer
|
||||
builder.HasIndex("_merchantId").HasDatabaseName("ix_merchant_staff_merchant_id");
|
||||
builder.HasIndex("_email").HasDatabaseName("ix_merchant_staff_email");
|
||||
|
||||
// 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<T>() 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<Devic
|
||||
builder.HasIndex(d => 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<ShopMe
|
||||
builder.HasIndex(m => 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);
|
||||
|
||||
@@ -128,13 +128,15 @@ public class ShopEntityTypeConfiguration : IEntityTypeConfiguration<Shop>
|
||||
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<T>() 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<Shop>
|
||||
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<ShopBr
|
||||
// VI: Indexes
|
||||
builder.HasIndex(b => 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);
|
||||
|
||||
@@ -26,29 +26,29 @@ public class MerchantRepository : IMerchantRepository
|
||||
public async Task<Merchant?> 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<bool>(m, "_isDeleted"), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Merchant?> GetByUserIdAsync(Guid userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Merchants
|
||||
.FirstOrDefaultAsync(m => m.UserId == userId && !m.IsDeleted, cancellationToken);
|
||||
.FirstOrDefaultAsync(m => EF.Property<Guid>(m, "_userId") == userId && !EF.Property<bool>(m, "_isDeleted"), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> ExistsByUserIdAsync(Guid userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Merchants
|
||||
.AnyAsync(m => m.UserId == userId && !m.IsDeleted, cancellationToken);
|
||||
.AnyAsync(m => EF.Property<Guid>(m, "_userId") == userId && !EF.Property<bool>(m, "_isDeleted"), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<Merchant>> GetAllAsync(int pageNumber, int pageSize, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Merchants
|
||||
.Where(m => !m.IsDeleted)
|
||||
.OrderByDescending(m => m.CreatedAt)
|
||||
.Where(m => !EF.Property<bool>(m, "_isDeleted"))
|
||||
.OrderByDescending(m => EF.Property<DateTime>(m, "_createdAt"))
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync(cancellationToken);
|
||||
@@ -58,8 +58,8 @@ public class MerchantRepository : IMerchantRepository
|
||||
public async Task<IReadOnlyList<Merchant>> 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<bool>(m, "_isDeleted"))
|
||||
.OrderByDescending(m => EF.Property<DateTime>(m, "_createdAt"))
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ public class ShopRepository : IShopRepository
|
||||
public async Task<Shop?> 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<bool>(s, "_isDeleted"), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -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<bool>(s, "_isDeleted"), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Shop?> GetBySlugAsync(string slug, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Shops
|
||||
.FirstOrDefaultAsync(s => s.Slug == slug.ToLowerInvariant() && !s.IsDeleted, cancellationToken);
|
||||
.FirstOrDefaultAsync(s => EF.Property<string>(s, "_slug") == slug.ToLowerInvariant() && !EF.Property<bool>(s, "_isDeleted"), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> SlugExistsAsync(string slug, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Shops
|
||||
.AnyAsync(s => s.Slug == slug.ToLowerInvariant(), cancellationToken);
|
||||
.AnyAsync(s => EF.Property<string>(s, "_slug") == slug.ToLowerInvariant(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<Shop>> 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<Guid>(s, "_merchantId") == merchantId && !EF.Property<bool>(s, "_isDeleted"))
|
||||
.OrderByDescending(s => EF.Property<DateTime>(s, "_createdAt"))
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -64,8 +64,8 @@ public class ShopRepository : IShopRepository
|
||||
public async Task<IReadOnlyList<Shop>> 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<bool>(s, "_isDeleted") && s.StatusId == ShopStatus.Active.Id)
|
||||
.OrderByDescending(s => EF.Property<DateTime>(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<bool>(s, "_isDeleted") && s.StatusId == ShopStatus.Active.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user