/
DbAggregateQueryBuilder.cs
348 lines (316 loc) · 14.3 KB
/
DbAggregateQueryBuilder.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
using DevZest.Data.Primitives;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace DevZest.Data
{
/// <summary>
/// Builds database aggregate query which can be translated to native SQL.
/// </summary>
public class DbAggregateQueryBuilder : DbQueryBuilder
{
private class GroupByCollection : ReadOnlyCollection<DbExpression>
{
public GroupByCollection()
: base(new List<DbExpression>())
{
}
public void Add(DbExpression item)
{
Debug.Assert(item != null);
Items.Add(item);
}
}
internal DbAggregateQueryBuilder(Model model)
: base(model)
{
AutoGroupBy = true;
}
internal override void Initialize(DbSelectStatement query)
{
Debug.Assert(query.IsAggregate);
base.Initialize(query);
foreach (var groupBy in query.GroupBy)
_groupByList.Add(groupBy);
HavingExpression = query.Having;
}
/// <summary>
/// Constructs SQL FROM clause.
/// </summary>
/// <typeparam name="T">Model type of the DbSet.</typeparam>
/// <param name="dbSet">The first DbSet in FROM clause.</param>
/// <param name="_">The model object for further SQL construction.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public new DbAggregateQueryBuilder From<T>(DbSet<T> dbSet, out T _)
where T : Model, new()
{
base.From(dbSet, out _);
return this;
}
/// <summary>
/// Constructs SQL INNER JOIN.
/// </summary>
/// <typeparam name="T">Model type of the target DbSet.</typeparam>
/// <typeparam name="TKey">Type of the candidate key to join.</typeparam>
/// <param name="dbSet">The target DbSet.</param>
/// <param name="left">Left side key of the join.</param>
/// <param name="_">The model object of the target DbSet for further SQL construction.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public new DbAggregateQueryBuilder InnerJoin<T, TKey>(DbSet<T> dbSet, TKey left, out T _)
where T : Model<TKey>, new()
where TKey : CandidateKey
{
base.InnerJoin(dbSet, left, out _);
return this;
}
/// <summary>
/// Constructs SQL INNER JOIN.
/// </summary>
/// <typeparam name="T">Model type of the target DbSet.</typeparam>
/// <typeparam name="TKey">Type of the candidate key to join.</typeparam>
/// <param name="dbSet">The target DbSet.</param>
/// <param name="left">Left side key of the join.</param>
/// <param name="right">The delegate to get right side key of the join.</param>
/// <param name="_">The model object of the target DbSet for further SQL construction.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public new DbAggregateQueryBuilder InnerJoin<T, TKey>(DbSet<T> dbSet, TKey left, Func<T, TKey> right, out T _)
where T : Model, new()
where TKey : CandidateKey
{
base.InnerJoin(dbSet, left, right, out _);
return this;
}
/// <summary>
/// Constructs SQL LEFT JOIN.
/// </summary>
/// <typeparam name="T">Model type of the target DbSet.</typeparam>
/// <typeparam name="TKey">Type of the candidate key to join.</typeparam>
/// <param name="dbSet">The target DbSet.</param>
/// <param name="left">Left side key of the join.</param>
/// <param name="_">The model object of the target DbSet for further SQL construction.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public new DbAggregateQueryBuilder LeftJoin<T, TKey>(DbSet<T> dbSet, TKey left, out T _)
where T : Model<TKey>, new()
where TKey : CandidateKey
{
base.LeftJoin(dbSet, left, out _);
return this;
}
/// <summary>
/// Constructs SQL LEFT JOIN.
/// </summary>
/// <typeparam name="T">Model type of the target DbSet.</typeparam>
/// <typeparam name="TKey">Type of the candidate key to join.</typeparam>
/// <param name="dbSet">The target DbSet.</param>
/// <param name="left">Left side key of the join.</param>
/// <param name="right">The delegate to get right side key of the join.</param>
/// <param name="_">The model object of the target DbSet for further SQL construction.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public new DbAggregateQueryBuilder LeftJoin<T, TKey>(DbSet<T> dbSet, TKey left, Func<T, TKey> right, out T _)
where T : Model, new()
where TKey : CandidateKey
{
base.LeftJoin(dbSet, left, right, out _);
return this;
}
/// <summary>
/// Constructs SQL RIGHT JOIN.
/// </summary>
/// <typeparam name="T">Model type of the target DbSet.</typeparam>
/// <typeparam name="TKey">Type of the candidate key to join.</typeparam>
/// <param name="dbSet">The target DbSet.</param>
/// <param name="left">Left side key of the join.</param>
/// <param name="_">The model object of the target DbSet for further SQL construction.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public new DbAggregateQueryBuilder RightJoin<T, TKey>(DbSet<T> dbSet, TKey left, out T _)
where T : Model<TKey>, new()
where TKey : CandidateKey
{
base.RightJoin(dbSet, left, out _);
return this;
}
/// <summary>
/// Constructs SQL RIGHT JOIN.
/// </summary>
/// <typeparam name="T">Model type of the target DbSet.</typeparam>
/// <typeparam name="TKey">Type of the candidate key to join.</typeparam>
/// <param name="dbSet">The target DbSet.</param>
/// <param name="left">Left side key of the join.</param>
/// <param name="right">The delegate to get right side key of the join.</param>
/// <param name="_">The model object of the target DbSet for further SQL construction.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public new DbAggregateQueryBuilder RightJoin<T, TKey>(DbSet<T> dbSet, TKey left, Func<T, TKey> right, out T _)
where T : Model, new()
where TKey : CandidateKey
{
base.RightJoin(dbSet, left, right, out _);
return this;
}
/// <summary>
/// Constructs SQL RIGHT JOIN.
/// </summary>
/// <typeparam name="T">Model type of the target DbSet.</typeparam>
/// <param name="dbSet">The target DbSet.</param>
/// <param name="_">The model object of the target DbSet for further SQL construction.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public new DbAggregateQueryBuilder CrossJoin<T>(DbSet<T> dbSet, out T _)
where T : Model, new()
{
base.CrossJoin(dbSet, out _);
return this;
}
/// <summary>
/// Constructs SQL SELECT by automatically matching columns.
/// </summary>
/// <returns>This aggregate query builder for fluent coding.</returns>
public new DbAggregateQueryBuilder AutoSelect()
{
base.AutoSelect();
return this;
}
/// <summary>
/// Constructs SQL SELECT by automatically matching columns between specified source model and target projection.
/// </summary>
/// <param name="from">The source model.</param>
/// <param name="to">The target projection.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public new DbAggregateQueryBuilder AutoSelect(Model from, Projection to)
{
base.AutoSelect(from, to);
return this;
}
/// <summary>
/// Constructs SQL SELECT by matching between specified source column and target column.
/// </summary>
/// <typeparam name="T">Type of the columns.</typeparam>
/// <param name="from">The source column.</param>
/// <param name="to">The target column.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
[SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods", Justification = "For fluent API design")]
public new DbAggregateQueryBuilder Select<T>(T from, T to)
where T : Column, new()
{
base.Select(from, to);
return this;
}
/// <summary>
/// Constructs SQL SELECT by matching between specified source column and target new adhoc column.
/// </summary>
/// <typeparam name="T">Type of lthe column.</typeparam>
/// <param name="from">The source column.</param>
/// <param name="to">The target new adhoc model.</param>
/// <param name="name">Name to create the adhoc column.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
[SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods", Justification = "For fluent API design")]
public new DbAggregateQueryBuilder Select<T>(T from, Adhoc to, string name = null)
where T : Column, new()
{
base.Select(from, to, name);
return this;
}
/// <summary>
/// Constructs SQL WHERE.
/// </summary>
/// <param name="condition">The condition.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public new DbAggregateQueryBuilder Where(_Boolean condition)
{
base.Where(condition);
return this;
}
/// <summary>
/// Gets or sets a value that indicates whether should group by selected columns automatically.
/// </summary>
public bool AutoGroupBy { get; set; }
/// <summary>
/// Sets a value that indicates whether should group by selected columns automatically.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public DbAggregateQueryBuilder WithAutoGroupBy(bool value)
{
AutoGroupBy = value;
return this;
}
private GroupByCollection _groupByList = new GroupByCollection();
/// <summary>
/// Gets the SQL GROUP BY list.
/// </summary>
public ReadOnlyCollection<DbExpression> GroupByList
{
get { return _groupByList; }
}
/// <summary>
/// Constructs SQL GROUP BY.
/// </summary>
/// <param name="column">The column to group by.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public DbAggregateQueryBuilder GroupBy(Column column)
{
VerifySourceColumn(column, nameof(column), false);
_groupByList.Add(EliminateSubQuery(column.DbExpression));
return this;
}
/// <summary>
/// Constructs SQL HAVING.
/// </summary>
/// <param name="condition">The condition.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public DbAggregateQueryBuilder Having(_Boolean condition)
{
VerifySourceColumn(condition, nameof(condition));
HavingExpression = EliminateSubQuery(condition.DbExpression);
return this;
}
/// <summary>
/// Gets the SQL HAVING expression.
/// </summary>
public DbExpression HavingExpression { get; private set; }
/// <summary>
/// Constructs SQL ORDER BY.
/// </summary>
/// <param name="orderByList">The order by list.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public new DbAggregateQueryBuilder OrderBy(params ColumnSort[] orderByList)
{
base.OrderBy(orderByList);
return this;
}
/// <summary>
/// Constructs SQL ORDER BY with specified offset and fetch.
/// </summary>
/// <param name="offset">Specifies how many rows to skip within the query result.</param>
/// <param name="fetch">Specifies how many rows to skip within the query result.</param>
/// <param name="orderByList">The order by list.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public new DbAggregateQueryBuilder OrderBy(int offset, int fetch, params ColumnSort[] orderByList)
{
base.OrderBy(offset, fetch, orderByList);
return this;
}
internal override void OnSelect(Column source, DbExpression sourceExpression, Column target)
{
if (source == null)
return;
if (target.IsSystem || (AutoGroupBy && source.AggregateSourceModels.Count == 0))
_groupByList.Add(sourceExpression);
}
internal override DbSelectStatement BuildSelectStatement(IReadOnlyList<ColumnMapping> selectList, DbFromClause from, DbExpression where, IReadOnlyList<DbExpressionSort> orderBy)
{
return new DbSelectStatement(Model, selectList, from, where, GroupByList, HavingExpression, orderBy, Offset, Fetch);
}
/// <summary>
/// Constructs SQL SELECT by matching between specified source column and target column, without type safety.
/// </summary>
/// <param name="from">The source column.</param>
/// <param name="to">The target column.</param>
/// <returns>This aggregate query builder for fluent coding.</returns>
public new DbAggregateQueryBuilder UnsafeSelect(Column from, Column to)
{
base.UnsafeSelect(from, to);
return this;
}
}
}