Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
b372805c4e
|
@@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
@@ -16,10 +19,13 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
|
||||
private readonly IQueryHandlerAsync _queryHandlerAsync;
|
||||
private readonly IEnumerable<IQueryableProvider<TSource>> _queryableProviders;
|
||||
private readonly IEnumerable<IAlterQueryableService<TSource, TDestination>> _alterQueryableServices;
|
||||
private readonly IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>> _dynamicQueryInterceptorProviders;
|
||||
|
||||
private readonly IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>>
|
||||
_dynamicQueryInterceptorProviders;
|
||||
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public DynamicQueryHandlerBase(IQueryHandlerAsync queryHandlerAsync,
|
||||
public DynamicQueryHandlerBase(IQueryHandlerAsync queryHandlerAsync,
|
||||
IEnumerable<IQueryableProvider<TSource>> queryableProviders,
|
||||
IEnumerable<IAlterQueryableService<TSource, TDestination>> alterQueryableServices,
|
||||
IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>> dynamicQueryInterceptorProviders,
|
||||
@@ -32,7 +38,8 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
protected virtual Task<IQueryable<TSource>> GetQueryableAsync(IDynamicQuery query, CancellationToken cancellationToken = default)
|
||||
protected virtual Task<IQueryable<TSource>> GetQueryableAsync(IDynamicQuery query,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_queryableProviders.Any())
|
||||
{
|
||||
@@ -56,7 +63,8 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
|
||||
yield return _serviceProvider.GetService(type) as IQueryInterceptor;
|
||||
}
|
||||
|
||||
protected async Task<IQueryExecutionResult<TDestination>> ProcessQueryAsync(IDynamicQuery query, CancellationToken cancellationToken = default)
|
||||
protected async Task<IQueryExecutionResult<TDestination>> ProcessQueryAsync(IDynamicQuery query,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var source = await GetQueryableAsync(query, cancellationToken);
|
||||
source = await AlterSourceAsync(source, query, cancellationToken);
|
||||
@@ -67,11 +75,13 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
|
||||
_queryHandlerAsync.AddInterceptor(interceptor);
|
||||
|
||||
var criteria = CreateCriteriaFromQuery(query);
|
||||
var result = await _queryHandlerAsync.ExecuteAsync<TSource, TDestination>(source, criteria, options, cancellationToken);
|
||||
var result =
|
||||
await _queryHandlerAsync.ExecuteAsync<TSource, TDestination>(source, criteria, options, cancellationToken);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected virtual async Task<IQueryable<TSource>> AlterSourceAsync(IQueryable<TSource> source, IDynamicQuery query, CancellationToken cancellationToken)
|
||||
protected virtual async Task<IQueryable<TSource>> AlterSourceAsync(IQueryable<TSource> source, IDynamicQuery query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var t in _alterQueryableServices)
|
||||
source = await t.AlterQueryableAsync(source, query, cancellationToken);
|
||||
@@ -81,16 +91,94 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
|
||||
|
||||
protected virtual IQueryCriteria CreateCriteriaFromQuery(IDynamicQuery query)
|
||||
{
|
||||
var filters = query?.GetFilters() ?? new List<IFilter>();
|
||||
ConvertFilterValuesToPropertyTypes(filters);
|
||||
var criteria = new QueryCriteria
|
||||
{
|
||||
Page = query?.GetPage(),
|
||||
PageSize = query?.GetPageSize(),
|
||||
Filters = query?.GetFilters() ?? new List<IFilter>(),
|
||||
Filters = filters,
|
||||
Sorts = query?.GetSorts() ?? new List<ISort>(),
|
||||
Groups = query?.GetGroups() ?? new List<IGroup>(),
|
||||
Aggregates = query?.GetAggregates() ?? new List<IAggregate>()
|
||||
};
|
||||
return criteria;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts string filter values to the correct CLR type based on TSource property types.
|
||||
/// This handles the case where transport layers (e.g. gRPC) pass all values as strings,
|
||||
/// but PoweredSoft.DynamicLinq needs the actual type to build LINQ expressions.
|
||||
/// </summary>
|
||||
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2087",
|
||||
Justification = "TSource properties are preserved by EF Core and DynamicLinq usage")]
|
||||
private static void ConvertFilterValuesToPropertyTypes(List<IFilter> filters)
|
||||
{
|
||||
for (var i = filters.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var filter = filters[i];
|
||||
if (filter is SimpleFilter simpleFilter)
|
||||
{
|
||||
if (simpleFilter.Value == null)
|
||||
{
|
||||
filters.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (simpleFilter.Value is string strValue && !string.IsNullOrEmpty(strValue))
|
||||
{
|
||||
var propertyType = ResolvePropertyType(typeof(TSource), simpleFilter.Path);
|
||||
if (propertyType == null)
|
||||
continue;
|
||||
|
||||
var targetType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
|
||||
|
||||
if (targetType == typeof(DateTime))
|
||||
{
|
||||
if (DateTime.TryParse(strValue, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal,
|
||||
out var dt))
|
||||
{
|
||||
simpleFilter.Value = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
|
||||
}
|
||||
}
|
||||
else if (targetType == typeof(DateTimeOffset))
|
||||
{
|
||||
if (DateTimeOffset.TryParse(strValue, CultureInfo.InvariantCulture, DateTimeStyles.None,
|
||||
out var dto))
|
||||
{
|
||||
simpleFilter.Value = dto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (filter is CompositeFilter compositeFilter && compositeFilter.Filters != null)
|
||||
{
|
||||
ConvertFilterValuesToPropertyTypes(compositeFilter.Filters);
|
||||
}
|
||||
}
|
||||
|
||||
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070",
|
||||
Justification = "Property types are preserved by EF Core and DynamicLinq usage")]
|
||||
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075",
|
||||
Justification = "Nested property type resolution is inherently dynamic")]
|
||||
static Type? ResolvePropertyType(
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, string? path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return null;
|
||||
|
||||
var currentType = type;
|
||||
foreach (var part in path.Split('.'))
|
||||
{
|
||||
var property = currentType.GetProperty(part,
|
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
||||
if (property == null)
|
||||
return null;
|
||||
currentType = property.PropertyType;
|
||||
}
|
||||
|
||||
return currentType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user