نیازی به استفاده از Id نیست. مسیر زیر را در نظر بگیرید:
مسیر بالا متناظر با نودی در درخت میباشد که در عمق 2 بوده و فرزند 5 ام مربوط به نود 00001.00042 میباشد. اگر نیاز باشد فرزند جدیدی به نود 00001.00042 اضافه شود، باید ابتدا مسیر آخرین فرزند آن یعنی الگوی بالایی واکشی شده و سپس مسیر جدیدی برای نود جدید به شکل زیر تشکیل شود:
/// Example: "00001.00042.00005".
/// Example: "00001.00042.00006".
دقیقا مشابه به کاری میباشد که نوع داده hierarchyid موجود در Sql Server انجام میدهد. با این روش دقیقا مشخص میباشد که نود x در چه مکانی قرار داد.
مدیریت واحدهای سازمانی
یکسری متد کمکی هم برای مدیریت فیلد Path در نظر گرفته شده است.
public class OrganizationalUnit : TrackableEntity<User>, IHasRowVersion, IPassivable { #region Constants /// <summary> /// Maximum depth of an UO hierarchy. /// </summary> public const int MaxDepth = 16; /// <summary> /// Length of a code unit between dots. /// </summary> public const int PathUnitLength = 5; /// <summary> /// Maximum length of the <see cref="Path"/> property. /// </summary> public const int MaxPathLength = MaxDepth * (PathUnitLength + 1) - 1; public const char HierarchicalDisplayNameSeperator = '»'; #endregion #region Properties public string Name { get; set; } public string NormalizedName { get; set; } public string HierarchicalDisplayName { get; set; } /// <summary> /// Hierarchical Path of this organization unit. /// Example: "00001.00042.00005". /// It's changeable if OU hierarch is changed. /// </summary> public string Path { get; set; } public bool IsActive { get; set; } = true; public byte[] RowVersion { get; set; } #endregion #region Navigation Properties public OrganizationalUnit Parent { get; set; } public long? ParentId { get; set; } public ICollection<OrganizationalUnit> Children { get; set; } = new HashSet<OrganizationalUnit>(); public ICollection<UserOrganizationalUnit> UserOrganizationalUnits { get; set; } = new HashSet<UserOrganizationalUnit>(); #endregion #region Public Methods /// <summary> /// Creates path for given numbers. /// Example: if numbers are 4,2 then returns "00004.00002"; /// </summary> /// <param name="numbers">Numbers</param> public static string CreatePath(params int[] numbers) { if (numbers.IsNullOrEmpty()) { return null; } return numbers.Select(number => number.ToString(new string('0', PathUnitLength))).JoinAsString("."); } /// <summary> /// Appends a child path to a parent path. /// Example: if parentPath = "00001", childPath = "00042" then returns "00001.00042". /// </summary> /// <param name="parentPath">Parent path. Can be null or empty if parent is a root.</param> /// <param name="childPath">Child path.</param> public static string AppendPath(string parentPath, string childPath) { if (childPath.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(childPath), "childPath can not be null or empty."); } if (parentPath.IsNullOrEmpty()) { return childPath; } return parentPath + "." + childPath; } /// <summary> /// Gets relative path to the parent. /// Example: if path = "00019.00055.00001" and parentPath = "00019" then returns "00055.00001". /// </summary> /// <param name="path">The path.</param> /// <param name="parentPath">The parent path.</param> public static string GetRelativePath(string path, string parentPath) { if (path.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(path), "Path can not be null or empty."); } if (parentPath.IsNullOrEmpty()) { return path; } if (path.Length == parentPath.Length) { return null; } return path.Substring(parentPath.Length + 1); } /// <summary> /// Calculates next path for given path. /// Example: if code = "00019.00055.00001" returns "00019.00055.00002". /// </summary> /// <param name="path">The path.</param> public static string CalculateNextPath(string path) { if (path.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(path), "Path can not be null or empty."); } var parentPath = GetParentPath(path); var lastUnitPath = GetLastUnitPath(path); return AppendPath(parentPath, CreatePath(Convert.ToInt32(lastUnitPath) + 1)); } /// <summary> /// Gets the last unit path. /// Example: if path = "00019.00055.00001" returns "00001". /// </summary> /// <param name="path">The path.</param> public static string GetLastUnitPath(string path) { if (path.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(path), "Path can not be null or empty."); } var splittedPath = path.Split('.'); return splittedPath[splittedPath.Length - 1]; } /// <summary> /// Gets parent path. /// Example: if path = "00019.00055.00001" returns "00019.00055". /// </summary> /// <param name="path">The path.</param> public static string GetParentPath(string path) { if (path.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(path), "Path can not be null or empty."); } var splittedPath = path.Split('.'); if (splittedPath.Length == 1) { return null; } return splittedPath.Take(splittedPath.Length - 1).JoinAsString("."); } #endregion }
البته یک ویو نمایشی برای حالت درختی هم بهتر است داشته باشید.
یکسری متد DomainService
public virtual async Task<string> GetNextChildPathAsync(long? parentId) { var lastChild = await GetLastChildOrNullAsync(parentId).ConfigureAwait(false); if (lastChild == null) { var parentPath = parentId != null ? await GetPathAsync(parentId.Value).ConfigureAwait(false) : null; return OrganizationalUnit.AppendPath(parentPath, OrganizationalUnit.CreatePath(1)); } return OrganizationalUnit.CalculateNextPath(lastChild.Path); } public async Task<string> GetNextChildHierarchicalDisplayNameAsync(string name, long? parentId) { var parent = parentId != null ? await _organizationalUnits.SingleOrDefaultAsync(a => a.Id == parentId.Value).ConfigureAwait(false) : null; return parent == null ? name : $"{parent.HierarchicalDisplayName} {OrganizationalUnit.HierarchicalDisplayNameSeperator} {name}"; } public virtual async Task<OrganizationalUnit> GetLastChildOrNullAsync(long? parentId) { return await _organizationalUnits.OrderByDescending(c => c.Path) .FirstOrDefaultAsync(ou => ou.ParentId == parentId).ConfigureAwait(false); } public virtual async Task<string> GetPathAsync(long id) { Guard.ArgumentNotZero(id, nameof(id)); var organizationalUnit = await _organizationalUnits.SingleOrDefaultAsync(ou => ou.Id == id).ConfigureAwait(false); if (organizationalUnit == null) { throw new KeyNotFoundException(); } return organizationalUnit.Path; } public async Task<List<OrganizationalUnit>> FindChildrenAsync(long? parentId, bool recursive = false) { if (!recursive) { return await _organizationalUnits.Where(ou => ou.ParentId == parentId).ToListAsync().ConfigureAwait(false); } if (!parentId.HasValue) { return await _organizationalUnits.ToListAsync().ConfigureAwait(false); } var path = await GetPathAsync(parentId.Value).ConfigureAwait(false); return await _organizationalUnits.Where( ou => ou.Path.StartsWith(path) && ou.Id != parentId.Value).ToListAsync().ConfigureAwait(false); } public virtual async Task MoveAsync(long id, long? parentId) { Guard.ArgumentNotZero(id, nameof(id)); var organizationalUnit = await _organizationalUnits.SingleOrDefaultAsync(ou => ou.Id == id).ConfigureAwait(false); if (organizationalUnit == null || organizationalUnit.ParentId == parentId) { return; } //Should find children before Path change var children = await FindChildrenAsync(id, true).ConfigureAwait(false); //Store old Path of OU var oldPath = organizationalUnit.Path; //Move OU organizationalUnit.Path = await GetNextChildPathAsync(parentId).ConfigureAwait(false); organizationalUnit.ParentId = parentId; //Update Children Paths foreach (var child in children) { child.Path = OrganizationalUnit.AppendPath(organizationalUnit.Path, OrganizationalUnit.GetRelativePath(child.Path, oldPath)); } }