Skip to content

Commit 50a9c2d

Browse files
authored
Merge pull request #3 from IgniteUI/dgdimitrov/subquery-get-generic-type
Get proper generic type in RunSubquery method
2 parents e72480a + e6268a7 commit 50a9c2d

File tree

2 files changed

+72
-41
lines changed

2 files changed

+72
-41
lines changed

QueryExecutor.cs

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
using AutoMapper;
2-
using AutoMapper.Internal;
3-
using AutoMapper.QueryableExtensions;
4-
using Microsoft.EntityFrameworkCore;
5-
using Microsoft.EntityFrameworkCore.Infrastructure;
6-
using Swashbuckle.AspNetCore.SwaggerGen;
1+
using System.ComponentModel.DataAnnotations;
72
using System.Globalization;
83
using System.Linq.Expressions;
94
using System.Reflection;
105
using System.Reflection.Emit;
116
using System.Text.Json;
127
using System.Text.Json.Nodes;
138
using System.Text.Json.Serialization;
9+
using AutoMapper;
10+
using AutoMapper.Internal;
11+
using AutoMapper.QueryableExtensions;
12+
using Microsoft.EntityFrameworkCore;
13+
using Microsoft.EntityFrameworkCore.Infrastructure;
14+
using Swashbuckle.AspNetCore.SwaggerGen;
1415

