PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/src/ServiceStack.Server/AutoQueryFeature.cs

http://github.com/ServiceStack/ServiceStack
C# | 1575 lines | 1273 code | 255 blank | 47 comment | 248 complexity | 44d21a15ca1e10fbfd4fb60aeac393eb MD5 | raw file
Possible License(s): BSD-3-Clause

Large files files are truncated, but you can click here to view the full file

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Data;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Reflection.Emit;
  8. using System.Runtime.Serialization;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using ServiceStack.MiniProfiler;
  12. using ServiceStack.Web;
  13. using ServiceStack.Data;
  14. using ServiceStack.Extensions;
  15. using ServiceStack.Host;
  16. using ServiceStack.OrmLite;
  17. using ServiceStack.Text;
  18. namespace ServiceStack
  19. {
  20. public delegate void QueryFilterDelegate(ISqlExpression q, IQueryDb dto, IRequest req);
  21. public class QueryDbFilterContext
  22. {
  23. public IDbConnection Db { get; set; }
  24. public List<Command> Commands { get; set; }
  25. public IQueryDb Dto { get; set; }
  26. public ISqlExpression SqlExpression { get; set; }
  27. public IQueryResponse Response { get; set; }
  28. }
  29. public partial class AutoQueryFeature : IPlugin, IPostInitPlugin, Model.IHasStringId
  30. {
  31. public string Id { get; set; } = Plugins.AutoQuery;
  32. private static readonly string[] DefaultIgnoreProperties =
  33. {"Skip", "Take", "OrderBy", "OrderByDesc", "Fields", "_select", "_from", "_join", "_where"};
  34. public HashSet<string> IgnoreProperties { get; set; } = new HashSet<string>(DefaultIgnoreProperties, StringComparer.OrdinalIgnoreCase);
  35. public HashSet<string> IllegalSqlFragmentTokens { get; set; } = new HashSet<string>(OrmLiteUtils.IllegalSqlFragmentTokens);
  36. public HashSet<Assembly> LoadFromAssemblies { get; set; } = new HashSet<Assembly>();
  37. public int? MaxLimit { get; set; }
  38. public bool IncludeTotal { get; set; }
  39. public bool StripUpperInLike { get; set; } = OrmLiteConfig.StripUpperInLike;
  40. public bool EnableUntypedQueries { get; set; } = true;
  41. public bool EnableRawSqlFilters { get; set; }
  42. public bool EnableAutoQueryViewer { get; set; } = true;
  43. public bool EnableAsync { get; set; } = true;
  44. public bool OrderByPrimaryKeyOnPagedQuery { get; set; } = true;
  45. public string UseNamedConnection { get; set; }
  46. public Type AutoQueryServiceBaseType { get; set; } = typeof(AutoQueryServiceBase);
  47. public QueryFilterDelegate GlobalQueryFilter { get; set; }
  48. public Dictionary<Type, QueryFilterDelegate> QueryFilters { get; set; } = new Dictionary<Type, QueryFilterDelegate>();
  49. public List<Action<QueryDbFilterContext>> ResponseFilters { get; set; }
  50. public Action<Type, TypeBuilder, MethodBuilder, ILGenerator> GenerateServiceFilter { get; set; }
  51. /// <summary>
  52. /// Enable code-gen of CRUD Services for registered database in any supported Add ServiceStack Reference Language:
  53. /// - /autocrud/{Include}/{Lang}
  54. ///
  55. /// View DB Schema Services:
  56. /// - /autocrud/schema - Default DB
  57. /// - /autocrud/schema/{Schema} - Specified DB Schema
  58. /// </summary>
  59. public IGenerateCrudServices GenerateCrudServices { get; set; }
  60. public Dictionary<string, string> ImplicitConventions = new Dictionary<string, string>
  61. {
  62. {"%Above%", SqlTemplate.GreaterThan},
  63. {"Begin%", SqlTemplate.GreaterThan},
  64. {"%Beyond%", SqlTemplate.GreaterThan},
  65. {"%Over%", SqlTemplate.GreaterThan},
  66. {"%OlderThan", SqlTemplate.GreaterThan},
  67. {"%After%", SqlTemplate.GreaterThan},
  68. {"OnOrAfter%", SqlTemplate.GreaterThanOrEqual},
  69. {"%From%", SqlTemplate.GreaterThanOrEqual},
  70. {"Since%", SqlTemplate.GreaterThanOrEqual},
  71. {"Start%", SqlTemplate.GreaterThanOrEqual},
  72. {"%Higher%", SqlTemplate.GreaterThanOrEqual},
  73. {">%", SqlTemplate.GreaterThanOrEqual},
  74. {"%>", SqlTemplate.GreaterThan},
  75. {"%!", SqlTemplate.NotEqual},
  76. {"%GreaterThanOrEqualTo%", SqlTemplate.GreaterThanOrEqual},
  77. {"%GreaterThan%", SqlTemplate.GreaterThan},
  78. {"%LessThan%", SqlTemplate.LessThan},
  79. {"%LessThanOrEqualTo%", SqlTemplate.LessThanOrEqual},
  80. {"%NotEqualTo", SqlTemplate.NotEqual},
  81. {"Behind%", SqlTemplate.LessThan},
  82. {"%Below%", SqlTemplate.LessThan},
  83. {"%Under%", SqlTemplate.LessThan},
  84. {"%Lower%", SqlTemplate.LessThan},
  85. {"%Before%", SqlTemplate.LessThan},
  86. {"%YoungerThan", SqlTemplate.LessThan},
  87. {"OnOrBefore%", SqlTemplate.LessThanOrEqual},
  88. {"End%", SqlTemplate.LessThanOrEqual},
  89. {"Stop%", SqlTemplate.LessThanOrEqual},
  90. {"To%", SqlTemplate.LessThanOrEqual},
  91. {"Until%", SqlTemplate.LessThanOrEqual},
  92. {"%<", SqlTemplate.LessThanOrEqual},
  93. {"<%", SqlTemplate.LessThan},
  94. {"%Like%", SqlTemplate.CaseInsensitiveLike },
  95. {"%In", "{Field} IN ({Values})"},
  96. {"%Ids", "{Field} IN ({Values})"},
  97. {"%Between%", "{Field} BETWEEN {Value1} AND {Value2}"},
  98. };
  99. public Dictionary<string, QueryDbFieldAttribute> StartsWithConventions =
  100. new Dictionary<string, QueryDbFieldAttribute>();
  101. public Dictionary<string, QueryDbFieldAttribute> EndsWithConventions = new Dictionary<string, QueryDbFieldAttribute>
  102. {
  103. { "StartsWith", new QueryDbFieldAttribute { Template = SqlTemplate.CaseInsensitiveLike, ValueFormat = "{0}%" }},
  104. { "Contains", new QueryDbFieldAttribute { Template = SqlTemplate.CaseInsensitiveLike, ValueFormat = "%{0}%" }},
  105. { "EndsWith", new QueryDbFieldAttribute { Template = SqlTemplate.CaseInsensitiveLike, ValueFormat = "%{0}" }},
  106. };
  107. public List<AutoQueryConvention> ViewerConventions { get; set; } = new List<AutoQueryConvention>
  108. {
  109. new AutoQueryConvention {Name = "=", Value = "%"},
  110. new AutoQueryConvention {Name = "!=", Value = "%!"},
  111. new AutoQueryConvention {Name = ">=", Value = ">%"},
  112. new AutoQueryConvention {Name = ">", Value = "%>"},
  113. new AutoQueryConvention {Name = "<=", Value = "%<"},
  114. new AutoQueryConvention {Name = "<", Value = "<%"},
  115. new AutoQueryConvention {Name = "In", Value = "%In"},
  116. new AutoQueryConvention {Name = "Between", Value = "%Between"},
  117. new AutoQueryConvention {Name = "Starts With", Value = "%StartsWith", Types = "string"},
  118. new AutoQueryConvention {Name = "Contains", Value = "%Contains", Types = "string"},
  119. new AutoQueryConvention {Name = "Ends With", Value = "%EndsWith", Types = "string"},
  120. };
  121. public AutoQueryFeature()
  122. {
  123. ResponseFilters = new List<Action<QueryDbFilterContext>> { IncludeAggregates };
  124. }
  125. public void Register(IAppHost appHost)
  126. {
  127. if (StripUpperInLike)
  128. {
  129. if (ImplicitConventions.TryGetValue("%Like%", out var convention) && convention == SqlTemplate.CaseInsensitiveLike)
  130. ImplicitConventions["%Like%"] = SqlTemplate.CaseSensitiveLike;
  131. foreach (var attr in EndsWithConventions)
  132. {
  133. if (attr.Value.Template == SqlTemplate.CaseInsensitiveLike)
  134. attr.Value.Template = SqlTemplate.CaseSensitiveLike;
  135. }
  136. }
  137. foreach (var entry in ImplicitConventions)
  138. {
  139. var key = entry.Key.Trim('%');
  140. var fmt = entry.Value;
  141. var query = new QueryDbFieldAttribute { Template = fmt }.Init();
  142. if (entry.Key.EndsWith("%"))
  143. StartsWithConventions[key] = query;
  144. if (entry.Key.StartsWith("%"))
  145. EndsWithConventions[key] = query;
  146. }
  147. var container = appHost.GetContainer();
  148. container.AddSingleton<IAutoQueryDb>(c => new AutoQuery
  149. {
  150. IgnoreProperties = IgnoreProperties,
  151. IllegalSqlFragmentTokens = IllegalSqlFragmentTokens,
  152. MaxLimit = MaxLimit,
  153. IncludeTotal = IncludeTotal,
  154. EnableUntypedQueries = EnableUntypedQueries,
  155. EnableSqlFilters = EnableRawSqlFilters,
  156. OrderByPrimaryKeyOnLimitQuery = OrderByPrimaryKeyOnPagedQuery,
  157. GlobalQueryFilter = GlobalQueryFilter,
  158. QueryFilters = QueryFilters,
  159. ResponseFilters = ResponseFilters,
  160. StartsWithConventions = StartsWithConventions,
  161. EndsWithConventions = EndsWithConventions,
  162. UseNamedConnection = UseNamedConnection,
  163. });
  164. appHost.Metadata.GetOperationAssemblies()
  165. .Each(x => LoadFromAssemblies.Add(x));
  166. ((ServiceStackHost)appHost).ServiceAssemblies.Each(x => {
  167. if (!LoadFromAssemblies.Contains(x))
  168. LoadFromAssemblies.Add(x);
  169. });
  170. appHost.AddToAppMetadata(meta => {
  171. meta.Plugins.AutoQuery = new AutoQueryInfo {
  172. MaxLimit = MaxLimit,
  173. UntypedQueries = EnableUntypedQueries.NullIfFalse(),
  174. RawSqlFilters = EnableRawSqlFilters.NullIfFalse(),
  175. Async = EnableAsync.NullIfFalse(),
  176. AutoQueryViewer = EnableAutoQueryViewer.NullIfFalse(),
  177. OrderByPrimaryKey = OrderByPrimaryKeyOnPagedQuery.NullIfFalse(),
  178. CrudEvents = container.Exists<ICrudEvents>().NullIfFalse(),
  179. CrudEventsServices = (ServiceRoutes.ContainsKey(typeof(GetCrudEventsService)) && AccessRole != null).NullIfFalse(),
  180. AccessRole = AccessRole,
  181. NamedConnection = UseNamedConnection,
  182. ViewerConventions = ViewerConventions,
  183. };
  184. });
  185. if (EnableAutoQueryViewer && appHost.GetPlugin<AutoQueryMetadataFeature>() == null)
  186. appHost.LoadPlugin(new AutoQueryMetadataFeature { MaxLimit = MaxLimit });
  187. appHost.GetPlugin<MetadataFeature>()?.ExportTypes.Add(typeof(CrudEvent));
  188. //CRUD Services
  189. GenerateCrudServices?.Register(appHost);
  190. OnRegister(appHost);
  191. }
  192. public void AfterPluginsLoaded(IAppHost appHost)
  193. {
  194. var scannedTypes = new HashSet<Type>();
  195. var crudServices = GenerateCrudServices?.GenerateMissingServices(this);
  196. crudServices?.Each(x => scannedTypes.Add(x));
  197. foreach (var assembly in LoadFromAssemblies)
  198. {
  199. try
  200. {
  201. assembly.GetTypes().Each(x => scannedTypes.Add(x));
  202. }
  203. catch (Exception ex)
  204. {
  205. appHost.NotifyStartupException(ex);
  206. }
  207. }
  208. var missingQueryRequestTypes = scannedTypes
  209. .Where(x => x.HasInterface(typeof(IQueryDb)) &&
  210. !appHost.Metadata.OperationsMap.ContainsKey(x))
  211. .ToList();
  212. var missingCrudRequestTypes = scannedTypes
  213. .Where(x => x.HasInterface(typeof(ICrud)) &&
  214. !appHost.Metadata.OperationsMap.ContainsKey(x))
  215. .ToList();
  216. if (missingQueryRequestTypes.Count == 0 && missingCrudRequestTypes.Count == 0)
  217. return;
  218. var serviceType = GenerateMissingQueryServices(missingQueryRequestTypes, missingCrudRequestTypes);
  219. appHost.RegisterService(serviceType);
  220. }
  221. Type GenerateMissingQueryServices(
  222. List<Type> missingQueryRequestTypes, List<Type> missingCrudRequestTypes)
  223. {
  224. var assemblyName = new AssemblyName { Name = "tmpAssembly" };
  225. var typeBuilder =
  226. AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run)
  227. .DefineDynamicModule("tmpModule")
  228. .DefineType("__AutoQueryServices",
  229. TypeAttributes.Public | TypeAttributes.Class,
  230. AutoQueryServiceBaseType);
  231. foreach (var requestType in missingQueryRequestTypes)
  232. {
  233. if (requestType.IsAbstract || requestType.IsGenericType)
  234. continue;
  235. var genericDef = requestType.GetTypeWithGenericTypeDefinitionOf(typeof(IQueryDb<,>));
  236. var hasExplicitInto = genericDef != null;
  237. if (genericDef == null)
  238. genericDef = requestType.GetTypeWithGenericTypeDefinitionOf(typeof(IQueryDb<>));
  239. if (genericDef == null)
  240. continue;
  241. var method = typeBuilder.DefineMethod(ActionContext.AnyMethod, MethodAttributes.Public | MethodAttributes.Virtual,
  242. CallingConventions.Standard,
  243. returnType: typeof(object),
  244. parameterTypes: new[] { requestType });
  245. var il = method.GetILGenerator();
  246. GenerateServiceFilter?.Invoke(requestType, typeBuilder, method, il);
  247. var queryMethod = EnableAsync
  248. ? nameof(AutoQueryServiceBase.ExecAsync)
  249. : nameof(AutoQueryServiceBase.Exec);
  250. var genericArgs = genericDef.GetGenericArguments();
  251. var mi = AutoQueryServiceBaseType.GetMethods()
  252. .First(x => x.Name == queryMethod &&
  253. x.GetGenericArguments().Length == genericArgs.Length);
  254. var genericMi = mi.MakeGenericMethod(genericArgs);
  255. var queryType = hasExplicitInto
  256. ? typeof(IQueryDb<,>).MakeGenericType(genericArgs)
  257. : typeof(IQueryDb<>).MakeGenericType(genericArgs);
  258. il.Emit(OpCodes.Nop);
  259. il.Emit(OpCodes.Ldarg_0);
  260. il.Emit(OpCodes.Ldarg_1);
  261. il.Emit(OpCodes.Box, queryType);
  262. il.Emit(OpCodes.Callvirt, genericMi);
  263. il.Emit(OpCodes.Ret);
  264. }
  265. foreach (var requestType in missingCrudRequestTypes)
  266. {
  267. if (requestType.IsAbstract || requestType.IsGenericType)
  268. continue;
  269. var crudTypes = AutoCrudOperation.GetAutoCrudDtoType(requestType);
  270. if (crudTypes == null)
  271. continue;
  272. var genericDef = crudTypes.Value.GenericDef;
  273. var crudType = crudTypes.Value.ModelType;
  274. var methodName = crudType.Name.LeftPart('`').Substring(1);
  275. methodName = methodName.Substring(0, methodName.Length - 2);
  276. if (!requestType.HasInterface(typeof(IReturnVoid)) &&
  277. !requestType.IsOrHasGenericInterfaceTypeOf(typeof(IReturn<>)))
  278. throw new NotSupportedException($"'{requestType.Name}' I{methodName}Db<T> AutoQuery Service must implement IReturn<T> or IReturnVoid");
  279. var method = typeBuilder.DefineMethod(ActionContext.AnyMethod, MethodAttributes.Public | MethodAttributes.Virtual,
  280. CallingConventions.Standard,
  281. returnType: typeof(object),
  282. parameterTypes: new[] { requestType });
  283. var il = method.GetILGenerator();
  284. GenerateServiceFilter?.Invoke(requestType, typeBuilder, method, il);
  285. var crudMethod = EnableAsync
  286. ? methodName + "Async"
  287. : methodName;
  288. var genericArgs = genericDef.GetGenericArguments();
  289. var mi = AutoQueryServiceBaseType.GetMethods()
  290. .First(x => x.Name == crudMethod &&
  291. x.GetGenericArguments().Length == genericArgs.Length);
  292. var genericMi = mi.MakeGenericMethod(genericArgs);
  293. var crudTypeArg = crudType.MakeGenericType(genericArgs);
  294. il.Emit(OpCodes.Nop);
  295. il.Emit(OpCodes.Ldarg_0);
  296. il.Emit(OpCodes.Ldarg_1);
  297. il.Emit(OpCodes.Box, crudTypeArg);
  298. il.Emit(OpCodes.Callvirt, genericMi);
  299. il.Emit(OpCodes.Ret);
  300. }
  301. var servicesType = typeBuilder.CreateTypeInfo().AsType();
  302. return servicesType;
  303. }
  304. public AutoQueryFeature RegisterQueryFilter<Request, From>(Action<SqlExpression<From>, Request, IRequest> filterFn)
  305. {
  306. QueryFilters[typeof(Request)] = (q, dto, req) =>
  307. filterFn((SqlExpression<From>)q, (Request)dto, req);
  308. return this;
  309. }
  310. public HashSet<string> SqlAggregateFunctions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
  311. {
  312. "AVG", "COUNT", "FIRST", "LAST", "MAX", "MIN", "SUM"
  313. };
  314. public void IncludeAggregates(QueryDbFilterContext ctx)
  315. {
  316. var commands = ctx.Commands;
  317. if (commands.Count == 0)
  318. return;
  319. var q = ctx.SqlExpression.GetUntypedSqlExpression()
  320. .Clone()
  321. .ClearLimits()
  322. .OrderBy();
  323. var aggregateCommands = new List<Command>();
  324. foreach (var cmd in commands)
  325. {
  326. if (!SqlAggregateFunctions.Contains(cmd.Name))
  327. continue;
  328. aggregateCommands.Add(cmd);
  329. if (cmd.Args.Count == 0)
  330. cmd.Args.Add("*".AsMemory());
  331. cmd.Original = cmd.AsMemory();
  332. var hasAlias = !cmd.Suffix.IsNullOrWhiteSpace();
  333. for (var i = 0; i < cmd.Args.Count; i++)
  334. {
  335. var arg = cmd.Args[i];
  336. string modifier = "";
  337. if (arg.StartsWith("DISTINCT ", StringComparison.OrdinalIgnoreCase))
  338. {
  339. arg.SplitOnFirst(' ', out var first, out var last);
  340. modifier = first + " ";
  341. arg = last;
  342. }
  343. var fieldRef = q.FirstMatchingField(arg.ToString());
  344. if (fieldRef != null)
  345. {
  346. //To return predictable aliases, if it's primary table don't fully qualify name
  347. var fieldName = fieldRef.Item2.FieldName;
  348. var needsRewrite = !fieldName.EqualsIgnoreCase(q.DialectProvider.NamingStrategy.GetColumnName(fieldName));
  349. if (fieldRef.Item1 != q.ModelDef || fieldRef.Item2.Alias != null || needsRewrite || hasAlias)
  350. {
  351. cmd.Args[i] = (modifier + q.DialectProvider.GetQuotedColumnName(fieldRef.Item1, fieldRef.Item2)).AsMemory();
  352. }
  353. }
  354. else
  355. {
  356. double d;
  357. if (!arg.EqualsOrdinal("*") && !double.TryParse(arg.ToString(), out d))
  358. {
  359. cmd.Args[i] = "{0}".SqlFmt(arg).AsMemory();
  360. }
  361. }
  362. }
  363. if (hasAlias)
  364. {
  365. var alias = cmd.Suffix.TrimStart().ToString();
  366. if (alias.StartsWith("as ", StringComparison.OrdinalIgnoreCase))
  367. alias = alias.Substring("as ".Length);
  368. cmd.Suffix = (" " + alias.SafeVarName()).AsMemory();
  369. }
  370. else
  371. {
  372. cmd.Suffix = (" " + q.DialectProvider.GetQuotedName(cmd.Original.ToString())).AsMemory();
  373. }
  374. }
  375. var selectSql = string.Join(", ", aggregateCommands.Map(x => x.ToString()));
  376. q.UnsafeSelect(selectSql);
  377. var rows = ctx.Db.Select<Dictionary<string, object>>(q);
  378. var row = rows.FirstOrDefault();
  379. foreach (var key in row.Keys)
  380. {
  381. ctx.Response.Meta[key] = row[key]?.ToString();
  382. }
  383. ctx.Commands.RemoveAll(aggregateCommands.Contains);
  384. }
  385. }
  386. public interface IAutoQueryDb : IAutoCrudDb
  387. {
  388. Type GetFromType(Type requestDtoType);
  389. IDbConnection GetDb(Type fromType, IRequest req = null);
  390. IDbConnection GetDb<From>(IRequest req = null);
  391. ITypedQuery GetTypedQuery(Type dtoType, Type fromType);
  392. SqlExpression<From> CreateQuery<From>(IQueryDb<From> dto, Dictionary<string, string> dynamicParams, IRequest req = null, IDbConnection db = null);
  393. QueryResponse<From> Execute<From>(IQueryDb<From> model, SqlExpression<From> query, IRequest req = null, IDbConnection db = null);
  394. Task<QueryResponse<From>> ExecuteAsync<From>(IQueryDb<From> model, SqlExpression<From> query, IRequest req = null, IDbConnection db = null);
  395. SqlExpression<From> CreateQuery<From, Into>(IQueryDb<From, Into> dto, Dictionary<string, string> dynamicParams, IRequest req = null, IDbConnection db = null);
  396. QueryResponse<Into> Execute<From, Into>(IQueryDb<From, Into> model, SqlExpression<From> query, IRequest req = null, IDbConnection db = null);
  397. Task<QueryResponse<Into>> ExecuteAsync<From, Into>(IQueryDb<From, Into> model, SqlExpression<From> query, IRequest req = null, IDbConnection db = null);
  398. ISqlExpression CreateQuery(IQueryDb dto, Dictionary<string, string> dynamicParams, IRequest req, IDbConnection db);
  399. /// <summary>
  400. /// Execute an AutoQuery Request
  401. /// </summary>
  402. IQueryResponse Execute(IQueryDb request, ISqlExpression q, IDbConnection db);
  403. /// <summary>
  404. /// Execute an AutoQuery Request
  405. /// </summary>
  406. Task<IQueryResponse> ExecuteAsync(IQueryDb request, ISqlExpression q, IDbConnection db);
  407. }
  408. public interface IAutoCrudDb
  409. {
  410. /// <summary>
  411. /// Inserts new entry into Table
  412. /// </summary>
  413. object Create<Table>(ICreateDb<Table> dto, IRequest req);
  414. /// <summary>
  415. /// Inserts new entry into Table Async
  416. /// </summary>
  417. Task<object> CreateAsync<Table>(ICreateDb<Table> dto, IRequest req);
  418. /// <summary>
  419. /// Updates entry into Table
  420. /// </summary>
  421. object Update<Table>(IUpdateDb<Table> dto, IRequest req);
  422. /// <summary>
  423. /// Updates entry into Table Async
  424. /// </summary>
  425. Task<object> UpdateAsync<Table>(IUpdateDb<Table> dto, IRequest req);
  426. /// <summary>
  427. /// Partially Updates entry into Table (Uses OrmLite UpdateNonDefaults behavior)
  428. /// </summary>
  429. object Patch<Table>(IPatchDb<Table> dto, IRequest req);
  430. /// <summary>
  431. /// Partially Updates entry into Table Async (Uses OrmLite UpdateNonDefaults behavior)
  432. /// </summary>
  433. Task<object> PatchAsync<Table>(IPatchDb<Table> dto, IRequest req);
  434. /// <summary>
  435. /// Deletes entry from Table
  436. /// </summary>
  437. object Delete<Table>(IDeleteDb<Table> dto, IRequest req);
  438. /// <summary>
  439. /// Deletes entry from Table Async
  440. /// </summary>
  441. Task<object> DeleteAsync<Table>(IDeleteDb<Table> dto, IRequest req);
  442. /// <summary>
  443. /// Inserts or Updates entry into Table
  444. /// </summary>
  445. object Save<Table>(ISaveDb<Table> dto, IRequest req);
  446. /// <summary>
  447. /// Inserts or Updates entry into Table Async
  448. /// </summary>
  449. Task<object> SaveAsync<Table>(ISaveDb<Table> dto, IRequest req);
  450. }
  451. public abstract partial class AutoQueryServiceBase : Service
  452. {
  453. public IAutoQueryDb AutoQuery { get; set; }
  454. public virtual object Exec<From>(IQueryDb<From> dto)
  455. {
  456. SqlExpression<From> q;
  457. using var db = AutoQuery.GetDb<From>(Request);
  458. using (Profiler.Current.Step("AutoQuery.CreateQuery"))
  459. {
  460. var reqParams = Request.IsInProcessRequest()
  461. ? new Dictionary<string, string>()
  462. : Request.GetRequestParams();
  463. q = AutoQuery.CreateQuery(dto, reqParams, Request, db);
  464. }
  465. using (Profiler.Current.Step("AutoQuery.Execute"))
  466. {
  467. return AutoQuery.Execute(dto, q, db);
  468. }
  469. }
  470. public virtual async Task<object> ExecAsync<From>(IQueryDb<From> dto)
  471. {
  472. SqlExpression<From> q;
  473. using var db = AutoQuery.GetDb<From>(Request);
  474. using (Profiler.Current.Step("AutoQuery.CreateQuery"))
  475. {
  476. var reqParams = Request.IsInProcessRequest()
  477. ? new Dictionary<string, string>()
  478. : Request.GetRequestParams();
  479. q = AutoQuery.CreateQuery(dto, reqParams, Request, db);
  480. }
  481. using (Profiler.Current.Step("AutoQuery.Execute"))
  482. {
  483. return await AutoQuery.ExecuteAsync(dto, q, db);
  484. }
  485. }
  486. public virtual object Exec<From, Into>(IQueryDb<From, Into> dto)
  487. {
  488. SqlExpression<From> q;
  489. using var db = AutoQuery.GetDb<From>(Request);
  490. using (Profiler.Current.Step("AutoQuery.CreateQuery"))
  491. {
  492. var reqParams = Request.IsInProcessRequest()
  493. ? new Dictionary<string, string>()
  494. : Request.GetRequestParams();
  495. q = AutoQuery.CreateQuery(dto, reqParams, Request, db);
  496. }
  497. using (Profiler.Current.Step("AutoQuery.Execute"))
  498. {
  499. return AutoQuery.Execute(dto, q, db);
  500. }
  501. }
  502. public virtual async Task<object> ExecAsync<From, Into>(IQueryDb<From, Into> dto)
  503. {
  504. SqlExpression<From> q;
  505. using var db = AutoQuery.GetDb<From>(Request);
  506. using (Profiler.Current.Step("AutoQuery.CreateQuery"))
  507. {
  508. var reqParams = Request.IsInProcessRequest()
  509. ? new Dictionary<string, string>()
  510. : Request.GetRequestParams();
  511. q = AutoQuery.CreateQuery(dto, reqParams, Request, db);
  512. }
  513. using (Profiler.Current.Step("AutoQuery.Execute"))
  514. {
  515. return await AutoQuery.ExecuteAsync(dto, q, db);
  516. }
  517. }
  518. }
  519. public interface IAutoQueryOptions
  520. {
  521. int? MaxLimit { get; set; }
  522. bool IncludeTotal { get; set; }
  523. bool EnableUntypedQueries { get; set; }
  524. bool EnableSqlFilters { get; set; }
  525. bool OrderByPrimaryKeyOnLimitQuery { get; set; }
  526. HashSet<string> IgnoreProperties { get; set; }
  527. HashSet<string> IllegalSqlFragmentTokens { get; set; }
  528. Dictionary<string, QueryDbFieldAttribute> StartsWithConventions { get; set; }
  529. Dictionary<string, QueryDbFieldAttribute> EndsWithConventions { get; set; }
  530. }
  531. public partial class AutoQuery : IAutoQueryDb, IAutoQueryOptions
  532. {
  533. public int? MaxLimit { get; set; }
  534. public bool IncludeTotal { get; set; }
  535. public bool EnableUntypedQueries { get; set; }
  536. public bool EnableSqlFilters { get; set; }
  537. public bool OrderByPrimaryKeyOnLimitQuery { get; set; }
  538. public string RequiredRoleForRawSqlFilters { get; set; }
  539. public HashSet<string> IgnoreProperties { get; set; }
  540. public HashSet<string> IllegalSqlFragmentTokens { get; set; }
  541. public Dictionary<string, QueryDbFieldAttribute> StartsWithConventions { get; set; }
  542. public Dictionary<string, QueryDbFieldAttribute> EndsWithConventions { get; set; }
  543. public string UseNamedConnection { get; set; }
  544. public QueryFilterDelegate GlobalQueryFilter { get; set; }
  545. public Dictionary<Type, QueryFilterDelegate> QueryFilters { get; set; }
  546. public List<Action<QueryDbFilterContext>> ResponseFilters { get; set; }
  547. private static Dictionary<Type, ITypedQuery> TypedQueries = new Dictionary<Type, ITypedQuery>();
  548. public Type GetFromType(Type requestDtoType)
  549. {
  550. var intoTypeDef = requestDtoType.GetTypeWithGenericTypeDefinitionOf(typeof(IQueryDb<,>));
  551. if (intoTypeDef != null)
  552. {
  553. var args = intoTypeDef.GetGenericArguments();
  554. return args[1];
  555. }
  556. var typeDef = requestDtoType.GetTypeWithGenericTypeDefinitionOf(typeof(IQueryDb<>));
  557. if (typeDef != null)
  558. {
  559. var args = typeDef.GetGenericArguments();
  560. return args[0];
  561. }
  562. throw new NotSupportedException("Request DTO is not an AutoQuery DTO: " + requestDtoType.Name);
  563. }
  564. public ITypedQuery GetTypedQuery(Type dtoType, Type fromType)
  565. {
  566. if (TypedQueries.TryGetValue(dtoType, out var defaultValue))
  567. return defaultValue;
  568. var genericType = typeof(TypedQuery<,>).MakeGenericType(dtoType, fromType);
  569. defaultValue = genericType.CreateInstance<ITypedQuery>();
  570. Dictionary<Type, ITypedQuery> snapshot, newCache;
  571. do
  572. {
  573. snapshot = TypedQueries;
  574. newCache = new Dictionary<Type, ITypedQuery>(TypedQueries) {
  575. [dtoType] = defaultValue
  576. };
  577. } while (!ReferenceEquals(
  578. Interlocked.CompareExchange(ref TypedQueries, newCache, snapshot), snapshot));
  579. return defaultValue;
  580. }
  581. public SqlExpression<From> Filter<From>(ISqlExpression q, IQueryDb dto, IRequest req)
  582. {
  583. GlobalQueryFilter?.Invoke(q, dto, req);
  584. if (QueryFilters == null)
  585. return (SqlExpression<From>)q;
  586. if (!QueryFilters.TryGetValue(dto.GetType(), out var filterFn))
  587. {
  588. foreach (var type in dto.GetType().GetInterfaces())
  589. {
  590. if (QueryFilters.TryGetValue(type, out filterFn))
  591. break;
  592. }
  593. }
  594. filterFn?.Invoke(q, dto, req);
  595. return (SqlExpression<From>)q;
  596. }
  597. public ISqlExpression Filter(ISqlExpression q, IQueryDb dto, IRequest req)
  598. {
  599. GlobalQueryFilter?.Invoke(q, dto, req);
  600. if (QueryFilters == null)
  601. return q;
  602. if (!QueryFilters.TryGetValue(dto.GetType(), out var filterFn))
  603. {
  604. foreach (var type in dto.GetType().GetInterfaces())
  605. {
  606. if (QueryFilters.TryGetValue(type, out filterFn))
  607. break;
  608. }
  609. }
  610. filterFn?.Invoke(q, dto, req);
  611. return q;
  612. }
  613. public QueryResponse<Into> ResponseFilter<From, Into>(IDbConnection db, QueryResponse<Into> response, SqlExpression<From> expr, IQueryDb dto)
  614. {
  615. response.Meta = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  616. var commands = dto.Include.ParseCommands();
  617. var ctx = new QueryDbFilterContext
  618. {
  619. Db = db,
  620. Commands = commands,
  621. Dto = dto,
  622. SqlExpression = expr,
  623. Response = response,
  624. };
  625. var totalCommand = commands.FirstOrDefault(x => x.Name.EqualsIgnoreCase("Total"));
  626. if (totalCommand != null)
  627. {
  628. totalCommand.Name = "COUNT";
  629. }
  630. var totalRequested = commands.Any(x =>
  631. x.Name.EqualsIgnoreCase("COUNT") &&
  632. (x.Args.Count == 0 || x.Args.Count == 1 && x.Args[0].EqualsOrdinal("*")));
  633. if (IncludeTotal || totalRequested)
  634. {
  635. if (!totalRequested)
  636. commands.Add(new Command { Name = "COUNT", Args = { "*".AsMemory() } });
  637. foreach (var responseFilter in ResponseFilters)
  638. {
  639. responseFilter(ctx);
  640. }
  641. response.Total = response.Meta.TryGetValue("COUNT(*)", out var total)
  642. ? total.ToInt()
  643. : (int)db.Count(expr); //fallback if it's not populated (i.e. if stripped by custom ResponseFilter)
  644. //reduce payload on wire
  645. if (totalCommand != null || !totalRequested)
  646. {
  647. response.Meta.Remove("COUNT(*)");
  648. if (response.Meta.Count == 0)
  649. response.Meta = null;
  650. }
  651. }
  652. else
  653. {
  654. foreach (var responseFilter in ResponseFilters)
  655. {
  656. responseFilter(ctx);
  657. }
  658. }
  659. return response;
  660. }
  661. public IDbConnection GetDb<From>(IRequest req = null) => GetDb(typeof(From), req);
  662. public IDbConnection GetDb(Type fromType, IRequest req = null)
  663. {
  664. var namedConnection = UseNamedConnection;
  665. var attr = fromType.FirstAttribute<NamedConnectionAttribute>();
  666. if (attr != null)
  667. namedConnection = attr.Name;
  668. return namedConnection == null
  669. ? HostContext.AppHost.GetDbConnection(req)
  670. : HostContext.TryResolve<IDbConnectionFactory>().OpenDbConnection(namedConnection);
  671. }
  672. public SqlExpression<From> CreateQuery<From>(IQueryDb<From> dto, Dictionary<string, string> dynamicParams, IRequest req = null, IDbConnection db = null)
  673. {
  674. using (db == null ? db = GetDb<From>(req) : null)
  675. {
  676. var typedQuery = GetTypedQuery(dto.GetType(), typeof(From));
  677. var q = typedQuery.CreateQuery(db);
  678. return Filter<From>(typedQuery.AddToQuery(q, dto, dynamicParams, this, req), dto, req);
  679. }
  680. }
  681. public QueryResponse<From> Execute<From>(IQueryDb<From> model, SqlExpression<From> query, IRequest req = null, IDbConnection db = null)
  682. {
  683. using (db == null ? db = GetDb<From>(req) : null)
  684. {
  685. var typedQuery = GetTypedQuery(model.GetType(), typeof(From));
  686. return ResponseFilter(db, typedQuery.Execute<From>(db, query), query, model);
  687. }
  688. }
  689. public async Task<QueryResponse<From>> ExecuteAsync<From>(IQueryDb<From> model, SqlExpression<From> query, IRequest req = null, IDbConnection db = null)
  690. {
  691. using (db == null ? db = GetDb<From>(req) : null)
  692. {
  693. var typedQuery = GetTypedQuery(model.GetType(), typeof(From));
  694. return ResponseFilter(db, await typedQuery.ExecuteAsync<From>(db, query), query, model);
  695. }
  696. }
  697. public SqlExpression<From> CreateQuery<From, Into>(IQueryDb<From, Into> dto, Dictionary<string, string> dynamicParams, IRequest req = null, IDbConnection db = null)
  698. {
  699. using (db == null ? db = GetDb<From>(req) : null)
  700. {
  701. var typedQuery = GetTypedQuery(dto.GetType(), typeof(From));
  702. var q = typedQuery.CreateQuery(db);
  703. return Filter<From>(typedQuery.AddToQuery(q, dto, dynamicParams, this, req), dto, req);
  704. }
  705. }
  706. public QueryResponse<Into> Execute<From, Into>(IQueryDb<From, Into> model, SqlExpression<From> query, IRequest req = null, IDbConnection db = null)
  707. {
  708. using (db == null ? db = GetDb<From>(req) : null)
  709. {
  710. var typedQuery = GetTypedQuery(model.GetType(), typeof(From));
  711. return ResponseFilter(db, typedQuery.Execute<Into>(db, query), query, model);
  712. }
  713. }
  714. public async Task<QueryResponse<Into>> ExecuteAsync<From, Into>(IQueryDb<From, Into> model, SqlExpression<From> query, IRequest req = null, IDbConnection db = null)
  715. {
  716. using (db == null ? db = GetDb<From>(req) : null)
  717. {
  718. var typedQuery = GetTypedQuery(model.GetType(), typeof(From));
  719. return ResponseFilter(db, await typedQuery.ExecuteAsync<Into>(db, query), query, model);
  720. }
  721. }
  722. public ISqlExpression CreateQuery(IQueryDb requestDto, Dictionary<string, string> dynamicParams, IRequest req = null, IDbConnection db = null)
  723. {
  724. var requestDtoType = requestDto.GetType();
  725. var fromType = GetFromType(requestDtoType);
  726. using (db == null ? db = GetDb(fromType) : null)
  727. {
  728. var typedQuery = GetTypedQuery(requestDtoType, fromType);
  729. var q = typedQuery.CreateQuery(db);
  730. return Filter(typedQuery.AddToQuery(q, requestDto, dynamicParams, this, req), requestDto, req);
  731. }
  732. }
  733. private Dictionary<Type, GenericAutoQueryDb> genericAutoQueryCache = new Dictionary<Type, GenericAutoQueryDb>();
  734. public IQueryResponse Execute(IQueryDb request, ISqlExpression q, IDbConnection db)
  735. {
  736. if (db == null)
  737. throw new ArgumentNullException(nameof(db));
  738. var requestDtoType = request.GetType();
  739. ResolveTypes(requestDtoType, out var fromType, out var intoType);
  740. if (genericAutoQueryCache.TryGetValue(fromType, out GenericAutoQueryDb typedApi))
  741. return typedApi.ExecuteObject(this, request, q, db);
  742. var instance = GetGenericAutoQueryDb(fromType, intoType, requestDtoType);
  743. return instance.ExecuteObject(this, request, q, db);
  744. }
  745. public Task<IQueryResponse> ExecuteAsync(IQueryDb request, ISqlExpression q, IDbConnection db)
  746. {
  747. if (db == null)
  748. throw new ArgumentNullException(nameof(db));
  749. var requestDtoType = request.GetType();
  750. ResolveTypes(requestDtoType, out var fromType, out var intoType);
  751. if (genericAutoQueryCache.TryGetValue(fromType, out GenericAutoQueryDb typedApi))
  752. return typedApi.ExecuteObjectAsync(this, request, q, db);
  753. var instance = GetGenericAutoQueryDb(fromType, intoType, requestDtoType);
  754. return instance.ExecuteObjectAsync(this, request, q, db);
  755. }
  756. private GenericAutoQueryDb GetGenericAutoQueryDb(Type fromType, Type intoType, Type requestDtoType)
  757. {
  758. var genericType = typeof(GenericAutoQueryDb<,>).MakeGenericType(fromType, intoType);
  759. var instance = genericType.CreateInstance<GenericAutoQueryDb>();
  760. Dictionary<Type, GenericAutoQueryDb> snapshot, newCache;
  761. do
  762. {
  763. snapshot = genericAutoQueryCache;
  764. newCache = new Dictionary<Type, GenericAutoQueryDb>(genericAutoQueryCache) {
  765. [requestDtoType] = instance
  766. };
  767. } while (!ReferenceEquals(
  768. Interlocked.CompareExchange(ref genericAutoQueryCache, newCache, snapshot), snapshot));
  769. return instance;
  770. }
  771. private static void ResolveTypes(Type requestDtoType, out Type fromType, out Type intoType)
  772. {
  773. var intoTypeDef = requestDtoType.GetTypeWithGenericTypeDefinitionOf(typeof(IQueryDb<,>));
  774. if (intoTypeDef != null)
  775. {
  776. var args = intoTypeDef.GetGenericArguments();
  777. fromType = args[0];
  778. intoType = args[1];
  779. }
  780. else
  781. {
  782. var typeDef = requestDtoType.GetTypeWithGenericTypeDefinitionOf(typeof(IQueryDb<>));
  783. var args = typeDef.GetGenericArguments();
  784. fromType = args[0];
  785. intoType = args[0];
  786. }
  787. }
  788. }
  789. internal abstract class GenericAutoQueryDb
  790. {
  791. public abstract IQueryResponse ExecuteObject(AutoQuery autoQuery, IQueryDb request, ISqlExpression query, IDbConnection db = null);
  792. public abstract Task<IQueryResponse> ExecuteObjectAsync(AutoQuery autoQuery, IQueryDb request, ISqlExpression query, IDbConnection db = null);
  793. }
  794. internal class GenericAutoQueryDb<From, Into> : GenericAutoQueryDb
  795. {
  796. public override IQueryResponse ExecuteObject(AutoQuery autoQuery, IQueryDb request, ISqlExpression query, IDbConnection db = null)
  797. {
  798. using (db == null ? autoQuery.GetDb(request.GetType(), null) : null)
  799. {
  800. var typedQuery = autoQuery.GetTypedQuery(request.GetType(), typeof(From));
  801. var q = (SqlExpression<From>)query;
  802. return autoQuery.ResponseFilter(db, typedQuery.Execute<Into>(db, q), q, request);
  803. }
  804. }
  805. public override async Task<IQueryResponse> ExecuteObjectAsync(AutoQuery autoQuery, IQueryDb request, ISqlExpression query, IDbConnection db = null)
  806. {
  807. using (db == null ? autoQuery.GetDb(request.GetType(), null) : null)
  808. {
  809. var typedQuery = autoQuery.GetTypedQuery(request.GetType(), typeof(From));
  810. var q = (SqlExpression<From>)query;
  811. return autoQuery.ResponseFilter(db, await typedQuery.ExecuteAsync<Into>(db, q), q, request);
  812. }
  813. }
  814. }
  815. public interface ITypedQuery
  816. {
  817. ISqlExpression CreateQuery(IDbConnection db);
  818. ISqlExpression AddToQuery(
  819. ISqlExpression query,
  820. IQueryDb dto,
  821. Dictionary<string, string> dynamicParams,
  822. IAutoQueryOptions options = null,
  823. IRequest req = null);
  824. QueryResponse<Into> Execute<Into>(
  825. IDbConnection db,
  826. ISqlExpression query);
  827. Task<QueryResponse<Into>> ExecuteAsync<Into>(
  828. IDbConnection db,
  829. ISqlExpression query);
  830. }
  831. internal struct ExprResult
  832. {
  833. internal string DefaultTerm;
  834. internal string Format;
  835. internal object[] Values;
  836. private ExprResult(string defaultTerm, string format, params object[] values)
  837. {
  838. DefaultTerm = defaultTerm;
  839. Format = format;
  840. Values = values;
  841. }
  842. internal static ExprResult? CreateExpression(string defaultTerm, string quotedColumn, object value, QueryDbFieldAttribute implicitQuery)
  843. {
  844. var seq = value as IEnumerable;
  845. if (value is string)
  846. seq = null;
  847. if (seq != null && value is ICollection collection && collection.Count == 0)
  848. return null;
  849. var format = seq == null
  850. ? (value != null ? quotedColumn + " = {0}" : quotedColumn + " IS NULL")
  851. : quotedColumn + " IN ({0})";
  852. if (implicitQuery != null)
  853. {
  854. var operand = implicitQuery.Operand ?? "=";
  855. if (implicitQuery.Term == QueryTerm.Or)
  856. defaultTerm = "OR";
  857. else if (implicitQuery.Term == QueryTerm.And)
  858. defaultTerm = "AND";
  859. format = "(" + quotedColumn + " " + operand + " {0}" + ")";
  860. if (implicitQuery.Template != null)
  861. {
  862. format = implicitQuery.Template.Replace("{Field}", quotedColumn);
  863. if (implicitQuery.ValueStyle == ValueStyle.Multiple)
  864. {
  865. if (value == null)
  866. return null;
  867. if (seq == null)
  868. throw new ArgumentException($"{implicitQuery.Field} requires {implicitQuery.ValueArity} values");
  869. var args = new object[implicitQuery.ValueArity];
  870. int i = 0;
  871. foreach (var x in seq)
  872. {
  873. if (i < args.Length)
  874. {
  875. format = format.Replace("{Value" + (i + 1) + "}", "{" + i + "}");
  876. var arg = x;
  877. if (implicitQuery.ValueFormat != null)
  878. arg = string.Format(implicitQuery.ValueFormat, arg);
  879. args[i++] = arg;
  880. }
  881. }
  882. return new ExprResult(defaultTerm, format, args);
  883. }
  884. if (implicitQuery.ValueStyle == ValueStyle.List)
  885. {
  886. if (value == null)
  887. return null;
  888. if (seq == null)
  889. throw new ArgumentException("{0} expects a list of values".Fmt(implicitQuery.Field));
  890. format = format.Replace("{Values}", "{0}");
  891. value = new SqlInValues(seq);
  892. }
  893. else
  894. {
  895. format = format.Replace("{Value}", "{0}");
  896. }
  897. if (implicitQuery.ValueFormat != null)
  898. {
  899. value = string.Format(implicitQuery.ValueFormat, value);
  900. }
  901. }
  902. }
  903. else
  904. {
  905. if (seq != null)
  906. value = new SqlInValues(seq);
  907. }
  908. return new ExprResult(defaultTerm, format, value);
  909. }
  910. internal static QueryDbFieldAttribute ToDbFieldAttribute(AutoFilterAttribute filter)
  911. {
  912. var dbField = new QueryDbFieldAttribute {
  913. Term = filter.Term,
  914. Field = filter.Field,
  915. Operand = filter.Operand,
  916. Template = filter.Template,
  917. ValueFormat = filter.ValueFormat,
  918. ValueStyle = ValueStyle.Single,
  919. };
  920. if (filter.Template?.IndexOf("{Values}", StringComparison.Ordinal) >= 0)
  921. {
  922. dbField.ValueStyle = ValueStyle.List;
  923. }
  924. else if (filter.Template?.IndexOf("{Value1}", StringComparison.Ordinal) >= 0)
  925. {
  926. dbField.ValueStyle = ValueStyle.Multiple;
  927. var arity = 1;
  928. while (filter.Template.IndexOf("{Value" + arity + "}", StringComparison.Ordinal) >= 0)
  929. {
  930. arity++;
  931. }
  932. dbField.ValueArity = arity;
  933. }
  934. return dbField;
  935. }
  936. }
  937. public class TypedQuery<QueryModel, From> : ITypedQuery
  938. {
  939. static readonly Dictionary<string, GetMemberDelegate> PropertyGetters =
  940. new Dictionary<string, GetMemberDelegate>();
  941. static readonly Dictionary<string, QueryDbFieldAttribute> QueryFieldMap =
  942. new Dictionary<string, QueryDbFieldAttribute>();
  943. static readonly AutoFilterAttribute[] AutoFilters;
  944. static readonly QueryDbFieldAttribute[] AutoFiltersDbFields;
  945. static TypedQuery()
  946. {
  947. foreach (var pi in typeof(QueryModel).GetPublicProperties())
  948. {
  949. var fn = pi.CreateGetter();
  950. PropertyGetters[pi.Name] = fn;
  951. var queryAttr = pi.FirstAttribute<QueryDbFieldAttribute>();
  952. if (queryAttr != null)
  953. QueryFieldMap[pi.Name] = queryAttr.Init();
  954. }
  955. var allAttrs = typeof(QueryModel).AllAttributes();
  956. AutoFilters = allAttrs.OfType<AutoFilterAttribute>().ToArray();
  957. AutoFiltersDbFields = new QueryDbFieldAttribute[AutoFilters.Length];
  958. for (int i = 0; i < AutoFilters.Length; i++)
  959. {
  960. var filter = AutoFilters[i];
  961. AutoFiltersDbFields[i] = ExprResult.ToDbFieldAttribute(filter);
  962. }
  963. }
  964. public ISqlExpression CreateQuery(IDbConnection db) => db.From<From>();
  965. public ISqlExpression AddToQuery(
  966. ISql

Large files files are truncated, but you can click here to view the full file