feat: add DatabaseFacade property to IApplicationDbContext for raw SQL access

This commit is contained in:
masoodafar-web
2025-12-25 00:58:18 +03:30
parent 7cebb27171
commit 2d09a69be9
3 changed files with 156 additions and 115 deletions
@@ -1,144 +1,178 @@
using System.Data;
using System.Data.Common;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree;
public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, NetworkTreeDto?>
{
private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekDefinitionRepository;
private readonly ILogger<GetNetworkTreeQueryHandler> _logger;
public GetNetworkTreeQueryHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekDefinitionRepository)
ILogger<GetNetworkTreeQueryHandler> logger)
{
_context = context;
_weekDefinitionRepository = weekDefinitionRepository;
_logger = logger;
}
public async Task<NetworkTreeDto?> Handle(GetNetworkTreeQuery request, CancellationToken cancellationToken)
{
var rootUser = await _context.Users
.AsNoTracking()
.FirstOrDefaultAsync(x => x.Id == request.UserId, cancellationToken);
if (rootUser == null)
try
{
return null;
}
// دریافت نتایج flat از Stored Procedure
var flatNodes = await ExecuteStoredProcedureAsync(request, cancellationToken);
if (flatNodes == null || !flatNodes.Any())
{
return null;
}
var tree = await BuildTree(rootUser.Id, request.MaxDepth, 0, cancellationToken, request);
return tree;
// تبدیل نتایج flat به ساختار درختی
var tree = BuildTreeFromFlatNodes(flatNodes);
return tree;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error executing GetNetworkTree for UserId: {UserId}", request.UserId);
throw;
}
}
private async Task<NetworkTreeDto> BuildTree(long userId, int maxDepth, int currentDepth, CancellationToken cancellationToken, GetNetworkTreeQuery request)
/// <summary>
/// اجرای Stored Procedure و دریافت نتایج
/// </summary>
private async Task<List<NetworkTreeNodeDto>> ExecuteStoredProcedureAsync(
GetNetworkTreeQuery request,
CancellationToken cancellationToken)
{
// دریافت کاربر با اطلاعات باشگاه مشتریان
var user = await _context.Users
.Include(u => u.ClubMembership)
.AsNoTracking()
.FirstOrDefaultAsync(x => x.Id == userId, cancellationToken);
if (user == null)
var results = new List<NetworkTreeNodeDto>();
var connection = _context.Database.GetDbConnection();
try
{
throw new NotFoundException(nameof(User), userId);
if (connection.State != ConnectionState.Open)
{
await connection.OpenAsync(cancellationToken);
}
using var command = connection.CreateCommand();
command.CommandText = "[CMS].[GetNetworkTree]";
command.CommandType = CommandType.StoredProcedure;
command.CommandTimeout = 120; // 2 minutes timeout for large trees
// Parameters - use DbParameter for provider-agnostic code
var rootUserIdParam = command.CreateParameter();
rootUserIdParam.ParameterName = "@RootUserId";
rootUserIdParam.DbType = DbType.Int64;
rootUserIdParam.Value = request.UserId;
command.Parameters.Add(rootUserIdParam);
var maxDepthParam = command.CreateParameter();
maxDepthParam.ParameterName = "@MaxDepth";
maxDepthParam.DbType = DbType.Int32;
maxDepthParam.Value = request.MaxDepth > 0 ? request.MaxDepth : 100;
command.Parameters.Add(maxDepthParam);
var isClubActiveParam = command.CreateParameter();
isClubActiveParam.ParameterName = "@IsClubActive";
isClubActiveParam.DbType = DbType.Boolean;
isClubActiveParam.Value = request.IsClubActive.HasValue ? request.IsClubActive.Value : DBNull.Value;
command.Parameters.Add(isClubActiveParam);
var weekParam = command.CreateParameter();
weekParam.ParameterName = "@ActivationWeekDefinitionId";
weekParam.DbType = DbType.Int64;
weekParam.Value = request.ActivationWeekDefinitionId.HasValue ? request.ActivationWeekDefinitionId.Value : DBNull.Value;
command.Parameters.Add(weekParam);
using var reader = await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
var node = new NetworkTreeNodeDto
{
UserId = reader.GetInt64(reader.GetOrdinal("UserId")),
Mobile = reader.IsDBNull(reader.GetOrdinal("Mobile")) ? null : reader.GetString(reader.GetOrdinal("Mobile")),
FirstName = reader.IsDBNull(reader.GetOrdinal("FirstName")) ? null : reader.GetString(reader.GetOrdinal("FirstName")),
LastName = reader.IsDBNull(reader.GetOrdinal("LastName")) ? null : reader.GetString(reader.GetOrdinal("LastName")),
LegPosition = reader.IsDBNull(reader.GetOrdinal("LegPosition")) ? null : reader.GetInt32(reader.GetOrdinal("LegPosition")),
ParentId = reader.IsDBNull(reader.GetOrdinal("ParentId")) ? null : reader.GetInt64(reader.GetOrdinal("ParentId")),
NetworkLevel = reader.GetInt32(reader.GetOrdinal("NetworkLevel")),
ClubActivatedAt = reader.IsDBNull(reader.GetOrdinal("ClubActivatedAt")) ? null : reader.GetDateTime(reader.GetOrdinal("ClubActivatedAt")),
IsClubActive = reader.GetBoolean(reader.GetOrdinal("IsClubActive")),
ActivationWeekDefinitionId = reader.IsDBNull(reader.GetOrdinal("ActivationWeekDefinitionId")) ? null : reader.GetInt64(reader.GetOrdinal("ActivationWeekDefinitionId")),
ActivationWeekDisplayName = reader.IsDBNull(reader.GetOrdinal("ActivationWeekDisplayName")) ? null : reader.GetString(reader.GetOrdinal("ActivationWeekDisplayName")),
IsActivatedInTargetWeek = reader.GetBoolean(reader.GetOrdinal("IsActivatedInTargetWeek")),
UserCreated = new DateTimeOffset(reader.GetDateTime(reader.GetOrdinal("UserCreated")))
};
results.Add(node);
}
}
finally
{
// Connection is managed by DbContext, don't close it here
}
// محاسبه شماره هفته فعال‌سازی
long? activationWeekDefinitionId = null;
string? activationWeekDisplayName = null;
bool isActivatedInTargetWeek = false;
if (user.ClubMembership?.ActivatedAt != null)
_logger.LogInformation("GetNetworkTree SP returned {Count} nodes for UserId: {UserId}", results.Count, request.UserId);
return results;
}
/// <summary>
/// تبدیل لیست flat به ساختار درختی
/// </summary>
private NetworkTreeDto? BuildTreeFromFlatNodes(List<NetworkTreeNodeDto> flatNodes)
{
if (!flatNodes.Any()) return null;
// Dictionary برای دسترسی سریع به نودها
var nodeDict = new Dictionary<long, NetworkTreeDto>();
// ایجاد همه نودها
foreach (var flatNode in flatNodes)
{
// activationWeekDefinitionId = CalculateWeekNumber(user.ClubMembership.ActivatedAt.Value);
var week = _weekDefinitionRepository.GetWeekByDate(user.ClubMembership.ActivatedAt.Value);
activationWeekDefinitionId = week.Id;
activationWeekDisplayName = week.DisplayName;
// بررسی آیا در هفته هدف فعال شده است
if (request.ActivationWeekDefinitionId!=null)
nodeDict[flatNode.UserId] = new NetworkTreeDto
{
isActivatedInTargetWeek = activationWeekDefinitionId == request.ActivationWeekDefinitionId;
UserId = flatNode.UserId,
Mobile = flatNode.Mobile,
FirstName = flatNode.FirstName,
LastName = flatNode.LastName,
LegPosition = flatNode.LegPosition.HasValue ? (NetworkLeg)flatNode.LegPosition.Value : null,
CurrentDepth = flatNode.NetworkLevel,
ClubActivatedAt = flatNode.ClubActivatedAt,
IsClubActive = flatNode.IsClubActive,
ActivationWeekDefinitionId = flatNode.ActivationWeekDefinitionId,
ActivationWeekDisplayName = flatNode.ActivationWeekDisplayName,
IsActivatedInTargetWeek = flatNode.IsActivatedInTargetWeek,
UserCreated = flatNode.UserCreated
};
}
// برقراری ارتباط Parent-Child
foreach (var flatNode in flatNodes)
{
if (flatNode.ParentId.HasValue && nodeDict.ContainsKey(flatNode.ParentId.Value))
{
var parent = nodeDict[flatNode.ParentId.Value];
var child = nodeDict[flatNode.UserId];
// تعیین موقعیت چپ یا راست
if (flatNode.LegPosition == (int)NetworkLeg.Left)
{
parent.LeftChild = child;
}
else if (flatNode.LegPosition == (int)NetworkLeg.Right)
{
parent.RightChild = child;
}
}
}
var node = new NetworkTreeDto
{
UserId = user.Id,
Mobile = user.Mobile,
FirstName = user.FirstName,
LastName = user.LastName,
LegPosition = user.LegPosition,
CurrentDepth = currentDepth,
ClubActivatedAt = user.ClubMembership?.ActivatedAt,
IsClubActive = user.ClubMembership?.IsActive ?? false,
ActivationWeekDefinitionId = activationWeekDefinitionId,
ActivationWeekDisplayName = activationWeekDisplayName,
IsActivatedInTargetWeek = isActivatedInTargetWeek,
UserCreated = user.Created
};
// اگر به حداکثر عمق رسیدیم، دیگر فرزندان را نمی‌خوانیم
if (currentDepth >= maxDepth)
{
return node;
}
// پیدا کردن فرزندان (چپ و راست)
var children = await GetFilteredChildren(userId, request, cancellationToken);
var leftChild = children.FirstOrDefault(c => c.LegPosition == NetworkLeg.Left);
if (leftChild != null)
{
node.LeftChild = await BuildTree(leftChild.Id, maxDepth, currentDepth + 1, cancellationToken, request);
}
var rightChild = children.FirstOrDefault(c => c.LegPosition == NetworkLeg.Right);
if (rightChild != null)
{
node.RightChild = await BuildTree(rightChild.Id, maxDepth, currentDepth + 1, cancellationToken, request);
}
return node;
// پیدا کردن ریشه (اولین نود با Level=0)
var rootNode = flatNodes.FirstOrDefault(n => n.NetworkLevel == 0);
return rootNode != null ? nodeDict[rootNode.UserId] : null;
}
/// <summary>
/// دریافت فرزندان با اعمال فیلترها
/// </summary>
private async Task<List<User>> GetFilteredChildren(long parentId, GetNetworkTreeQuery request, CancellationToken cancellationToken)
{
var query = _context.Users
.Include(u => u.ClubMembership)
.AsNoTracking()
.Where(x => x.NetworkParentId == parentId);
// اعمال فیلتر IsClubActive
if (request.IsClubActive.HasValue)
{
query = query.Where(u =>
u.ClubMembership != null &&
u.ClubMembership.IsDeleted == false &&
u.ClubMembership.IsActive == request.IsClubActive.Value);
}
return await query.ToListAsync(cancellationToken);
}
/// <summary>
/// محاسبه شماره هفته از تاریخ
/// </summary>
// private long CalculateWeekNumber(DateTime date)
// {
// // First try to get from repository cache
// var weekDef = _weekDefinitionRepository.GetWeekByDate(date);
// if (weekDef != null)
// {
// return weekDef.Id;
// }
//
// // Fallback: use repository's calculation method
// // return _weekDefinitionRepository.CalculateGregorianWeekNumber(date);
// return 0;
// }
}