Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 52 additions & 12 deletions QueryExecutor.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
using AutoMapper;
using AutoMapper.Internal;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using AutoMapper;
using AutoMapper.Internal;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Swashbuckle.AspNetCore.SwaggerGen;

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

public static object[] InvokeRunMethod(Type[] genericArgs, object?[] parameters)
{
var method = typeof(QueryExecutor)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.FirstOrDefault(m =>
m.Name == "Run" &&
m.IsGenericMethodDefinition &&
m.GetGenericArguments().Length == genericArgs.Length)
?.MakeGenericMethod(genericArgs);

var result = method?.Invoke(null, parameters) ?? Array.Empty<object>();
return (object[])result;
}

private static IQueryable<object> BuildQuery<TSource, TTarget>(DbContext db, IQueryable<TSource> source, Query? query, IMapper? mapper = null)
{
if (query is null)
Expand Down Expand Up @@ -222,9 +237,18 @@ private static Expression BuildInExpression<T>(DbContext db, Query? query, Membe

private static IEnumerable<dynamic> RunSubquery(DbContext db, Query? query)
{
var t = query?.Entity.ToLower(CultureInfo.InvariantCulture) ?? string.Empty;
var p = db.GetType().GetProperty(t, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) ?? throw new InvalidOperationException($"Property '{t}' not found on type '{db.GetType()}'");
return p.GetValue(db) is not IQueryable<dynamic> q ? Array.Empty<dynamic>() : [.. q.Run(query)];
var propName = query?.Entity.ToLower(CultureInfo.InvariantCulture) ?? string.Empty;
var prop = db.GetType().GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
?? throw new InvalidOperationException($"Property '{propName}' not found on type '{db.GetType()}'");

var methods = typeof(QueryExecutor).GetMethods(BindingFlags.Static | BindingFlags.Public);
var method = methods?.FirstOrDefault(m => m.CustomAttributes.Count() == 1);
var dbSet = prop.GetValue(db) ?? throw new ValidationException($"DbSet property '{prop.Name}' is null in DbContext.");
var genericType = prop.PropertyType.GetGenericArguments().FirstOrDefault() ?? throw new ValidationException($"Missing DbSet generic type");
var queryable = dbSet?.GetType().GetMethod("AsQueryable")?.Invoke(dbSet, null);

return InvokeRunMethod([genericType], [queryable, query]);

}

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

var nonNullableType = Nullable.GetUnderlyingType(targetType) ?? targetType;
var value = jsonVal.Deserialize(targetType);

if (nonNullableType.IsEnum && value is string)
if (nonNullableType.IsEnum)
{
return Expression.Constant(Enum.Parse(nonNullableType, (string)value));
if (valueKind == JsonValueKind.String)
{
var enumValue = jsonVal.Deserialize<string>();
if (enumValue != null)
{
return Expression.Constant(Enum.Parse(nonNullableType, enumValue));
}
}
else if (valueKind == JsonValueKind.Number)
{
var enumValue = jsonVal.Deserialize<int?>();
if (enumValue != null)
{
return Expression.Constant(Enum.ToObject(nonNullableType, enumValue));
}
}
}

var value = jsonVal.Deserialize(targetType);

var convertedValue = Convert.ChangeType(value, nonNullableType, CultureInfo.InvariantCulture);
return Expression.Constant(convertedValue, targetType);
}
Expand Down
49 changes: 20 additions & 29 deletions Services/QueryBuilderService.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Reflection;
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

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

var propInfo = db?.GetType().GetProperties()
.FirstOrDefault(p => p.PropertyType.IsGenericType && p.Name.ToLower(CultureInfo.InvariantCulture) == t && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>));
if (propInfo != null)
{
var methods = typeof(QueryExecutor).GetMethods(BindingFlags.Static | BindingFlags.Public);
var method = methods?.FirstOrDefault(m => m.CustomAttributes.Count() == 2);
.FirstOrDefault(p =>
p.PropertyType.IsGenericType &&
p.Name.ToLower(CultureInfo.InvariantCulture) == t &&
p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
?? throw new InvalidOperationException($"Unknown entity {t}");

var dbSet = propInfo.GetValue(db) ?? throw new ValidationException($"DbSet property '{propInfo.Name}' is null in DbContext.");
var dbGenericType = dbSet.GetType().GenericTypeArguments.FirstOrDefault() ?? throw new ValidationException($"Missing DbSet generic type");

var resultProperty = typeof(TResults).GetProperty(t, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase)
?? throw new ValidationException($"Unknown entity {t}");

var dbSet = propInfo.GetValue(db);
var dbGenericType = dbSet?.GetType()?.GenericTypeArguments.FirstOrDefault();
if (dbGenericType != null && dbSet != null)
{
var dtoGenericType = typeof(TResults).GetProperty(propInfo.Name)?.PropertyType.GetElementType();
if (dtoGenericType != null)
{
var genericMethod = method?.MakeGenericMethod(dbGenericType, dtoGenericType);
var dtoGenericType = resultProperty.PropertyType.GetElementType() ?? throw new ValidationException($"Missing Dto generic type");

var asQueryableMethod = dbSet.GetType().GetMethod("AsQueryable");
var queryable = asQueryableMethod?.Invoke(dbSet, null);
if (queryable != null)
{
if (genericMethod?.Invoke(null, [queryable, query, mapper]) is object[] propRes)
{
return new Dictionary<string, object[]> { { propInfo.Name, propRes } };
}
}
}
}
}
var queryable = dbSet.GetType().GetMethod("AsQueryable")?.Invoke(dbSet, null)
?? throw new InvalidOperationException($"DbSet '{propInfo.Name}' does not support AsQueryable().");

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