前言: 在我們的業務中,展示列表時經常會聯表查詢,比如說我們有學生表和班級表,表結構如下:包含了學生表、班級表以及列表返回模型 /// <summary> /// 學生表 /// </summary> public class StudentInfo { /// <summary> /// 標識 / ...
前言:
在我們的業務中,展示列表時經常會聯表查詢,比如說我們有學生表和班級表,表結構如下:包含了學生表、班級表以及列表返回模型
/// <summary> /// 學生表 /// </summary> public class StudentInfo { /// <summary> /// 標識 /// </summary> public Guid Id { get; set; } /// <summary> /// 學號 /// </summary> public string Number { get; set; } /// <summary> /// 姓名 /// </summary> public string Name { get; set; } /// <summary> /// 班級標識 /// </summary> public Guid ClassId { get; set; } } /// <summary> /// 班級表 /// </summary> public class ClassInfo { /// <summary> /// 標識 /// </summary> public Guid Id { get; set; } /// <summary> /// 班級總人數 /// </summary> public int TotalNumber { get; set; } } /// <summary> /// 列表返回模型 /// </summary> public class ClassStudentModel { /// <summary> /// 學生標識 /// </summary> public Guid Id { get; set; } /// <summary> /// 學號 /// </summary> public string Number { get; set; } /// <summary> /// 學生名稱 /// </summary> public string Name { get; set; } /// <summary> /// 班級總人數 /// </summary> public int TotalNumber { get; set; } }
普通的查詢語句,例子中的返回模型欄位並不多,所以寫4個欄位也不費勁,但如果是十幾二十個那就挺麻煩的了,如下:
var query = from s in dbStore.Set<StudentInfo>() from c in dbStore.Set<ClassInfo>().Where(c => c.Id == s.ClassId).DefaultIfEmpty() select new { s, c }; // 一般一些業務中應該會有更多的查詢條件,這裡就省略了 // 比如: query = query.Where(e => e.s.Name == options.Name); var result = query.Select(e => new ClassStudentModel { Id = e.s.Id, Number = e.s.Number, Name = e.s.Name, TotalNumber = e.c.TotalNumber }).ToList();
所以我想有沒有什麼方法能夠讓這個匿名類型中的欄位自動映射到列表的返回模型中呢?
正文:
使用表達式樹+反射可以實現此需求,通過反射將各模型中的欄位名與列表返回模型中的各欄位進行對應,再利用表達式樹進行拼接構造函數。有了這個思路之後呢,還有個問題,就是可能各模型中有很多相同的欄位都可以和返回模型中的欄位對應,比如說學生表裡有Id,班級表中也有Id,那麼到底是哪個Id和返回模型中的Id呢,為此,我又加了一個特性自動映射ParentAnonymousAttribute,通過此特性來判別取哪個模型中的Id,自動映射方法和特性代碼如下:
/// <summary> /// 父級匿名特性 /// </summary> public class ParentAnonymousAttribute : Attribute { /// <summary> /// 構造函數 /// </summary> public ParentAnonymousAttribute(string parentName) { ParentName = parentName; } /// <summary> /// /// </summary> /// <param name="parentName"></param> /// <param name="name"></param> public ParentAnonymousAttribute(string parentName, string name) { ParentName = parentName; Name = name; } /// <summary> /// 父級匿名名稱 /// </summary> public string ParentName { get; set; } /// <summary> /// 屬性名稱 /// </summary> public string Name { get; set; } }
/// <summary> /// 自動select /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="TGraph"></typeparam> /// <param name="query"></param> /// <param name="graph"></param> /// <returns></returns> public static IQueryable<TGraph> AutoSelect<T, TGraph>(this IQueryable<T> query, TGraph graph) { var defaultCtor = typeof(TGraph).GetConstructor(Type.EmptyTypes); if (defaultCtor == null) { throw new Exception(); } var constructor = Expression.New(defaultCtor); var bindings = new List<MemberAssignment>(); var tExp = Expression.Parameter(typeof(T), "t"); var typeT = typeof(T); var tProperties = typeT.GetProperties(); var graphProperties = typeof(TGraph).GetProperties().Where(e => e.SetMethod != null).ToList(); var graphPointParentProps = graphProperties.Where(e => e.GetCustomAttribute<ParentAnonymousAttribute>() != null).ToList(); var graphNoPointParentProps = graphProperties.Where(e => e.GetCustomAttribute<ParentAnonymousAttribute>() == null).ToList(); foreach (var item in graphPointParentProps) { var attr = item.GetCustomAttribute<ParentAnonymousAttribute>(); var pExp = Expression.Property(tExp, attr.ParentName); var realExp = Expression.Property(pExp, attr.Name ?? item.Name); bindings.Add(Expression.Bind(item, realExp)); } foreach (var prop in tProperties) { var anoyClass = prop.PropertyType.GetProperties(); foreach (var item in anoyClass) { if (graphNoPointParentProps.Any(a => a.Name == item.Name)) { var property = graphNoPointParentProps.Where(a => a.Name == item.Name).FirstOrDefault(); if (property.PropertyType != item.PropertyType) { continue; } if (bindings.Any(e => e.Member.Name == property.Name)) { continue; } var pExp = Expression.Property(tExp, prop.Name); var realExp = Expression.Property(pExp, item.Name); bindings.Add(Expression.Bind(property, realExp)); } } } var init = Expression.MemberInit(constructor, bindings); var final = Expression.Lambda<Func<T, TGraph>>(init, tExp); return query.Select(final); }
先給列表返回模型的欄位加上特性:
/// <summary> /// 列表返回模型 /// </summary> public class ClassStudentModel { /// <summary> /// 學生標識 /// </summary> [ParentAnonymous("s")] public Guid Id { get; set; } /// <summary> /// 學號 /// </summary> public string Number { get; set; } /// <summary> /// 學生名稱 /// </summary> public string Name { get; set; } /// <summary> /// 班級總人數 /// </summary> public int TotalNumber { get; set; } }
然後再改一下查詢方法:
var query = from s in dbStore.Set<StudentInfo>() from c in dbStore.Set<ClassInfo>().Where(c => c.Id == s.ClassId).DefaultIfEmpty() select new { s, c }; var result = query.AutoSelect(new ClassStudentModel()).ToList();
至此,自動映射,大功告成