EFCore擴展Select方法(根據實體定制查詢語句)
通常用操作數據庫的時候查詢返回的字段是跟 我們的定義的實體是不一致的,所以往往針對UI或者接口層創建大量的Model, 而且需要手動對應字段,非常繁瑣。 本文將通過表達式樹解決這些重復的過程。
先貼上實現代碼
Queryable 類中 的擴展方法 Select<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector) 需要參數 Expression<Func<TSource, TResult>> selector 只要構造相應的表達式樹即可實現自定義映射
using System.Collections; using System.ComponentModel.DataAnnotations.Schema; using System.Linq.Expressions; using System.Reflection; using static System.Linq.Expressions.Expression; public static class QueryableExtentions { public static IQueryable<TTarget> Select<TTarget>(this IQueryable<object> query) { return Queryable.Select(query, GetLamda<object, TTarget>(query.GetType().GetGenericArguments()[0])); } public static IQueryable<TTarget> Select<TSource, TTarget>(this IQueryable<TSource> query) { return Queryable.Select(query, GetLamda<TSource, TTarget>()); } public static Expression<Func<TSource, TTarget>> GetLamda<TSource, TTarget>(Type type = null) { var sourceType = typeof(TSource); var targetType = typeof(TTarget); var parameter = Parameter(sourceType); Expression propertyParameter; if (type != null) { propertyParameter = Convert(parameter, type); sourceType = type; } else propertyParameter = parameter; return Lambda<Func<TSource, TTarget>>(GetExpression(propertyParameter, sourceType, targetType), parameter); } public static MemberInitExpression GetExpression(Expression parameter, Type sourceType, Type targetType) { var memberBindings = new List<MemberBinding>(); foreach (var targetItem in targetType.GetProperties().Where(x => x.CanWrite)) { var fromEntityAttr = targetItem.GetCustomAttribute<FromEntityAttribute>(); if (fromEntityAttr != null) { var property = GetFromEntityExpression(parameter, sourceType, fromEntityAttr); if (property != null) memberBindings.Add(Bind(targetItem, property)); continue; } var sourceItem = sourceType.GetProperty(targetItem.Name); if (sourceItem == null)//當沒有對應的屬性時,查找 實體名+屬性 { var complexSourceItemProperty = GetCombinationExpression(parameter, sourceType, targetItem); if (complexSourceItemProperty != null) memberBindings.Add(Bind(targetItem, complexSourceItemProperty)); continue; } //判斷實體的讀寫權限 if (sourceItem == null || !sourceItem.CanRead) continue; //標注NotMapped特性的屬性忽略轉換 if (sourceItem.GetCustomAttribute<NotMappedAttribute>() != null) continue; var sourceProperty = Property(parameter, sourceItem); //當非值類型且類型不相同時 if (!sourceItem.PropertyType.IsValueType && sourceItem.PropertyType != targetItem.PropertyType && targetItem.PropertyType != targetType) { //判斷都是(非泛型、非數組)class if (sourceItem.PropertyType.IsClass && targetItem.PropertyType.IsClass && !sourceItem.PropertyType.IsArray && !targetItem.PropertyType.IsArray && !sourceItem.PropertyType.IsGenericType && !targetItem.PropertyType.IsGenericType) { var expression = GetExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType); memberBindings.Add(Bind(targetItem, expression)); } continue; } if (targetItem.PropertyType != sourceItem.PropertyType) continue; memberBindings.Add(Bind(targetItem, sourceProperty)); } return MemberInit(New(targetType), memberBindings); } /// <summary> /// 根據FromEntityAttribute 的值獲取屬性對應的路徑 /// </summary> /// <param name="sourceProperty"></param> /// <param name="sourceType"></param> /// <param name="fromEntityAttribute"></param> /// <returns></returns> private static Expression GetFromEntityExpression(Expression sourceProperty, Type sourceType, FromEntityAttribute fromEntityAttribute) { var findType = sourceType; var resultProperty = sourceProperty; var tableNames = fromEntityAttribute.EntityNames; if (tableNames == null) { var columnProperty = findType.GetProperty(fromEntityAttribute.EntityColuum); if (columnProperty == null) return null; else return Property(resultProperty, columnProperty); } for (int i = tableNames.Length - 1; i >= 0; i--) { var tableProperty = findType.GetProperty(tableNames[i]); if (tableProperty == null) return null; findType = tableProperty.PropertyType; resultProperty = Property(resultProperty, tableProperty); } var property = findType.GetProperty(fromEntityAttribute.EntityColuum); if (property == null) return null; else return Property(resultProperty, property); } /// <summary> /// 根據組合字段獲取其屬性路徑 /// </summary> /// <param name="sourceProperty"></param> /// <param name="sourcePropertys"></param> /// <param name="targetItem"></param> /// <returns></returns> private static Expression GetCombinationExpression(Expression sourceProperty, Type sourceType, PropertyInfo targetItem) { foreach (var item in sourceType.GetProperties().Where(x => x.CanRead)) { if (targetItem.Name.StartsWith(item.Name)) { if (item != null && item.CanRead && item.PropertyType.IsClass && !item.PropertyType.IsGenericType) { var rightName = targetItem.Name.Substring(item.Name.Length); var complexSourceItem = item.PropertyType.GetProperty(rightName); if (complexSourceItem != null && complexSourceItem.CanRead) return Property(Property(sourceProperty, item), complexSourceItem); } } } return null; } } /// <summary> /// 用於標注字段 來自哪個表的的哪一列(僅限於有關聯的表中) /// </summary> public class FromEntityAttribute : Attribute { /// <summary> /// 類名(表名) /// </summary> public string[] EntityNames { get; } /// <summary> /// 字段(列名) /// </summary> public string EntityColuum { get; } /// <summary> /// 列名 + 該列的表名 + 該列的表的上一級表名 /// </summary> /// <param name="entityColuum"></param> /// <param name="entityNames"></param> public FromEntityAttribute(string entityColuum, params string[] entityNames) { EntityNames = entityNames; EntityColuum = entityColuum; } }
調用方法如下,先構造測試類
public partial class User { public int Id { get; set; } [Required] [StringLength(50)] public string Name { get; set; } public int RoleId { get; set; } [ForeignKey(nameof(RoleId))] public virtual Role Role { get; set; } } public partial class Role { public int Id { get; set; } public string Name { get; set; } public int DepartmentId { get; set; } [ForeignKey(nameof(DepartmentId))] public virtual Department Department { get; set; } } public partial class Department { public int Id { get; set; } [Required] [StringLength(50)] public string Name { get; set; } }
如上所以構造了,用戶表,角色表,和部門表。 查詢某個用戶 的角色名和部門名 則需要關聯 角色表和部門表
public partial class UserModel { public string Name { get; set; } public string RoleName { get; set; } //[FromEntity("Name","Role")] //public string RoleName1 { get; set; } [FromEntity("Name", "Department", "Role")] public string DepartmentName { get; set; } //public virtual RoleModel Role { get; set; } //[FromEntity("Department", "Role")] //public virtual Department Department { get; set; } }
查詢代碼如下
static void Main(string[] args) { using (var context = new TestContext()) { var list = context.User.Select<UserModel>().ToList(); } Console.WriteLine($"------------結束--------------------"); Console.ReadLine(); }
生成的sql語句 如下圖
實體中的 DepartmentName 由於通過用戶表關聯角色表,再通過角色表關聯 部門表得到故 需要通過特性標注
當然結果實體也可以多級關聯
public partial class UserModel { public string Name { get; set; } public string RoleName { get; set; } [FromEntity("Name","Role")] public string RoleName1 { get; set; } [FromEntity("Name", "Department", "Role")] public string DepartmentName { get; set; } public virtual RoleModel Role { get; set; } [FromEntity("Department", "Role")] public virtual Department Department { get; set; } } public partial class RoleModel { public string Name { get; set; } public string DepartmentName { get; set; } public virtual DepartmentModel Department { get; set; } } public partial class DepartmentModel { public string Name { get; set; } }
生成的查詢語句如下圖
總結 此方案用在接口,精確查詢字段,需要強類型視圖的地方相對比較方便
作者:costyuan
GitHub地址:https://github.com/bieyuan/EFCoreSelectExtentions
地址:https://www.cnblogs.com/castyuan/p/10186619.html
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
如果文中有什么錯誤,歡迎指出,謝謝!