|
5 | 5 | using Microsoft.EntityFrameworkCore.Query;
|
6 | 6 | using System;
|
7 | 7 | using System.Collections.Generic;
|
| 8 | +using System.Globalization; |
8 | 9 | using System.Linq;
|
9 | 10 | using System.Linq.Expressions;
|
10 | 11 | using System.Threading;
|
11 | 12 | using System.Threading.Tasks;
|
| 13 | +using LogicBuilder.Expressions.Utils; |
12 | 14 |
|
13 | 15 | namespace AutoMapper.AspNet.OData
|
14 | 16 | {
|
@@ -133,19 +135,24 @@ private static IQueryable<TModel> GetQueryable<TModel, TData>(this IQueryable<TD
|
133 | 135 |
|
134 | 136 | var expansions = options.SelectExpand.GetExpansions(typeof(TModel));
|
135 | 137 |
|
136 |
| - var selects = options.SelectExpand.GetSelects(); |
137 |
| - var literalLists = typeof(TModel).GetLiteralLists(); |
138 |
| - var includes = selects.Union(literalLists).ToList(); |
| 138 | + var includeProperties = expansions |
| 139 | + .Select(list => new List<Expansion>(list)) |
| 140 | + .BuildIncludes<TModel>(options.SelectExpand.GetSelects()); |
139 | 141 |
|
| 142 | + var includeLiteralLists = expansions |
| 143 | + .Select(list => new List<Expansion>(list)) |
| 144 | + .BuildWithLiteralLists<TModel>(options.SelectExpand.GetSelects()); |
| 145 | + |
| 146 | + var includes = includeProperties |
| 147 | + .UnionBy(includeLiteralLists, e => e.Body.ToString()) |
| 148 | + .ToList(); |
| 149 | + |
140 | 150 | return query.GetQuery
|
141 | 151 | (
|
142 | 152 | mapper,
|
143 | 153 | filter,
|
144 | 154 | options.GetQueryableExpression(querySettings?.ODataSettings),
|
145 |
| - expansions |
146 |
| - .Select(list => new List<Expansion>(list)) |
147 |
| - .BuildIncludes<TModel>(includes) |
148 |
| - .ToList(), |
| 155 | + includes, |
149 | 156 | querySettings?.ProjectionSettings
|
150 | 157 | ).UpdateQueryableExpression(expansions, options.Context);
|
151 | 158 | }
|
@@ -205,5 +212,175 @@ private static long QueryLongCount<TModel, TData>(this IQueryable<TData> query,
|
205 | 212 | (
|
206 | 213 | mapper.MapExpression<Expression<Func<TData, bool>>>(modelFilter)
|
207 | 214 | );
|
| 215 | + |
| 216 | + private static ICollection<Expression<Func<TSource, object>>> BuildWithLiteralLists<TSource>(this IEnumerable<List<Expansion>> includes, List<string> selects) |
| 217 | + where TSource : class |
| 218 | + { |
| 219 | + return GetAllExpansions(new List<LambdaExpression>()); |
| 220 | + |
| 221 | + List<Expression<Func<TSource, object>>> GetAllExpansions(List<LambdaExpression> valueMemberSelectors) |
| 222 | + { |
| 223 | + string parameterName = "i"; |
| 224 | + ParameterExpression param = Expression.Parameter(typeof(TSource), parameterName); |
| 225 | + |
| 226 | + valueMemberSelectors.AddSelectors(selects, param, param); |
| 227 | + |
| 228 | + return includes |
| 229 | + .Select(include => BuildSelectorExpression<TSource>(include, valueMemberSelectors, parameterName)) |
| 230 | + .Concat(valueMemberSelectors.Select(selector => (Expression<Func<TSource, object>>)selector)) |
| 231 | + .ToList(); |
| 232 | + } |
| 233 | + } |
| 234 | + |
| 235 | + private static Expression<Func<TSource, object>> BuildSelectorExpression<TSource>(List<Expansion> fullName, List<LambdaExpression> valueMemberSelectors, string parameterName = "i") |
| 236 | + { |
| 237 | + ParameterExpression param = Expression.Parameter(typeof(TSource), parameterName); |
| 238 | + |
| 239 | + return (Expression<Func<TSource, object>>)Expression.Lambda |
| 240 | + ( |
| 241 | + typeof(Func<,>).MakeGenericType(new[] { param.Type, typeof(object) }), |
| 242 | + BuildSelectorExpression(param, fullName, valueMemberSelectors, parameterName), |
| 243 | + param |
| 244 | + ); |
| 245 | + } |
| 246 | + |
| 247 | + // e.g. /opstenant?$top=5&$expand=Buildings($expand=Builder($expand=City)) |
| 248 | + private static Expression BuildSelectorExpression(Expression sourceExpression, List<Expansion> parts, List<LambdaExpression> valueMemberSelectors, string parameterName = "i") |
| 249 | + { |
| 250 | + Expression parent = sourceExpression; |
| 251 | + |
| 252 | + //Arguments to create a nested expression when the parent expansion is a collection |
| 253 | + //See AddChildSeelctors() below |
| 254 | + List<LambdaExpression> childValueMemberSelectors = new List<LambdaExpression>(); |
| 255 | + |
| 256 | + for (int i = 0; i < parts.Count; i++) |
| 257 | + { |
| 258 | + if (parent.Type.IsList()) |
| 259 | + { |
| 260 | + Expression selectExpression = GetSelectExpression |
| 261 | + ( |
| 262 | + parts.Skip(i), |
| 263 | + parent, |
| 264 | + childValueMemberSelectors, |
| 265 | + parameterName |
| 266 | + ); |
| 267 | + |
| 268 | + AddChildSeelctors(); |
| 269 | + |
| 270 | + return selectExpression; |
| 271 | + } |
| 272 | + else |
| 273 | + { |
| 274 | + parent = Expression.MakeMemberAccess(parent, parent.Type.GetMemberInfo(parts[i].MemberName)); |
| 275 | + |
| 276 | + if (parent.Type.IsList()) |
| 277 | + { |
| 278 | + ParameterExpression childParam = Expression.Parameter(parent.GetUnderlyingElementType(), parameterName.ChildParameterName()); |
| 279 | + //selectors from an underlying list element must be added here. |
| 280 | + childValueMemberSelectors.AddSelectors |
| 281 | + ( |
| 282 | + parts[i].Selects, |
| 283 | + childParam, |
| 284 | + childParam |
| 285 | + ); |
| 286 | + } |
| 287 | + else |
| 288 | + { |
| 289 | + valueMemberSelectors.AddSelectors(parts[i].Selects, Expression.Parameter(sourceExpression.Type, parameterName), parent); |
| 290 | + } |
| 291 | + } |
| 292 | + } |
| 293 | + |
| 294 | + AddChildSeelctors(); |
| 295 | + |
| 296 | + return parent; |
| 297 | + |
| 298 | + //Adding childValueMemberSelectors created above and in a the recursive call: |
| 299 | + //i0 => i0.Builder.Name becomes |
| 300 | + //i => i.Buildings.Select(i0 => i0.Builder.Name) |
| 301 | + void AddChildSeelctors() |
| 302 | + { |
| 303 | + childValueMemberSelectors.ForEach(selector => |
| 304 | + { |
| 305 | + valueMemberSelectors.Add(Expression.Lambda |
| 306 | + ( |
| 307 | + typeof(Func<,>).MakeGenericType(new[] { sourceExpression.Type, typeof(object) }), |
| 308 | + Expression.Call |
| 309 | + ( |
| 310 | + typeof(Enumerable), |
| 311 | + "Select", |
| 312 | + new Type[] { parent.GetUnderlyingElementType(), typeof(object) }, |
| 313 | + parent, |
| 314 | + selector |
| 315 | + ), |
| 316 | + Expression.Parameter(sourceExpression.Type, parameterName) |
| 317 | + )); |
| 318 | + }); |
| 319 | + } |
| 320 | + } |
| 321 | + |
| 322 | + private static Expression GetSelectExpression(IEnumerable<Expansion> expansions, Expression parent, List<LambdaExpression> valueMemberSelectors, string parameterName) |
| 323 | + { |
| 324 | + ParameterExpression parameter = Expression.Parameter(parent.GetUnderlyingElementType(), parameterName.ChildParameterName()); |
| 325 | + Expression selectorBody = BuildSelectorExpression(parameter, expansions.ToList(), valueMemberSelectors, parameter.Name); |
| 326 | + return Expression.Call |
| 327 | + ( |
| 328 | + typeof(Enumerable), |
| 329 | + "Select", |
| 330 | + new Type[] { parameter.Type, selectorBody.Type }, |
| 331 | + parent, |
| 332 | + Expression.Lambda |
| 333 | + ( |
| 334 | + typeof(Func<,>).MakeGenericType(new[] { parameter.Type, selectorBody.Type }), |
| 335 | + selectorBody, |
| 336 | + parameter |
| 337 | + ) |
| 338 | + ); |
| 339 | + } |
| 340 | + |
| 341 | + private static string ChildParameterName(this string currentParameterName) |
| 342 | + { |
| 343 | + string lastChar = currentParameterName.Substring(currentParameterName.Length - 1); |
| 344 | + if (short.TryParse(lastChar, out short lastCharShort)) |
| 345 | + { |
| 346 | + return string.Concat |
| 347 | + ( |
| 348 | + currentParameterName.Substring(0, currentParameterName.Length - 1), |
| 349 | + (lastCharShort++).ToString(CultureInfo.CurrentCulture) |
| 350 | + ); |
| 351 | + } |
| 352 | + else |
| 353 | + { |
| 354 | + return currentParameterName += "0"; |
| 355 | + } |
| 356 | + } |
| 357 | + |
| 358 | + private static void AddSelectors(this List<LambdaExpression> valueMemberSelectors, List<string> selects, ParameterExpression param, Expression parentBody) |
| 359 | + { |
| 360 | + if (parentBody.Type.IsList() || parentBody.Type.IsLiteralType()) |
| 361 | + return; |
| 362 | + |
| 363 | + valueMemberSelectors.AddRange |
| 364 | + ( |
| 365 | + parentBody.Type |
| 366 | + .GetSelectedMembers(selects) |
| 367 | + .Select(member => Expression.MakeMemberAccess(parentBody, member)) |
| 368 | + .Select |
| 369 | + ( |
| 370 | + selector => selector.Type.IsValueType |
| 371 | + ? (Expression)Expression.Convert(selector, typeof(object)) |
| 372 | + : selector |
| 373 | + ) |
| 374 | + .Select |
| 375 | + ( |
| 376 | + selector => Expression.Lambda |
| 377 | + ( |
| 378 | + typeof(Func<,>).MakeGenericType(new[] { param.Type, typeof(object) }), |
| 379 | + selector, |
| 380 | + param |
| 381 | + ) |
| 382 | + ) |
| 383 | + ); |
| 384 | + } |
208 | 385 | }
|
209 | 386 | }
|
0 commit comments