后台做菜单管理、商品分类管理的时候,我们建表一般会有个 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); }
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于