refactor(platform): migrate Tenant to anemia model, use Manager pattern

- Convert Tenant to anemia model with long Id (no strong-typed ID)
- Add ApplicationUser, ApplicationRole to Platform.Domain (inherit Identity)
- Add TenantInfo value object for user-tenant redundancy
- Implement TenantManager/TenantStore in Platform.Infrastructure
- Update PlatformDbContext to inherit IdentityDbContext
- Migrate AuthService and Console to use Platform entities
- Remove old TenantRepository (replaced by TenantManager)
- Update AGENTS.md documentation
This commit is contained in:
movingsam 2026-02-21 13:22:08 +08:00
parent f9bd73a71d
commit a17dc9c419
16 changed files with 514 additions and 190 deletions

View File

@ -4,6 +4,7 @@
<NetCorePalVersion>3.2.1</NetCorePalVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0" />
<PackageVersion Include="NetCorePal.Extensions.Repository.EntityFrameworkCore.Snowflake" Version="3.2.1" />

View File

@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Identity;
namespace Fengling.Platform.Domain.AggregatesModel.RoleAggregate;
public class ApplicationRole : IdentityRole<long>
{
public string? Description { get; set; }
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
public long? TenantId { get; set; }
public bool IsSystem { get; set; }
public string? DisplayName { get; set; }
public List<string>? Permissions { get; set; }
}

View File