1516
namespace Infragistics.QueryBuilder.Executor
1617
{
@@ -33,6 +34,20 @@ public static object[] Run<TSource, TTarget>(this IQueryable<TSource> source, Qu
3334
return db is not null ? BuildQuery<TSource, TTarget>(db, source, query, mapper).ToArray() : Array.Empty<object>();
3435
}
3536

37+
public static object[] InvokeRunMethod(Type[] genericArgs, object?[] parameters)
38+
{
39+
var method = typeof(QueryExecutor)
40+
.GetMethods(BindingFlags.Static | BindingFlags.Public)
41+
.FirstOrDefault(m =>
42+
m.Name == "Run" &&
43+
m.IsGenericMethodDefinition &&
44+
m.GetGenericArguments().Length == genericArgs.Length)
45+
?.MakeGenericMethod(genericArgs);
46+
47+
var result = method?.Invoke(null, parameters) ?? Array.Empty<object>();
48+
return (object[])result;
49+
}
50+
3651
private static IQueryable<object> BuildQuery<TSource, TTarget>(DbContext db, IQueryable<TSource> source, Query? query, IMapper? mapper = null)
3752
{
3853
if (query is null)
@@ -222,9 +237,18 @@ private static Expression BuildInExpression<T>(DbContext db, Query? query, Membe
222237

223238
private static IEnumerable<dynamic> RunSubquery(DbContext db, Query? query)
224239
{
225-
var t = query?.Entity.ToLower(CultureInfo.InvariantCulture) ?? string.Empty;
226-
var p = db.GetType().GetProperty(t, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) ?? throw new InvalidOperationException($"Property '{t}' not found on type '{db.GetType()}'");
227-
return p.GetValue(db) is not IQueryable<dynamic> q ? Array.Empty<dynamic>() : [.. q.Run(query)];
240+
var propName = query?.Entity.ToLower(CultureInfo.InvariantCulture) ?? string.Empty;
241+
var prop = db.GetType().GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
242+
?? throw new InvalidOperationException($"Property '{propName}' not found on type '{db.GetType()}'");
243+
244+
var methods = typeof(QueryExecutor).GetMethods(BindingFlags.Static | BindingFlags.Public);
245+
var method = methods?.FirstOrDefault(m => m.CustomAttributes.Count() == 1);
246+
var dbSet = prop.GetValue(db) ?? throw new ValidationException($"DbSet property '{prop.Name}' is null in DbContext.");
247+
var genericType = prop.PropertyType.GetGenericArguments().FirstOrDefault() ?? throw new ValidationException($"Missing DbSet generic type");
248+
var queryable = dbSet?.GetType().GetMethod("AsQueryable")?.Invoke(dbSet, null);
249+
250+
return InvokeRunMethod([genericType], [queryable, query]);
251+
228252
}
229253

230254
private static dynamic? ProjectField(object? obj, string field)
@@ -242,13 +266,29 @@ private static Expression GetSearchValue(JsonValue? jsonVal, Type targetType)
242266
}
243267

244268
var nonNullableType = Nullable.GetUnderlyingType(targetType) ?? targetType;
245-
var value = jsonVal.Deserialize(targetType);
246269

247-
if (nonNullableType.IsEnum && value is string)
270+
if (nonNullableType.IsEnum)
248271
{
249-
return Expression.Constant(Enum.Parse(nonNullableType, (string)value));
272+
if (valueKind == JsonValueKind.String)
273+
{
274+
var enumValue = jsonVal.Deserialize<string>();
275+
if (enumValue != null)
276+
{
277+
return Expression.Constant(Enum.Parse(nonNullableType, enumValue));
278+
}
279+
}
280+
else if (valueKind == JsonValueKind.Number)
281+
{
282+
var enumValue = jsonVal.Deserialize<int?>();
283+
if (enumValue != null)
284+
{
285+
return Expression.Constant(Enum.ToObject(nonNullableType, enumValue));
286+
}
287+
}
250288
}
251289

290+
var value = jsonVal.Deserialize(targetType);
291+
252292
var convertedValue = Convert.ChangeType(value, nonNullableType, CultureInfo.InvariantCulture);
253293
return Expression.Constant(convertedValue, targetType);
254294
}

Services/QueryBuilderService.cs

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
using AutoMapper;
2-
using Microsoft.EntityFrameworkCore;
3-
using Microsoft.Extensions.Logging;
1+
using System.ComponentModel.DataAnnotations;
42
using System.Globalization;
53
using System.Reflection;
4+
using AutoMapper;
5+
using Microsoft.EntityFrameworkCore;
6+
using Microsoft.Extensions.Logging;
67

78
namespace Infragistics.QueryBuilder.Executor
89
{
@@ -15,35 +16,25 @@ public Dictionary<string, object[]> RunQuery(Query query)
1516
var t = query.Entity.ToLower(CultureInfo.InvariantCulture);
1617

1718
var propInfo = db?.GetType().GetProperties()
18-
.FirstOrDefault(p => p.PropertyType.IsGenericType && p.Name.ToLower(CultureInfo.InvariantCulture) == t && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>));
19-
if (propInfo != null)
20-
{
21-
var methods = typeof(QueryExecutor).GetMethods(BindingFlags.Static | BindingFlags.Public);
22-
var method = methods?.FirstOrDefault(m => m.CustomAttributes.Count() == 2);
19+
.FirstOrDefault(p =>
20+
p.PropertyType.IsGenericType &&
21+
p.Name.ToLower(CultureInfo.InvariantCulture) == t &&
22+
p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
23+
?? throw new InvalidOperationException($"Unknown entity {t}");
24+
25+
var dbSet = propInfo.GetValue(db) ?? throw new ValidationException($"DbSet property '{propInfo.Name}' is null in DbContext.");
26+
var dbGenericType = dbSet.GetType().GenericTypeArguments.FirstOrDefault() ?? throw new ValidationException($"Missing DbSet generic type");
27+
28+
var resultProperty = typeof(TResults).GetProperty(t, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase)
29+
?? throw new ValidationException($"Unknown entity {t}");
2330

24-
var dbSet = propInfo.GetValue(db);
25-
var dbGenericType = dbSet?.GetType()?.GenericTypeArguments.FirstOrDefault();
26-
if (dbGenericType != null && dbSet != null)
27-
{
28-
var dtoGenericType = typeof(TResults).GetProperty(propInfo.Name)?.PropertyType.GetElementType();
29-
if (dtoGenericType != null)
30-
{
31-
var genericMethod = method?.MakeGenericMethod(dbGenericType, dtoGenericType);
31+
var dtoGenericType = resultProperty.PropertyType.GetElementType() ?? throw new ValidationException($"Missing Dto generic type");
3232

33-
var asQueryableMethod = dbSet.GetType().GetMethod("AsQueryable");
34-
var queryable = asQueryableMethod?.Invoke(dbSet, null);
35-
if (queryable != null)
36-
{
37-
if (genericMethod?.Invoke(null, [queryable, query, mapper]) is object[] propRes)
38-
{
39-
return new Dictionary<string, object[]> { { propInfo.Name, propRes } };
40-
}
41-
}
42-
}
43-
}
44-
}
33+
var queryable = dbSet.GetType().GetMethod("AsQueryable")?.Invoke(dbSet, null)
34+
?? throw new InvalidOperationException($"DbSet '{propInfo.Name}' does not support AsQueryable().");
4535

46-
throw new InvalidOperationException($"Unknown entity {t}");
36+
var propRes = QueryExecutor.InvokeRunMethod([dbGenericType, dtoGenericType], [queryable, query, mapper]);
37+
return new Dictionary<string, object[]> { { propInfo.Name.ToLowerInvariant(), propRes } };
4738
}
4839
}
4940
}

0 commit comments

Comments
 (0)