SqlSugarExtension.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. // Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
  2. //
  3. // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
  4. //
  5. // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
  6. using System.Text.Json;
  7. namespace Admin.NET.Core;
  8. /// <summary>
  9. /// Sqlsugar 动态查询扩展方法
  10. /// </summary>
  11. public static class SqlSugarExtension
  12. {
  13. public static ISugarQueryable<T> SearchBy<T>(this ISugarQueryable<T> queryable, BaseFilter filter)
  14. {
  15. return queryable.SearchByKeyword(filter.Keyword)
  16. .AdvancedSearch(filter.Search)
  17. .AdvancedFilter(filter.Filter);
  18. }
  19. public static ISugarQueryable<T> SearchByKeyword<T>(this ISugarQueryable<T> queryable, string keyword)
  20. {
  21. return queryable.AdvancedSearch(new Search { Keyword = keyword });
  22. }
  23. public static ISugarQueryable<T> AdvancedSearch<T>(this ISugarQueryable<T> queryable, Search search)
  24. {
  25. if (!string.IsNullOrWhiteSpace(search?.Keyword))
  26. {
  27. var paramExpr = Expression.Parameter(typeof(T));
  28. Expression right = Expression.Constant(false);
  29. if (search.Fields?.Any() is true)
  30. {
  31. foreach (string field in search.Fields)
  32. {
  33. MemberExpression propertyExpr = GetPropertyExpression(field, paramExpr);
  34. var left = AddSearchPropertyByKeyword<T>(propertyExpr, search.Keyword);
  35. right = Expression.Or(left, right);
  36. }
  37. }
  38. else
  39. {
  40. var properties = typeof(T).GetProperties()
  41. .Where(prop => Nullable.GetUnderlyingType(prop.PropertyType) == null
  42. && !prop.PropertyType.IsEnum
  43. && Type.GetTypeCode(prop.PropertyType) != TypeCode.Object);
  44. foreach (var property in properties)
  45. {
  46. var propertyExpr = Expression.Property(paramExpr, property);
  47. var left = AddSearchPropertyByKeyword<T>(propertyExpr, search.Keyword);
  48. right = Expression.Or(left, right);
  49. }
  50. }
  51. var lambda = Expression.Lambda<Func<T, bool>>(right, paramExpr);
  52. return queryable.Where(lambda);
  53. }
  54. return queryable;
  55. }
  56. public static ISugarQueryable<T> AdvancedFilter<T>(this ISugarQueryable<T> queryable, Filter filter)
  57. {
  58. if (filter is not null)
  59. {
  60. var parameter = Expression.Parameter(typeof(T));
  61. Expression binaryExpresioFilter;
  62. if (filter.Logic.HasValue)
  63. {
  64. if (filter.Filters is null) throw new ArgumentException("The Filters attribute is required when declaring a logic");
  65. binaryExpresioFilter = CreateFilterExpression(filter.Logic.Value, filter.Filters, parameter);
  66. }
  67. else
  68. {
  69. var filterValid = GetValidFilter(filter);
  70. binaryExpresioFilter = CreateFilterExpression(filterValid.Field!, filterValid.Operator.Value, filterValid.Value, parameter);
  71. }
  72. var lambda = Expression.Lambda<Func<T, bool>>(binaryExpresioFilter, parameter);
  73. return queryable.Where(lambda);
  74. }
  75. return queryable;
  76. }
  77. private static Expression CombineFilter(
  78. FilterLogicEnum filterLogic,
  79. Expression bExpresionBase,
  80. Expression bExpresion)
  81. {
  82. return filterLogic switch
  83. {
  84. FilterLogicEnum.And => Expression.And(bExpresionBase, bExpresion),
  85. FilterLogicEnum.Or => Expression.Or(bExpresionBase, bExpresion),
  86. FilterLogicEnum.Xor => Expression.ExclusiveOr(bExpresionBase, bExpresion),
  87. _ => throw new ArgumentException("FilterLogic is not valid.", nameof(filterLogic)),
  88. };
  89. }
  90. private static Filter GetValidFilter(Filter filter)
  91. {
  92. if (string.IsNullOrEmpty(filter.Field)) throw new ArgumentException("The field attribute is required when declaring a filter");
  93. if (filter.Operator.IsNullOrEmpty()) throw new ArgumentException("The Operator attribute is required when declaring a filter");
  94. return filter;
  95. }
  96. private static Expression CreateFilterExpression(
  97. FilterLogicEnum filterLogic,
  98. IEnumerable<Filter> filters,
  99. ParameterExpression parameter)
  100. {
  101. Expression filterExpression = default!;
  102. foreach (var filter in filters)
  103. {
  104. Expression bExpresionFilter;
  105. if (filter.Logic.HasValue)
  106. {
  107. if (filter.Filters is null) throw new ArgumentException("The Filters attribute is required when declaring a logic");
  108. bExpresionFilter = CreateFilterExpression(filter.Logic.Value, filter.Filters, parameter);
  109. }
  110. else
  111. {
  112. var filterValid = GetValidFilter(filter);
  113. bExpresionFilter = CreateFilterExpression(filterValid.Field!, filterValid.Operator.Value, filterValid.Value, parameter);
  114. }
  115. filterExpression = filterExpression is null ? bExpresionFilter : CombineFilter(filterLogic, filterExpression, bExpresionFilter);
  116. }
  117. return filterExpression;
  118. }
  119. private static Expression CreateFilterExpression(
  120. string field,
  121. FilterOperatorEnum filterOperator,
  122. object? value,
  123. ParameterExpression parameter)
  124. {
  125. var propertyExpresion = GetPropertyExpression(field, parameter);
  126. var valueExpresion = GeValuetExpression(field, value, propertyExpresion.Type);
  127. return CreateFilterExpression(propertyExpresion, valueExpresion, filterOperator);
  128. }
  129. private static Expression CreateFilterExpression(
  130. MemberExpression memberExpression,
  131. ConstantExpression constantExpression,
  132. FilterOperatorEnum filterOperator)
  133. {
  134. return filterOperator switch
  135. {
  136. FilterOperatorEnum.EQ => Expression.Equal(memberExpression, constantExpression),
  137. FilterOperatorEnum.NEQ => Expression.NotEqual(memberExpression, constantExpression),
  138. FilterOperatorEnum.LT => Expression.LessThan(memberExpression, constantExpression),
  139. FilterOperatorEnum.LTE => Expression.LessThanOrEqual(memberExpression, constantExpression),
  140. FilterOperatorEnum.GT => Expression.GreaterThan(memberExpression, constantExpression),
  141. FilterOperatorEnum.GTE => Expression.GreaterThanOrEqual(memberExpression, constantExpression),
  142. FilterOperatorEnum.Contains => Expression.Call(memberExpression, nameof(FilterOperatorEnum.Contains), null, constantExpression),
  143. FilterOperatorEnum.StartsWith => Expression.Call(memberExpression, nameof(FilterOperatorEnum.StartsWith), null, constantExpression),
  144. FilterOperatorEnum.EndsWith => Expression.Call(memberExpression, nameof(FilterOperatorEnum.EndsWith), null, constantExpression),
  145. _ => throw new ArgumentException("Filter Operator is not valid."),
  146. };
  147. }
  148. private static string GetStringFromJsonElement(object value)
  149. {
  150. if (value is JsonElement) return ((JsonElement)value).GetString()!;
  151. if (value is string) return (string)value;
  152. return value?.ToString();
  153. }
  154. private static ConstantExpression GeValuetExpression(
  155. string field,
  156. object? value,
  157. Type propertyType)
  158. {
  159. if (value == null) return Expression.Constant(null, propertyType);
  160. if (propertyType.IsEnum)
  161. {
  162. string? stringEnum = GetStringFromJsonElement(value);
  163. if (!Enum.TryParse(propertyType, stringEnum, true, out object? valueparsed)) throw new ArgumentException(string.Format("Value {0} is not valid for {1}", value, field));
  164. return Expression.Constant(valueparsed, propertyType);
  165. }
  166. if (propertyType == typeof(long) || propertyType == typeof(long?))
  167. {
  168. string? stringLong = GetStringFromJsonElement(value);
  169. if (!long.TryParse(stringLong, out long valueparsed)) throw new ArgumentException(string.Format("Value {0} is not valid for {1}", value, field));
  170. return Expression.Constant(valueparsed, propertyType);
  171. }
  172. if (propertyType == typeof(Guid))
  173. {
  174. string? stringGuid = GetStringFromJsonElement(value);
  175. if (!Guid.TryParse(stringGuid, out Guid valueparsed)) throw new ArgumentException(string.Format("Value {0} is not valid for {1}", value, field));
  176. return Expression.Constant(valueparsed, propertyType);
  177. }
  178. if (propertyType == typeof(string))
  179. {
  180. string? text = GetStringFromJsonElement(value);
  181. return Expression.Constant(text, propertyType);
  182. }
  183. if (propertyType == typeof(DateTime) || propertyType == typeof(DateTime?))
  184. {
  185. string? text = GetStringFromJsonElement(value);
  186. return Expression.Constant(ChangeType(text, propertyType), propertyType);
  187. }
  188. return Expression.Constant(ChangeType(((JsonElement)value).GetRawText(), propertyType), propertyType);
  189. }
  190. private static dynamic? ChangeType(object value, Type conversion)
  191. {
  192. var t = conversion;
  193. if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
  194. {
  195. if (value == null)
  196. {
  197. return null;
  198. }
  199. t = Nullable.GetUnderlyingType(t);
  200. }
  201. return Convert.ChangeType(value, t!);
  202. }
  203. private static MemberExpression GetPropertyExpression(
  204. string propertyName,
  205. ParameterExpression parameter)
  206. {
  207. Expression propertyExpression = parameter;
  208. foreach (string member in propertyName.Split('.'))
  209. {
  210. propertyExpression = Expression.PropertyOrField(propertyExpression, member);
  211. }
  212. return (MemberExpression)propertyExpression;
  213. }
  214. private static Expression AddSearchPropertyByKeyword<T>(
  215. Expression propertyExpr,
  216. string keyword,
  217. FilterOperatorEnum operatorSearch = FilterOperatorEnum.Contains)
  218. {
  219. if (propertyExpr is not MemberExpression memberExpr || memberExpr.Member is not PropertyInfo property)
  220. {
  221. throw new ArgumentException("propertyExpr must be a property expression.", nameof(propertyExpr));
  222. }
  223. ConstantExpression constant = Expression.Constant(keyword);
  224. MethodInfo method = operatorSearch switch
  225. {
  226. FilterOperatorEnum.Contains => typeof(string).GetMethod(nameof(FilterOperatorEnum.Contains), new Type[] { typeof(string) }),
  227. FilterOperatorEnum.StartsWith => typeof(string).GetMethod(nameof(FilterOperatorEnum.StartsWith), new Type[] { typeof(string) }),
  228. FilterOperatorEnum.EndsWith => typeof(string).GetMethod(nameof(FilterOperatorEnum.EndsWith), new Type[] { typeof(string) }),
  229. _ => throw new ArgumentException("Filter Operator is not valid."),
  230. };
  231. Expression selectorExpr =
  232. property.PropertyType == typeof(string)
  233. ? propertyExpr
  234. : Expression.Condition(
  235. Expression.Equal(Expression.Convert(propertyExpr, typeof(object)), Expression.Constant(null, typeof(object))),
  236. Expression.Constant(null, typeof(string)),
  237. Expression.Call(propertyExpr, "ToString", null, null));
  238. return Expression.Call(selectorExpr, method, constant);
  239. }
  240. #region 视图操作
  241. /// <summary>
  242. /// 获取映射SQL语句, 用于创建视图
  243. /// </summary>
  244. /// <param name="queryable"></param>
  245. /// <typeparam name="T"></typeparam>
  246. /// <returns></returns>
  247. public static string ToMappedSqlString<T>(this ISugarQueryable<T> queryable) where T : class
  248. {
  249. ArgumentNullException.ThrowIfNull(queryable);
  250. // 获取实体映射信息
  251. var entityInfo = queryable.Context.EntityMaintenance.GetEntityInfo(typeof(T));
  252. if (entityInfo?.Columns == null || entityInfo.Columns.Count == 0) return queryable.ToSqlString();
  253. // 构建需要替换的字段名映射(只处理实际有差异的字段)
  254. var nameMap = entityInfo.Columns
  255. .Where(c => !string.Equals(c.PropertyName, c.DbColumnName, StringComparison.OrdinalIgnoreCase))
  256. .ToDictionary(k => k.PropertyName.ToLower(), v => v.DbColumnName, StringComparer.OrdinalIgnoreCase);
  257. if (nameMap.Count == 0) return queryable.ToSqlString();
  258. // 预编译正则表达式提升性能
  259. var sql = queryable.ToSqlString();
  260. foreach (var kv in nameMap)
  261. {
  262. sql = Regex.Replace(sql, $@"\b{kv.Key}\b", kv.Value ?? kv.Key, RegexOptions.IgnoreCase | RegexOptions.Compiled); // 单词边界匹配
  263. }
  264. return sql;
  265. }
  266. #endregion 视图操作
  267. }