diff --git a/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj b/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj
index ff3298e..dd5c36f 100644
--- a/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj
+++ b/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj
@@ -11,6 +11,7 @@
+
diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs
index a4e43ff..849830a 100644
--- a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs
+++ b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs
@@ -2,6 +2,7 @@ using CMSMicroservice.Domain.Entities.Payment;
using CMSMicroservice.Domain.Entities.Order;
using CMSMicroservice.Domain.Entities.DiscountShop;
using CMSMicroservice.Domain.Entities.Geography;
+using Microsoft.EntityFrameworkCore.Infrastructure;
namespace CMSMicroservice.Application.Common.Interfaces;
@@ -60,5 +61,10 @@ public interface IApplicationDbContext
DbSet States { get; }
DbSet Cities { get; }
+ ///
+ /// دسترسی به DatabaseFacade برای اجرای raw SQL و Stored Procedures
+ ///
+ DatabaseFacade Database { get; }
+
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}
\ No newline at end of file
diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs
index a349938..44bbcb9 100644
--- a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs
+++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs
@@ -1,144 +1,178 @@
+using System.Data;
+using System.Data.Common;
+using Microsoft.EntityFrameworkCore;
+
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree;
public class GetNetworkTreeQueryHandler : IRequestHandler
{
private readonly IApplicationDbContext _context;
- private readonly IWeekDefinitionRepository _weekDefinitionRepository;
+ private readonly ILogger _logger;
public GetNetworkTreeQueryHandler(
IApplicationDbContext context,
- IWeekDefinitionRepository weekDefinitionRepository)
+ ILogger logger)
{
_context = context;
- _weekDefinitionRepository = weekDefinitionRepository;
+ _logger = logger;
}
public async Task 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 BuildTree(long userId, int maxDepth, int currentDepth, CancellationToken cancellationToken, GetNetworkTreeQuery request)
+ ///
+ /// اجرای Stored Procedure و دریافت نتایج
+ ///
+ private async Task> 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();
+
+ 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;
+ }
+
+ ///
+ /// تبدیل لیست flat به ساختار درختی
+ ///
+ private NetworkTreeDto? BuildTreeFromFlatNodes(List flatNodes)
+ {
+ if (!flatNodes.Any()) return null;
+
+ // Dictionary برای دسترسی سریع به نودها
+ var nodeDict = new Dictionary();
+
+ // ایجاد همه نودها
+ 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;
}
-
- ///
- /// دریافت فرزندان با اعمال فیلترها
- ///
- private async Task> 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);
- }
-
- ///
- /// محاسبه شماره هفته از تاریخ
- ///
- // 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;
- // }
-
-
-
}