@ -1,99 +1,21 @@
namespace Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
using System.Threading;
using Fengling.Platform.Domain.DomainEvents;
public partial record TenantId : IInt64StronglyTypedId
public class Tenant
{
public static TenantId New() => new TenantId(Interlocked.Increment(ref _lastId));
private static long _lastId = 0;
public long Value => this;
public static implicit operator long(TenantId id) => id.Value;
public static implicit operator TenantId(long value) => new TenantId(value);
}
public class Tenant : Entity<TenantId>, IAggregateRoot
{
protected Tenant() { }
public Tenant(string tenantCode, string name, string contactName, string contactEmail)
{
TenantCode = tenantCode;
Name = name;
ContactName = contactName;
ContactEmail = contactEmail;
Status = TenantStatus.Active;
CreatedAt = DateTime.UtcNow;
this.AddDomainEvent(new TenantCreatedDomainEvent(this));
}
public Tenant(string tenantCode, string name, string contactName, string contactEmail,
string? contactPhone, int? maxUsers, string? description, DateTime? expiresAt)
: this(tenantCode, name, contactName, contactEmail)
{
ContactPhone = contactPhone;
MaxUsers = maxUsers;
Description = description;
ExpiresAt = expiresAt;
}
public string TenantCode { get; private set; } = string.Empty;
public string Name { get; private set; } = string.Empty;
public string ContactName { get; private set; } = string.Empty;
public string ContactEmail { get; private set; } = string.Empty;
public string? ContactPhone { get; private set; }
public int? MaxUsers { get; private set; }
public DateTime CreatedAt { get; private set; }
public DateTime? UpdatedAt { get; private set; }
public DateTime? ExpiresAt { get; private set; }
public string? Description { get; private set; }
public TenantStatus Status { get; private set; }
public Deleted Deleted { get; private set; } = new();
public RowVersion RowVersion { get; private set; } = new(0);
public void UpdateInfo(string name, string contactName, string contactEmail, string? contactPhone = null)
{
Name = name;
ContactName = contactName;
ContactEmail = contactEmail;
ContactPhone = contactPhone;
UpdatedAt = DateTime.UtcNow;
}
public void Activate()
{
if (Status == TenantStatus.Active) return;
Status = TenantStatus.Active;
UpdatedAt = DateTime.UtcNow;
this.AddDomainEvent(new TenantStatusChangedDomainEvent(this, TenantStatus.Inactive, TenantStatus.Active));
}
public void Deactivate()
{
if (Status == TenantStatus.Inactive) return;
Status = TenantStatus.Inactive;
UpdatedAt = DateTime.UtcNow;
this.AddDomainEvent(new TenantStatusChangedDomainEvent(this, TenantStatus.Active, TenantStatus.Inactive));
}
public void Delete()
{
if (Status == TenantStatus.Inactive) return;
Status = TenantStatus.Inactive;
UpdatedAt = DateTime.UtcNow;
Deleted = new Deleted(true);
}
public void Freeze()
{
if (Status == TenantStatus.Frozen) return;
Status = TenantStatus.Frozen;
UpdatedAt = DateTime.UtcNow;
this.AddDomainEvent(new TenantStatusChangedDomainEvent(this, TenantStatus.Active, TenantStatus.Frozen));
}
public long Id { get; set; }
public string TenantCode { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string ContactName { get; set; } = string.Empty;
public string ContactEmail { get; set; } = string.Empty;
public string? ContactPhone { get; set; }
public int? MaxUsers { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
public DateTime? ExpiresAt { get; set; }
public string? Description { get; set; }
public TenantStatus Status { get; set; } = TenantStatus.Active;
public bool IsDeleted { get; set; }
public long RowVersion { get; set; }
}
public enum TenantStatus

View File

@ -0,0 +1,43 @@
namespace Fengling.Platform.Domain.AggregatesModel.UserAggregate;
using System.ComponentModel.DataAnnotations;
public class AccessLog
{
[Key]
public long Id { get; set; }
[MaxLength(50)]
public string? UserName { get; set; }
[MaxLength(50)]
public string? TenantId { get; set; }
[MaxLength(20)]
public string Action { get; set; } = string.Empty;
[MaxLength(200)]
public string? Resource { get; set; }
[MaxLength(10)]
public string? Method { get; set; }
[MaxLength(50)]
public string? IpAddress { get; set; }
[MaxLength(500)]
public string? UserAgent { get; set; }
[MaxLength(20)]
public string Status { get; set; } = "success";
public int Duration { get; set; }
public string? RequestData { get; set; }
public string? ResponseData { get; set; }
public string? ErrorMessage { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

View File

@ -0,0 +1,14 @@
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
using Microsoft.AspNetCore.Identity;
namespace Fengling.Platform.Domain.AggregatesModel.UserAggregate;
public class ApplicationUser : IdentityUser<long>
{
public string? RealName { get; set; }
public string? Phone { get; set; }
public TenantInfo TenantInfo { get; set; } = null!;
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedTime { get; set; }
public bool IsDeleted { get; set; }
}

View File

@ -0,0 +1,47 @@
namespace Fengling.Platform.Domain.AggregatesModel.UserAggregate;
using System.ComponentModel.DataAnnotations;
public class AuditLog
{
[Key]
public long Id { get; set; }
[MaxLength(50)]
[Required]
public string Operator { get; set; } = string.Empty;
[MaxLength(50)]
public string? TenantId { get; set; }
[MaxLength(20)]
public string Operation { get; set; } = string.Empty;
[MaxLength(20)]
public string Action { get; set; } = string.Empty;
[MaxLength(50)]
public string? TargetType { get; set; }
public long? TargetId { get; set; }
[MaxLength(100)]
public string? TargetName { get; set; }
[MaxLength(50)]
public string IpAddress { get; set; } = string.Empty;
[MaxLength(500)]
public string? Description { get; set; }
public string? OldValue { get; set; }
public string? NewValue { get; set; }
public string? ErrorMessage { get; set; }
[MaxLength(20)]
public string Status { get; set; } = "success";
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

View File

@ -1,6 +0,0 @@
namespace Fengling.Platform.Domain.DomainEvents;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
public record TenantCreatedDomainEvent(Tenant Tenant) : IDomainEvent;
public record TenantStatusChangedDomainEvent(Tenant Tenant, TenantStatus OldStatus, TenantStatus NewStatus) : IDomainEvent;

View File

@ -9,6 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" />
<PackageReference Include="NetCorePal.Extensions.Domain.Abstractions" />
<PackageReference Include="NetCorePal.Extensions.Primitives" />
</ItemGroup>

View File

@ -11,7 +11,7 @@ public class TenantConfiguration : IEntityTypeConfiguration<Tenant>
builder.ToTable("Platform_Tenants");
builder.HasKey(t => t.Id);
builder.Property(t => t.Id).UseSnowFlakeValueGenerator();
builder.Property(t => t.Id).ValueGeneratedOnAdd();
builder.Property(t => t.TenantCode)
.IsRequired()

View File

@ -9,6 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" />
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" />
<PackageReference Include="NetCorePal.Extensions.Repository.EntityFrameworkCore.Snowflake" />

View File

@ -0,0 +1,38 @@
namespace Fengling.Platform.Infrastructure;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
public interface ITenantStore
{
Task<Tenant?> FindByIdAsync(long tenantId, CancellationToken cancellationToken = default);
Task<Tenant?> FindByTenantCodeAsync(string tenantCode, CancellationToken cancellationToken = default);
Task<IList<Tenant>> GetAllAsync(CancellationToken cancellationToken = default);
Task<IList<Tenant>> GetPagedAsync(int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default);
Task<int> GetCountAsync(string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default);
Task<int> GetUserCountAsync(long tenantId, CancellationToken cancellationToken = default);
Task<Microsoft.AspNetCore.Identity.IdentityResult> CreateAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<Microsoft.AspNetCore.Identity.IdentityResult> UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<Microsoft.AspNetCore.Identity.IdentityResult> DeleteAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<string> GetTenantCodeAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<string> GetNameAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<string> GetContactNameAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<string> GetContactEmailAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<string?> GetContactPhoneAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<int?> GetMaxUsersAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<string?> GetDescriptionAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<TenantStatus> GetStatusAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<DateTime?> GetExpiresAtAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<DateTime> GetCreatedAtAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<DateTime?> GetUpdatedAtAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task SetTenantCodeAsync(Tenant tenant, string code, CancellationToken cancellationToken = default);
Task SetNameAsync(Tenant tenant, string name, CancellationToken cancellationToken = default);
Task SetContactNameAsync(Tenant tenant, string name, CancellationToken cancellationToken = default);
Task SetContactEmailAsync(Tenant tenant, string email, CancellationToken cancellationToken = default);
Task SetContactPhoneAsync(Tenant tenant, string? phone, CancellationToken cancellationToken = default);
Task SetMaxUsersAsync(Tenant tenant, int? maxUsers, CancellationToken cancellationToken = default);
Task SetDescriptionAsync(Tenant tenant, string? description, CancellationToken cancellationToken = default);
Task SetStatusAsync(Tenant tenant, TenantStatus status, CancellationToken cancellationToken = default);
Task SetExpiresAtAsync(Tenant tenant, DateTime? expiresAt, CancellationToken cancellationToken = default);
}

View File

@ -1,14 +1,17 @@
namespace Fengling.Platform.Infrastructure;
using Fengling.Platform.Domain.AggregatesModel.RoleAggregate;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
using MediatR;
using Fengling.Platform.Domain.AggregatesModel.UserAggregate;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NetCorePal.Extensions.Repository.EntityFrameworkCore;
public partial class PlatformDbContext(DbContextOptions<PlatformDbContext> options, IMediator mediator)
: AppDbContextBase(options, mediator)
public partial class PlatformDbContext(DbContextOptions<PlatformDbContext> options)
: IdentityDbContext<ApplicationUser, ApplicationRole, long>(options)
{
public DbSet<Tenant> Tenants => Set<Tenant>();
public DbSet<AccessLog> AccessLogs => Set<AccessLog>();
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@ -17,13 +20,64 @@ public partial class PlatformDbContext(DbContextOptions<PlatformDbContext> optio
throw new ArgumentNullException(nameof(modelBuilder));
}
modelBuilder.Entity<ApplicationUser>(entity =>
{
entity.Property(e => e.RealName).HasMaxLength(100);
entity.Property(e => e.Phone).HasMaxLength(20);
entity.HasIndex(e => e.Phone).IsUnique();
entity.OwnsOne(e => e.TenantInfo, navigationBuilder =>
{
navigationBuilder.Property(t => t.TenantCode).HasColumnName("TenantCode");
navigationBuilder.Property(t => t.TenantId).HasColumnName("TenantId");
navigationBuilder.Property(t => t.TenantName).HasColumnName("TenantName");
navigationBuilder.WithOwner();
});
});
modelBuilder.Entity<ApplicationRole>(entity =>
{
entity.Property(e => e.Description).HasMaxLength(200);
});
modelBuilder.Entity<AccessLog>(entity =>
{
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.CreatedAt);
entity.HasIndex(e => e.UserName);
entity.HasIndex(e => e.TenantId);
entity.HasIndex(e => e.Action);
entity.HasIndex(e => e.Status);
entity.Property(e => e.UserName).HasMaxLength(50);
entity.Property(e => e.TenantId).HasMaxLength(50);
entity.Property(e => e.Action).HasMaxLength(20);
entity.Property(e => e.Resource).HasMaxLength(200);
entity.Property(e => e.Method).HasMaxLength(10);
entity.Property(e => e.IpAddress).HasMaxLength(50);
entity.Property(e => e.UserAgent).HasMaxLength(500);
entity.Property(e => e.Status).HasMaxLength(20);
});
modelBuilder.Entity<AuditLog>(entity =>
{
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.CreatedAt);
entity.HasIndex(e => e.Operator);
entity.HasIndex(e => e.TenantId);
entity.HasIndex(e => e.Operation);
entity.HasIndex(e => e.Action);
entity.Property(e => e.Operator).HasMaxLength(50);
entity.Property(e => e.TenantId).HasMaxLength(50);
entity.Property(e => e.Operation).HasMaxLength(20);
entity.Property(e => e.Action).HasMaxLength(20);
entity.Property(e => e.TargetType).HasMaxLength(50);
entity.Property(e => e.TargetName).HasMaxLength(100);
entity.Property(e => e.IpAddress).HasMaxLength(50);
entity.Property(e => e.Description).HasMaxLength(500);
entity.Property(e => e.Status).HasMaxLength(20);
});
modelBuilder.ApplyConfigurationsFromAssembly(typeof(PlatformDbContext).Assembly);
base.OnModelCreating(modelBuilder);
}
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
ConfigureStronglyTypedIdValueConverter(configurationBuilder);
base.ConfigureConventions(configurationBuilder);
}
}

View File

@ -1,78 +0,0 @@
using NetCorePal.Extensions.Repository.EntityFrameworkCore;
namespace Fengling.Platform.Infrastructure.Repositories;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
using NetCorePal.Extensions.Repository;
using Microsoft.EntityFrameworkCore;
public interface ITenantRepository : IRepository<Tenant, TenantId>
{
Task<Tenant?> GetByTenantIdAsync(string tenantId, CancellationToken cancellationToken = default);
Task<Tenant?> GetByIdIncludeH5Async(TenantId id, CancellationToken cancellationToken = default);
Task<Tenant?> GetByIdAsync(long id, CancellationToken cancellationToken = default);
Task<IEnumerable<Tenant>> GetPagedAsync(int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default);
Task<int> CountAsync(string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default);
Task<int> GetUserCountAsync(TenantId tenantId, CancellationToken cancellationToken = default);
}
public class TenantRepository(PlatformDbContext context)
: RepositoryBase<Tenant, TenantId, PlatformDbContext>(context), ITenantRepository
{
public async Task<Tenant?> GetByTenantIdAsync(string tenantId, CancellationToken cancellationToken = default)
{
return await DbContext.Tenants.FirstOrDefaultAsync(t => t.TenantCode == tenantId, cancellationToken);
}
public async Task<Tenant?> GetByIdIncludeH5Async(TenantId id, CancellationToken cancellationToken = default)
{
return await DbContext.Tenants.FirstOrDefaultAsync(t => t.Id == id, cancellationToken);
}
public async Task<Tenant?> GetByIdAsync(long id, CancellationToken cancellationToken = default)
{
return await DbContext.Tenants.FindAsync(new object[] { id }, cancellationToken);
}
public async Task<IEnumerable<Tenant>> GetPagedAsync(int page, int pageSize, string? name = null,
string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default)
{
var query = DbContext.Tenants.AsQueryable();
if (!string.IsNullOrEmpty(name))
query = query.Where(t => t.Name.Contains(name));
if (!string.IsNullOrEmpty(tenantCode))
query = query.Where(t => t.TenantCode.Contains(tenantCode));
if (status.HasValue)
query = query.Where(t => t.Status == status);
return await query
.OrderByDescending(t => t.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync(cancellationToken);
}
public async Task<int> CountAsync(string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default)
{
var query = DbContext.Tenants.AsQueryable();
if (!string.IsNullOrEmpty(name))
query = query.Where(t => t.Name.Contains(name));
if (!string.IsNullOrEmpty(tenantCode))
query = query.Where(t => t.TenantCode.Contains(tenantCode));
if (status.HasValue)
query = query.Where(t => t.Status == status);
return await query.CountAsync(cancellationToken);
}
public async Task<int> GetUserCountAsync(TenantId tenantId, CancellationToken cancellationToken = default)
{
return 0;
}
}

View File

@ -15,8 +15,15 @@ public static class SeedData
return adminTenant;
}
adminTenant = new Tenant("Administrator", "超级系统",
"", "");
adminTenant = new Tenant
{
TenantCode = "admin",
Name = "超级系统",
ContactName = "",
ContactEmail = "",
Status = TenantStatus.Active,
CreatedAt = DateTime.UtcNow
};
await context.Tenants.AddAsync(adminTenant);
await context.SaveChangesAsync();
return adminTenant;

View File

@ -0,0 +1,81 @@
namespace Fengling.Platform.Infrastructure;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
using Microsoft.AspNetCore.Identity;
public interface ITenantManager
{
Task<Tenant?> FindByIdAsync(long tenantId, CancellationToken cancellationToken = default);
Task<Tenant?> FindByTenantCodeAsync(string tenantCode, CancellationToken cancellationToken = default);
Task<IList<Tenant>> GetAllAsync(CancellationToken cancellationToken = default);
Task<IList<Tenant>> GetPagedAsync(int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default);
Task<int> GetCountAsync(string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default);
Task<IdentityResult> CreateAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<IdentityResult> UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<IdentityResult> DeleteAsync(Tenant tenant, CancellationToken cancellationToken = default);
Task<int> GetUserCountAsync(long tenantId, CancellationToken cancellationToken = default);
Task<IdentityResult> SetTenantCodeAsync(Tenant tenant, string code, CancellationToken cancellationToken = default);
}
public class TenantManager : ITenantManager
{
private readonly ITenantStore _store;
public TenantManager(ITenantStore store)
{
_store = store;
}
public virtual async Task<Tenant?> FindByIdAsync(long tenantId, CancellationToken cancellationToken = default)
{
return await _store.FindByIdAsync(tenantId, cancellationToken);
}
public virtual async Task<Tenant?> FindByTenantCodeAsync(string tenantCode, CancellationToken cancellationToken = default)
{
return await _store.FindByTenantCodeAsync(tenantCode, cancellationToken);
}
public virtual async Task<IList<Tenant>> GetAllAsync(CancellationToken cancellationToken = default)
{
return await _store.GetAllAsync(cancellationToken);
}
public virtual async Task<IList<Tenant>> GetPagedAsync(int page, int pageSize, string? name = null,
string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default)
{
return await _store.GetPagedAsync(page, pageSize, name, tenantCode, status, cancellationToken);
}
public virtual async Task<int> GetCountAsync(string? name = null, string? tenantCode = null,
TenantStatus? status = null, CancellationToken cancellationToken = default)
{
return await _store.GetCountAsync(name, tenantCode, status, cancellationToken);
}
public virtual async Task<IdentityResult> CreateAsync(Tenant tenant, CancellationToken cancellationToken = default)
{
return await _store.CreateAsync(tenant, cancellationToken);
}
public virtual async Task<IdentityResult> UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default)
{
return await _store.UpdateAsync(tenant, cancellationToken);
}
public virtual async Task<IdentityResult> DeleteAsync(Tenant tenant, CancellationToken cancellationToken = default)
{
return await _store.DeleteAsync(tenant, cancellationToken);
}
public virtual async Task<int> GetUserCountAsync(long tenantId, CancellationToken cancellationToken = default)
{
return await _store.GetUserCountAsync(tenantId, cancellationToken);
}
public virtual async Task<IdentityResult> SetTenantCodeAsync(Tenant tenant, string code, CancellationToken cancellationToken = default)
{
await _store.SetTenantCodeAsync(tenant, code, cancellationToken);
return IdentityResult.Success;
}
}

