后台做菜单管理、商品分类管理的时候,我们建表一般会有个 ParentId 对应父 ID,这样能形成无限极的分类。存在数据库中是列表数据,当我们返回前端的时候一般会处理成树形数据,方便前端展示,这个场景比较多,所以可以想到使用泛型来写一个算法模板。
首先定义数据库模型的接口:
/// <summary>
/// 水平对象接口,一般是数据库实体对象
/// </summary>
/// <typeparam name="TId"></typeparam>
public interface ILevelModel<TId> where TId : struct
{
TId Id { get; set; }
TId ParentId { get; set; }
}
ID 的类型一般是 int 类型,但是也有习惯使用 long 类型的,所以这里使用 TId 泛型来表示。
然后定义返回前端的接口:
/// <summary>
/// 有children属性的可内嵌本身的层级对象
/// </summary>
/// <typeparam name="T"></typeparam>
public interface INestedModel<T>
{
ICollection<T> Children { get; set; }
}
这个接口超级简单,就是一个子集合。
接下来就可以实现我们的通用方法了:
/// <summary>
/// 从水平list转变成嵌套tree列表
/// </summary>
/// <param name="targetCollection">目标tree列表</param>
/// <param name="sourceData">所有源数据</param>
/// <param name="parentId">第一级的parentId,默认0</param>
/// <typeparam name="T">目标对象类型</typeparam>
/// <typeparam name="S">原对象类型</typeparam>
/// <typeparam name="IId">原对象id类型</typeparam>
public static void ListToTree<T, S, IId>(ICollection<T> targetCollection, IEnumerable<S> sourceData,
IId parentId = default(IId)) where T : INestedModel<T> where S : ILevelModel<IId> where IId : struct
{
foreach (var sysRes in sourceData.Where(x => x.ParentId.Equals(parentId)))
{
var info = SimpleMapper.Map<T>(sysRes);
if (sourceData.Any(x => x.ParentId.Equals(sysRes.Id)))
{
info.Children = new List<T>();
ListToTree(info.Children, sourceData, sysRes.Id);
}
targetCollection.Add(info);
}
}
这样基本就完成了,但是有可能有这样的情况,现有的类中属性名字不是 Id、不是 ParentId 或者不是 Children,这个时候上面的接口定义就不方便了,为了更加通用,我们可以另外增加两个接口,里面只包含方法,不使用属性:
/// <summary>
/// 水平对象接口,一般是数据库实体对象,版本2
/// </summary>
/// <typeparam name="IId"></typeparam>
public interface ILevelModel2<out IId> where IId : struct
{
IId GetId();
IId GetParentId();
}
/// <summary>
/// 有children属性的可内嵌本身的层级对象,版本2
/// </summary>
/// <typeparam name="T"></typeparam>
public interface INestedModel2<T>
{
ICollection<T> GetChildren();
void SetChildren(ICollection<T> children);
}
这样就更灵活了,然后在实现一个针对这两个接口的方法:
/// <summary>
/// 从水平list转变成嵌套tree列表
/// </summary>
/// <param name="targetCollection">目标tree列表</param>
/// <param name="sourceData">所有源数据</param>
/// <param name="parentId">第一级的parentId,默认0</param>
/// <typeparam name="T">目标对象类型</typeparam>
/// <typeparam name="S">原对象类型</typeparam>
/// <typeparam name="IId">原对象id类型</typeparam>
public static void ListToTree2<T, S, IId>(ICollection<T> targetCollection, IEnumerable<S> sourceData,
IId parentId = default(IId)) where T : INestedModel2<T> where S : ILevelModel2<IId> where IId : struct
{
foreach (var sysRes in sourceData.Where(x => x.GetParentId().Equals(parentId)))
{
var info = SimpleMapper.Map<T>(sysRes);
if (sourceData.Any(x => x.GetParentId().Equals(sysRes.GetId())))
{
info.SetChildren(new List<T>());
ListToTree2(info.GetChildren(), sourceData, sysRes.GetId());
}
targetCollection.Add(info);
}
}
调用的例子:
public class Res : ILevelModel<int>
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
}
public class ResInfo : INestedModel<ResInfo>
{
public string Name { get; set; }
public int Id { get; set; }
public ICollection<ResInfo> Children { get; set; }
}
static void Main(string[] args)
{
var sourceData = new List<Res>();
sourceData.Add(new Res() {Id = 1, Name = "第1个", ParentId = 0});
sourceData.Add(new Res() {Id = 2, Name = "第2个", ParentId = 0});
sourceData.Add(new Res() {Id = 3, Name = "第1-1个", ParentId = 1});
sourceData.Add(new Res() {Id = 4, Name = "第1-2个", ParentId = 1});
sourceData.Add(new Res() {Id = 5, Name = "第2-1个", ParentId = 2});
sourceData.Add(new Res() {Id = 6, Name = "第2-1-1个", ParentId = 5});
sourceData.Add(new Res() {Id = 7, Name = "第2-1-2个", ParentId = 5});
var list = new List<ResInfo>();
CollectionHelper.ListToTree(list, sourceData, 0);
var json = JsonSerializer.Serialize(list, new JsonSerializerOptions()
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
Console.WriteLine(json);
}
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于