|
1 | 1 | using System.Linq.Expressions;
|
2 | 2 | using System.Reflection;
|
| 3 | +using GreenDonut.Data; |
3 | 4 | using static GreenDonut.ExpressionHelpers;
|
4 | 5 |
|
5 |
| -namespace GreenDonut.Data; |
| 6 | +// ReSharper disable once CheckNamespace |
| 7 | +namespace System.Linq; |
6 | 8 |
|
7 | 9 | /// <summary>
|
8 |
| -/// Data loader extensions for projections. |
| 10 | +/// Provides extension methods to integrate <see cref="IQueryable{T}"/> |
| 11 | +/// for <see cref="SortDefinition{T}"/> and <see cref="QueryContext{T}"/>. |
9 | 12 | /// </summary>
|
10 |
| -public static class SelectionDataLoaderExtensions |
| 13 | +public static class GreenDonutQueryableExtensions |
11 | 14 | {
|
12 | 15 | private static readonly MethodInfo _selectMethod =
|
13 | 16 | typeof(Enumerable)
|
14 | 17 | .GetMethods()
|
15 | 18 | .Where(m => m.Name == nameof(Enumerable.Select) && m.GetParameters().Length == 2)
|
16 | 19 | .First(m => m.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>));
|
17 | 20 |
|
18 |
| - /// <summary> |
19 |
| - /// Branches a DataLoader and applies a selector to load the data. |
20 |
| - /// </summary> |
21 |
| - /// <param name="dataLoader"> |
22 |
| - /// The DataLoader to branch. |
23 |
| - /// </param> |
24 |
| - /// <param name="selector"> |
25 |
| - /// The data selector. |
26 |
| - /// </param> |
27 |
| - /// <typeparam name="TKey"> |
28 |
| - /// The key type. |
29 |
| - /// </typeparam> |
30 |
| - /// <typeparam name="TValue"> |
31 |
| - /// The value type. |
32 |
| - /// </typeparam> |
33 |
| - /// <returns> |
34 |
| - /// Returns a branched DataLoader with the selector applied. |
35 |
| - /// </returns> |
36 |
| - /// <exception cref="ArgumentNullException"> |
37 |
| - /// Throws if <paramref name="dataLoader"/> is <c>null</c>. |
38 |
| - /// </exception> |
39 |
| - public static IDataLoader<TKey, TValue> Select<TKey, TValue>( |
40 |
| - this IDataLoader<TKey, TValue> dataLoader, |
41 |
| - Expression<Func<TValue, TValue>>? selector) |
42 |
| - where TKey : notnull |
43 |
| - { |
44 |
| - if (dataLoader is null) |
45 |
| - { |
46 |
| - throw new ArgumentNullException(nameof(dataLoader)); |
47 |
| - } |
48 |
| - |
49 |
| - if (selector is null) |
50 |
| - { |
51 |
| - return dataLoader; |
52 |
| - } |
53 |
| - |
54 |
| - if (dataLoader.ContextData.TryGetValue(DataStateKeys.Selector, out var value)) |
55 |
| - { |
56 |
| - var context = (DefaultSelectorBuilder)value!; |
57 |
| - context.Add(selector); |
58 |
| - return dataLoader; |
59 |
| - } |
60 |
| - |
61 |
| - var branchKey = selector.ComputeHash(); |
62 |
| - var state = new QueryState(DataStateKeys.Selector, new DefaultSelectorBuilder(selector)); |
63 |
| - return (IQueryDataLoader<TKey, TValue>)dataLoader.Branch(branchKey, DataStateHelper.CreateBranch, state); |
64 |
| - } |
65 |
| - |
66 |
| - public static IDataLoader<TKey, TValue[]> Select<TKey, TValue>( |
67 |
| - this IDataLoader<TKey, TValue[]> dataLoader, |
68 |
| - Expression<Func<TValue, TValue>>? selector) |
69 |
| - where TKey : notnull |
70 |
| - { |
71 |
| - if (dataLoader is null) |
72 |
| - { |
73 |
| - throw new ArgumentNullException(nameof(dataLoader)); |
74 |
| - } |
75 |
| - |
76 |
| - if (selector is null) |
77 |
| - { |
78 |
| - return dataLoader; |
79 |
| - } |
80 |
| - |
81 |
| - if (dataLoader.ContextData.TryGetValue(DataStateKeys.Selector, out var value)) |
82 |
| - { |
83 |
| - var context = (DefaultSelectorBuilder)value!; |
84 |
| - context.Add(selector); |
85 |
| - return dataLoader; |
86 |
| - } |
87 |
| - |
88 |
| - var branchKey = selector.ComputeHash(); |
89 |
| - var state = new QueryState(DataStateKeys.Selector, new DefaultSelectorBuilder(selector)); |
90 |
| - return (IQueryDataLoader<TKey, TValue[]>)dataLoader.Branch(branchKey, DataStateHelper.CreateBranch, state); |
91 |
| - } |
92 |
| - |
93 |
| - public static IDataLoader<TKey, List<TValue>> Select<TKey, TValue>( |
94 |
| - this IDataLoader<TKey, List<TValue>> dataLoader, |
95 |
| - Expression<Func<TValue, TValue>>? selector) |
96 |
| - where TKey : notnull |
97 |
| - { |
98 |
| - if (dataLoader is null) |
99 |
| - { |
100 |
| - throw new ArgumentNullException(nameof(dataLoader)); |
101 |
| - } |
102 |
| - |
103 |
| - if (selector is null) |
104 |
| - { |
105 |
| - return dataLoader; |
106 |
| - } |
107 |
| - |
108 |
| - if (dataLoader.ContextData.TryGetValue(DataStateKeys.Selector, out var value)) |
109 |
| - { |
110 |
| - var context = (DefaultSelectorBuilder)value!; |
111 |
| - context.Add(selector); |
112 |
| - return dataLoader; |
113 |
| - } |
114 |
| - |
115 |
| - var branchKey = selector.ComputeHash(); |
116 |
| - var state = new QueryState(DataStateKeys.Selector, new DefaultSelectorBuilder(selector)); |
117 |
| - return (IQueryDataLoader<TKey, List<TValue>>)dataLoader.Branch(branchKey, DataStateHelper.CreateBranch, state); |
118 |
| - } |
119 |
| - |
120 |
| - /// <summary> |
121 |
| - /// Includes a property in the query. |
122 |
| - /// </summary> |
123 |
| - /// <param name="dataLoader"> |
124 |
| - /// The DataLoader to include the property in. |
125 |
| - /// </param> |
126 |
| - /// <param name="includeSelector"> |
127 |
| - /// The property selector. |
128 |
| - /// </param> |
129 |
| - /// <typeparam name="TKey"> |
130 |
| - /// The key type. |
131 |
| - /// </typeparam> |
132 |
| - /// <typeparam name="TValue"> |
133 |
| - /// The value type. |
134 |
| - /// </typeparam> |
135 |
| - /// <returns> |
136 |
| - /// Returns the DataLoader with the property included. |
137 |
| - /// </returns> |
138 |
| - /// <exception cref="ArgumentNullException"> |
139 |
| - /// Throws if <paramref name="dataLoader"/> is <c>null</c>. |
140 |
| - /// </exception> |
141 |
| - /// <exception cref="ArgumentException"> |
142 |
| - /// Throws if the include selector is not a property selector. |
143 |
| - /// </exception> |
144 |
| - public static IDataLoader<TKey, TValue> Include<TKey, TValue>( |
145 |
| - this IDataLoader<TKey, TValue> dataLoader, |
146 |
| - Expression<Func<TValue, object?>> includeSelector) |
147 |
| - where TKey : notnull |
148 |
| - { |
149 |
| - if (dataLoader is null) |
150 |
| - { |
151 |
| - throw new ArgumentNullException(nameof(dataLoader)); |
152 |
| - } |
153 |
| - |
154 |
| - if(!dataLoader.ContextData.ContainsKey(DataStateKeys.Selector)) |
155 |
| - { |
156 |
| - throw new InvalidOperationException( |
157 |
| - "The Include method must be called after the Select method."); |
158 |
| - } |
159 |
| - |
160 |
| - if (includeSelector is null) |
161 |
| - { |
162 |
| - throw new ArgumentNullException(nameof(includeSelector)); |
163 |
| - } |
164 |
| - |
165 |
| - if (includeSelector is not LambdaExpression lambda) |
166 |
| - { |
167 |
| - throw new ArgumentException( |
168 |
| - "The include selector must be a lambda expression.", |
169 |
| - nameof(includeSelector)); |
170 |
| - } |
171 |
| - |
172 |
| - if (lambda.Body is not MemberExpression member |
173 |
| - || member.Member.MemberType != MemberTypes.Property) |
174 |
| - { |
175 |
| - throw new ArgumentException( |
176 |
| - "The include selector must be a property selector.", |
177 |
| - nameof(includeSelector)); |
178 |
| - } |
179 |
| - |
180 |
| - var context = dataLoader.GetOrSetState( |
181 |
| - DataStateKeys.Selector, |
182 |
| - _ => new DefaultSelectorBuilder()); |
183 |
| - context.Add(Rewrite(includeSelector)); |
184 |
| - return dataLoader; |
185 |
| - } |
186 |
| - |
187 | 21 | /// <summary>
|
188 | 22 | /// Applies the selector from the DataLoader state to a queryable.
|
189 | 23 | /// </summary>
|
@@ -343,6 +177,119 @@ public static IQueryable<KeyValueResult<TKey, IEnumerable<TValue>>> Select<T, TK
|
343 | 177 | return query.Select(keyValueSelectorExpr);
|
344 | 178 | }
|
345 | 179 |
|
| 180 | + /// <summary> |
| 181 | + /// Applies the predicate from the DataLoader state to a queryable. |
| 182 | + /// </summary> |
| 183 | + /// <param name="query"> |
| 184 | + /// The queryable to apply the predicate to. |
| 185 | + /// </param> |
| 186 | + /// <param name="builder"> |
| 187 | + /// The predicate builder. |
| 188 | + /// </param> |
| 189 | + /// <typeparam name="T"> |
| 190 | + /// The queryable type. |
| 191 | + /// </typeparam> |
| 192 | + /// <returns> |
| 193 | + /// Returns a query with the predicate applied, ready to fetch data with the key. |
| 194 | + /// </returns> |
| 195 | + /// <exception cref="ArgumentNullException"> |
| 196 | + /// Throws if <paramref name="query"/> is <c>null</c>. |
| 197 | + /// </exception> |
| 198 | + public static IQueryable<T> Where<T>( |
| 199 | + this IQueryable<T> query, |
| 200 | + IPredicateBuilder builder) |
| 201 | + { |
| 202 | + if (query is null) |
| 203 | + { |
| 204 | + throw new ArgumentNullException(nameof(query)); |
| 205 | + } |
| 206 | + |
| 207 | + if (builder is null) |
| 208 | + { |
| 209 | + throw new ArgumentNullException(nameof(builder)); |
| 210 | + } |
| 211 | + |
| 212 | + var predicate = builder.TryCompile<T>(); |
| 213 | + |
| 214 | + if (predicate is not null) |
| 215 | + { |
| 216 | + query = query.Where(predicate); |
| 217 | + } |
| 218 | + |
| 219 | + return query; |
| 220 | + } |
| 221 | + |
| 222 | + public static IQueryable<T> Order<T>(this IQueryable<T> queryable, SortDefinition<T>? sortDefinition) |
| 223 | + { |
| 224 | + if (queryable is null) |
| 225 | + { |
| 226 | + throw new ArgumentNullException(nameof(queryable)); |
| 227 | + } |
| 228 | + |
| 229 | + if (sortDefinition is null || sortDefinition.Operations.Length == 0) |
| 230 | + { |
| 231 | + return queryable; |
| 232 | + } |
| 233 | + |
| 234 | + var first = sortDefinition.Operations[0]; |
| 235 | + var query = first.ApplyOrderBy(queryable); |
| 236 | + |
| 237 | + for (var i = 1; i < sortDefinition.Operations.Length; i++) |
| 238 | + { |
| 239 | + query = sortDefinition.Operations[i].ApplyThenBy(query); |
| 240 | + } |
| 241 | + |
| 242 | + return query; |
| 243 | + } |
| 244 | + |
| 245 | + /// <summary> |
| 246 | + /// Applies a data context to the queryable. |
| 247 | + /// </summary> |
| 248 | + /// <param name="queryable"> |
| 249 | + /// The queryable that shall be projected, filtered and sorted. |
| 250 | + /// </param> |
| 251 | + /// <param name="queryContext"> |
| 252 | + /// The data context that shall be applied to the queryable. |
| 253 | + /// </param> |
| 254 | + /// <typeparam name="T"> |
| 255 | + /// The type of the queryable. |
| 256 | + /// </typeparam> |
| 257 | + /// <returns> |
| 258 | + /// Returns a queryable that has the data context applied. |
| 259 | + /// </returns> |
| 260 | + /// <exception cref="ArgumentNullException"> |
| 261 | + /// Throws if <paramref name="queryable"/> is <c>null</c> or if <paramref name="queryContext"/> is <c>null</c>. |
| 262 | + /// </exception> |
| 263 | + public static IQueryable<T> Apply<T>(this IQueryable<T> queryable, QueryContext<T>? queryContext) |
| 264 | + { |
| 265 | + if (queryable is null) |
| 266 | + { |
| 267 | + throw new ArgumentNullException(nameof(queryable)); |
| 268 | + } |
| 269 | + |
| 270 | + if (queryContext is null) |
| 271 | + { |
| 272 | + return queryable; |
| 273 | + } |
| 274 | + |
| 275 | + if (queryContext.Predicate is not null) |
| 276 | + { |
| 277 | + queryable = queryable.Where(queryContext.Predicate); |
| 278 | + } |
| 279 | + |
| 280 | + if (queryContext.Sorting is not null) |
| 281 | + { |
| 282 | + queryable = queryable.Order(queryContext.Sorting); |
| 283 | + } |
| 284 | + |
| 285 | + if (queryContext.Selector is not null) |
| 286 | + { |
| 287 | + queryable = queryable.Select(queryContext.Selector); |
| 288 | + } |
| 289 | + |
| 290 | + return queryable; |
| 291 | + } |
| 292 | + |
346 | 293 | private static Expression<T> ReplaceParameter<T>(
|
347 | 294 | Expression<T> expression,
|
348 | 295 | ParameterExpression oldParameter,
|
|
0 commit comments