View File

@ -0,0 +1,186 @@
namespace Fengling.Platform.Infrastructure;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
public class TenantStore : ITenantStore
{
private readonly PlatformDbContext _context;
private readonly DbSet<Tenant> _tenants;
public TenantStore(PlatformDbContext context)
{
_context = context;
_tenants = context.Tenants;
}
public void Dispose() { }
public virtual Task<Tenant?> FindByIdAsync(long tenantId, CancellationToken cancellationToken = default)
{
return _tenants.FirstOrDefaultAsync(t => t.Id == tenantId, cancellationToken);
}
public virtual Task<Tenant?> FindByTenantCodeAsync(string tenantCode, CancellationToken cancellationToken = default)
{
return _tenants.FirstOrDefaultAsync(t => t.TenantCode == tenantCode, cancellationToken);
}
public virtual async Task<IList<Tenant>> GetAllAsync(CancellationToken cancellationToken = default)
{
return await _tenants.ToListAsync(cancellationToken);
}
public virtual async Task<IList<Tenant>> GetPagedAsync(int page, int pageSize, string? name = null,
string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default)
{
var query = _tenants.AsQueryable();
if (!string.IsNullOrEmpty(name))
query = query.Where(t => t.Name.Contains(name));
if (!string.IsNullOrEmpty(tenantCode))
query = query.Where(t => t.TenantCode.Contains(tenantCode));
if (status.HasValue)
query = query.Where(t => t.Status == status);
return await query
.OrderByDescending(t => t.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync(cancellationToken);
}
public virtual async Task<int> GetCountAsync(string? name = null, string? tenantCode = null,
TenantStatus? status = null, CancellationToken cancellationToken = default)
{
var query = _tenants.AsQueryable();
if (!string.IsNullOrEmpty(name))
query = query.Where(t => t.Name.Contains(name));
if (!string.IsNullOrEmpty(tenantCode))
query = query.Where(t => t.TenantCode.Contains(tenantCode));
if (status.HasValue)
query = query.Where(t => t.Status == status);
return await query.CountAsync(cancellationToken);
}
public virtual async Task<IdentityResult> CreateAsync(Tenant tenant, CancellationToken cancellationToken = default)
{
_tenants.Add(tenant);
await _context.SaveChangesAsync(cancellationToken);
return IdentityResult.Success;
}
public virtual async Task<IdentityResult> UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default)
{
tenant.UpdatedAt = DateTime.UtcNow;
_tenants.Update(tenant);
await _context.SaveChangesAsync(cancellationToken);
return IdentityResult.Success;
}
public virtual async Task<IdentityResult> DeleteAsync(Tenant tenant, CancellationToken cancellationToken = default)
{
_tenants.Remove(tenant);
await _context.SaveChangesAsync(cancellationToken);
return IdentityResult.Success;
}
public virtual async Task<int> GetUserCountAsync(long tenantId, CancellationToken cancellationToken = default)
{
return await _context.Users.CountAsync(u => u.TenantInfo.TenantId == tenantId && !u.IsDeleted, cancellationToken);
}
public virtual Task<string> GetTenantCodeAsync(Tenant tenant, CancellationToken cancellationToken = default)
=> Task.FromResult(tenant.TenantCode);
public virtual Task<string> GetNameAsync(Tenant tenant, CancellationToken cancellationToken = default)
=> Task.FromResult(tenant.Name);
public virtual Task<string> GetContactNameAsync(Tenant tenant, CancellationToken cancellationToken = default)
=> Task.FromResult(tenant.ContactName);
public virtual Task<string> GetContactEmailAsync(Tenant tenant, CancellationToken cancellationToken = default)
=> Task.FromResult(tenant.ContactEmail);
public virtual Task<string?> GetContactPhoneAsync(Tenant tenant, CancellationToken cancellationToken = default)
=> Task.FromResult(tenant.ContactPhone);
public virtual Task<int?> GetMaxUsersAsync(Tenant tenant, CancellationToken cancellationToken = default)
=> Task.FromResult(tenant.MaxUsers);
public virtual Task<string?> GetDescriptionAsync(Tenant tenant, CancellationToken cancellationToken = default)
=> Task.FromResult(tenant.Description);
public virtual Task<TenantStatus> GetStatusAsync(Tenant tenant, CancellationToken cancellationToken = default)
=> Task.FromResult(tenant.Status);
public virtual Task<DateTime?> GetExpiresAtAsync(Tenant tenant, CancellationToken cancellationToken = default)
=> Task.FromResult(tenant.ExpiresAt);
public virtual Task<DateTime> GetCreatedAtAsync(Tenant tenant, CancellationToken cancellationToken = default)
=> Task.FromResult(tenant.CreatedAt);
public virtual Task<DateTime?> GetUpdatedAtAsync(Tenant tenant, CancellationToken cancellationToken = default)
=> Task.FromResult(tenant.UpdatedAt);
public virtual Task SetTenantCodeAsync(Tenant tenant, string code, CancellationToken cancellationToken = default)
{
tenant.TenantCode = code;
return Task.CompletedTask;
}
public virtual Task SetNameAsync(Tenant tenant, string name, CancellationToken cancellationToken = default)
{
tenant.Name = name;
return Task.CompletedTask;
}
public virtual Task SetContactNameAsync(Tenant tenant, string name, CancellationToken cancellationToken = default)
{
tenant.ContactName = name;
return Task.CompletedTask;
}
public virtual Task SetContactEmailAsync(Tenant tenant, string email, CancellationToken cancellationToken = default)
{
tenant.ContactEmail = email;
return Task.CompletedTask;
}
public virtual Task SetContactPhoneAsync(Tenant tenant, string? phone, CancellationToken cancellationToken = default)
{
tenant.ContactPhone = phone;
return Task.CompletedTask;
}
public virtual Task SetMaxUsersAsync(Tenant tenant, int? maxUsers, CancellationToken cancellationToken = default)
{
tenant.MaxUsers = maxUsers;
return Task.CompletedTask;
}
public virtual Task SetDescriptionAsync(Tenant tenant, string? description, CancellationToken cancellationToken = default)
{
tenant.Description = description;
return Task.CompletedTask;
}
public virtual Task SetStatusAsync(Tenant tenant, TenantStatus status, CancellationToken cancellationToken = default)
{
tenant.Status = status;
return Task.CompletedTask;
}
public virtual Task SetExpiresAtAsync(Tenant tenant, DateTime? expiresAt, CancellationToken cancellationToken = default)
{
tenant.ExpiresAt = expiresAt;
return Task.CompletedTask;
}
}