basic querying not async.

This commit is contained in:
David Lebee 2018-10-18 20:52:05 -05:00
parent 369c6f117f
commit 65db65171a
14 changed files with 365 additions and 174 deletions

View File

@ -5,6 +5,10 @@
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\PoweredSoft.DynamicQuery.Core\PoweredSoft.DynamicQuery.Core.csproj" /> <ProjectReference Include="..\PoweredSoft.DynamicQuery.Core\PoweredSoft.DynamicQuery.Core.csproj" />
<ProjectReference Include="..\PoweredSoft.DynamicQuery\PoweredSoft.DynamicQuery.csproj" /> <ProjectReference Include="..\PoweredSoft.DynamicQuery\PoweredSoft.DynamicQuery.csproj" />

View File

@ -2,6 +2,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace PoweredSoft.DynamicQuery.Cli namespace PoweredSoft.DynamicQuery.Cli
{ {
@ -73,26 +75,42 @@ namespace PoweredSoft.DynamicQuery.Cli
}; };
var queryable = list.AsQueryable(); var queryable = list.AsQueryable();
var criteria = new QueryCriteria(); var criteria = new QueryCriteria();
criteria.Page = 1;
criteria.PageSize = 10;
criteria.Filters.Add(new SimpleFilter criteria.Filters = new List<IFilter>
{ {
Path = "LastName", new SimpleFilter() {Path = nameof(Person.LastName), Value = "Lebee", Type = FilterType.Equal},
Value = "Lebee", new CompositeFilter()
Type = FilterType.Equal, {
}); Type = FilterType.Composite,
And = true,
criteria.Filters.Add(new SimpleFilter Filters = new List<IFilter>
{ {
Path = "FirstName", new SimpleFilter() {Path = nameof(Person.FirstName), Value = "David", Type = FilterType.Equal},
Value = "David,Michaela", new SimpleFilter() {Path = nameof(Person.FirstName), Value = "Zohra", Type = FilterType.Equal},
Type = FilterType.Equal, }
}); }
};
var handler = new QueryHandler(); var handler = new QueryHandler();
handler.AddInterceptor(new PersonQueryInterceptor()); handler.AddInterceptor(new PersonQueryInterceptor());
handler.Execute(queryable, criteria); var result = handler.Execute(queryable, criteria);
var jsonSettings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
jsonSettings.Converters.Add(new StringEnumConverter { AllowIntegerValues = false });
Console.WriteLine("Request:\n");
Console.WriteLine(JsonConvert.SerializeObject(criteria, Formatting.Indented, jsonSettings));
Console.WriteLine("");
Console.WriteLine("Response:\n");
Console.WriteLine(JsonConvert.SerializeObject(result, Formatting.Indented, jsonSettings));
Console.ReadKey();
} }
} }
} }

View File

@ -1,14 +0,0 @@
using System.Linq;
namespace PoweredSoft.DynamicQuery.Core
{
public interface IBeforeQueryAlteredInterceptor : IQueryInterceptor
{
IQueryable InterceptQueryBeforeAltered(IQueryCriteria criteria, IQueryable queryable);
}
public interface IBeforeQueryAlteredInterceptor<T> : IQueryInterceptor
{
IQueryable<T> InterceptQueryBeforeAltered(IQueryCriteria criteria, IQueryable<T> queryable);
}
}

View File

@ -1,14 +0,0 @@
using System.Linq;
namespace PoweredSoft.DynamicQuery.Core
{
public interface IBeforeQueryExecuteInterceptor : IQueryInterceptor
{
IQueryable InterceptBeforeQuery(IQueryCriteria criteria, IQueryable queryable);
}
public interface IBeforeQueryExecuteInterceptor<T> : IQueryInterceptor
{
IQueryable<T> InterceptBeforeQuery(IQueryCriteria criteria, IQueryable<T> queryable);
}
}

View File

@ -0,0 +1,14 @@
using System.Linq;
namespace PoweredSoft.DynamicQuery.Core
{
public interface IBeforeQueryFilterInterceptor : IQueryInterceptor
{
IQueryable InterceptBeforeFiltering(IQueryCriteria criteria, IQueryable queryable);
}
public interface IBeforeQueryFilterInterceptor<T> : IQueryInterceptor
{
IQueryable<T> InterceptBeforeFiltering(IQueryCriteria criteria, IQueryable<T> queryable);
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PoweredSoft.DynamicQuery.Core
{
public interface IIncludeStrategyInterceptor : IQueryInterceptor
{
IQueryable InterceptIncludeStrategy(IQueryCriteria criteria, IQueryable queryable);
}
public interface IIncludeStrategyInterceptor<T> : IQueryInterceptor
{
IQueryable<T> InterceptIncludeStrategy(IQueryCriteria criteria, IQueryable<T> queryable);
}
}

View File

@ -0,0 +1,14 @@
using System.Linq;
namespace PoweredSoft.DynamicQuery.Core
{
public interface INoSortInterceptor : IQueryInterceptor
{
IQueryable InterceptNoSort(IQueryable queryable);
}
public interface INoSortInterceptor<T> : IQueryInterceptor
{
IQueryable<T> InterceptNoSort(IQueryable<T> queryable);
}
}

View File

@ -5,10 +5,18 @@ using System.Threading.Tasks;
namespace PoweredSoft.DynamicQuery.Core namespace PoweredSoft.DynamicQuery.Core
{ {
public interface IQueryHandler public interface IInterceptableQueryHandler
{ {
IQueryResult Execute(IQueryable queryable, IQueryCriteria criteria);
Task<IQueryResult> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria);
void AddInterceptor(IQueryInterceptor interceptor); void AddInterceptor(IQueryInterceptor interceptor);
} }
public interface IQueryHandler : IInterceptableQueryHandler
{
IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria);
}
public interface IAsyncQueryHandler : IInterceptableQueryHandler
{
Task<IQueryExecutionResult> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria);
}
} }

View File

@ -1,7 +1,11 @@
namespace PoweredSoft.DynamicQuery.Core using System.Linq;
namespace PoweredSoft.DynamicQuery.Core
{ {
public interface IQueryInterceptor public interface IQueryInterceptor
{ {
} }
} }

View File

@ -13,17 +13,21 @@ namespace PoweredSoft.DynamicQuery.Core
public interface IQueryResult public interface IQueryResult
{ {
long Count { get; }
List<IAggregateResult> Aggregates { get; } List<IAggregateResult> Aggregates { get; }
}
public interface IQueryResultSimple : IQueryResult
{
List<object> Data { get; } List<object> Data { get; }
} }
public interface IQueryResultGrouped : IQueryResult public interface IGroupQueryResult : IQueryResult
{ {
List<IQueryResult> Data { get; } string GroupPath { get; set; }
object GroupValue { get; set; }
} }
public interface IQueryExecutionResult : IQueryResult
{
long TotalRecords { get; set; }
long? NumberOfPages { get; set; }
}
} }

View File

@ -2,7 +2,7 @@
namespace PoweredSoft.DynamicQuery.Core namespace PoweredSoft.DynamicQuery.Core
{ {
public interface ISortInteceptor : IQueryInterceptor public interface ISortInterceptor : IQueryInterceptor
{ {
IEnumerable<ISort> InterceptSort(ISort sort); IEnumerable<ISort> InterceptSort(ISort sort);
} }

View File

@ -1,142 +1,61 @@
using PoweredSoft.DynamicQuery.Core; using System;
using PoweredSoft.DynamicLinq;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Net;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks; using PoweredSoft.DynamicLinq;
using PoweredSoft.DynamicLinq.Fluent; using PoweredSoft.DynamicQuery.Core;
using PoweredSoft.DynamicQuery.Extensions;
namespace PoweredSoft.DynamicQuery namespace PoweredSoft.DynamicQuery
{ {
public class QueryHandler : IQueryHandler public class QueryHandler : QueryHandlerBase, IQueryHandler
{ {
protected List<IQueryInterceptor> Interceptors { get; } = new List<IQueryInterceptor>(); internal MethodInfo ExecuteGeneric = typeof(QueryHandler).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).First(t => t.Name == "Execute" && t.IsGenericMethod);
protected IQueryCriteria Criteria { get; set; } internal IQueryExecutionResult ExecuteReflected() => (IQueryExecutionResult)ExecuteGeneric.MakeGenericMethod(QueryableUnderlyingType).Invoke(this, new object[]{});
protected IQueryable QueryableAtStart { get; private set; }
protected IQueryable CurrentQueryable { get; set; }
protected Type QueryableUnderlyingType => QueryableAtStart.ElementType;
private MethodInfo ApplyInterceptorsAndCriteriaMethod { get; } = typeof(QueryHandler).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).First(t => t.Name == "ApplyInterceptorsAndCriteria" && t.IsGenericMethod);
protected virtual void Reset(IQueryable queryable, IQueryCriteria criteria) protected virtual IQueryExecutionResult Execute<T>()
{ {
Criteria = criteria ?? throw new ArgumentNullException("criteria"); ApplyIncludeStrategyInterceptors<T>();
QueryableAtStart = queryable ?? throw new ArgumentNullException("queryable"); ApplyBeforeFilterInterceptors<T>();
CurrentQueryable = QueryableAtStart;
}
public virtual void AddInterceptor(IQueryInterceptor interceptor)
{
if (interceptor == null) throw new ArgumentNullException("interceptor");
if (!Interceptors.Contains(interceptor))
Interceptors.Add(interceptor);
}
protected virtual void ApplyInterceptorsAndCriteria<T>()
{
ApplySimpleBeforeAlterInterceptors();
ApplyGenericBeforeAlterInterceptors<T>();
ApplyFilters<T>(); ApplyFilters<T>();
return HasGrouping ? ExecuteGrouping<T>() : ExecuteNoGrouping<T>();
}
// create group levels. protected virtual IQueryExecutionResult ExecuteGrouping<T>()
if (Criteria.Groups.Count != 0) {
throw new NotImplementedException();
}
protected virtual IQueryExecutionResult ExecuteNoGrouping<T>()
{
var result = new QueryExecutionResult();
// total records.
result.TotalRecords = CurrentQueryable.LongCount();
// sorts and paging.
ApplyNoGroupingSorts<T>();
ApplyNoGroupingPaging<T>();
// the data.
result.Data = CurrentQueryable.ToObjectList();
// if there is paging.
if (HasPaging)
{ {
if (result.TotalRecords < Criteria.PageSize)
result.NumberOfPages = 1;
else
result.NumberOfPages = result.TotalRecords / Criteria.PageSize + (result.TotalRecords % Criteria.PageSize != 0 ? 1 : 0);
} }
// TODO. return result;
} }
protected virtual ConditionOperators? ResolveFromOrDefault(FilterType filterType) => public virtual IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria)
filterType.ConditionOperator();
protected virtual ConditionOperators ResolveFrom(FilterType filterType)
{
var ret = ResolveFromOrDefault(filterType);
if (ret == null)
throw new NotSupportedException($"{filterType} is not supported");
return ret.Value;
}
protected virtual void ApplyFilters<T>()
{
CurrentQueryable = CurrentQueryable.Query(whereBuilder =>
{
Criteria.Filters.ForEach(filter => ApplyFilter<T>(whereBuilder, filter));
});
}
protected virtual void ApplyFilter<T>(WhereBuilder whereBuilder, IFilter filter)
{
var transformedFilter = InterceptFilter<T>(filter);
if (transformedFilter is ISimpleFilter)
ApplySimpleFilter<T>(whereBuilder, transformedFilter as ISimpleFilter);
else if (transformedFilter is ICompositeFilter)
AppleCompositeFilter<T>(whereBuilder, transformedFilter as ICompositeFilter);
else
throw new NotSupportedException();
}
protected virtual void AppleCompositeFilter<T>(WhereBuilder whereBuilder, ICompositeFilter filter)
{
whereBuilder.SubQuery(subWhereBuilder => filter.Filters.ForEach(subFilter => ApplyFilter<T>(subWhereBuilder, subFilter)), filter.And == true);
}
protected virtual void ApplySimpleFilter<T>(WhereBuilder whereBuilder, ISimpleFilter filter)
{
var resolvedConditionOperator = ResolveFrom(filter.Type);
whereBuilder.Compare(filter.Path, resolvedConditionOperator, filter.Value, and: filter.And == true);
}
private IFilter InterceptFilter<T>(IFilter filter)
{
var ret = Interceptors.Where(t => t is IFilterInterceptor)
.Cast<IFilterInterceptor>()
.Aggregate(filter, (previousFilter, interceptor) => interceptor.InterceptFilter(previousFilter));
return ret;
}
private void ApplyInterceptorsAndCriteria()
{
var genericMethod = ApplyInterceptorsAndCriteriaMethod.MakeGenericMethod(QueryableUnderlyingType);
genericMethod.Invoke(this, null);
}
protected virtual void ApplyGenericBeforeAlterInterceptors<T>()
{
CurrentQueryable = Interceptors
.Where(t => t is IBeforeQueryAlteredInterceptor<T>)
.Cast<IBeforeQueryAlteredInterceptor<T>>()
.Aggregate((IQueryable<T>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptQueryBeforeAltered(Criteria, prev));
}
protected virtual void ApplySimpleBeforeAlterInterceptors()
{
CurrentQueryable = Interceptors
.Where(t => t is IBeforeQueryAlteredInterceptor)
.Cast<IBeforeQueryAlteredInterceptor>()
.Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptQueryBeforeAltered(Criteria, prev));
}
public virtual IQueryResult Execute(IQueryable queryable, IQueryCriteria criteria)
{ {
Reset(queryable, criteria); Reset(queryable, criteria);
ApplyInterceptorsAndCriteria(); return ExecuteReflected();
var debug = CurrentQueryable.ToObjectList();
return null;
}
public virtual Task<IQueryResult> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria)
{
throw new NotImplementedException();
} }
} }
} }

View File

@ -0,0 +1,167 @@
using PoweredSoft.DynamicQuery.Core;
using PoweredSoft.DynamicLinq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using PoweredSoft.DynamicLinq.Fluent;
using PoweredSoft.DynamicQuery.Extensions;
namespace PoweredSoft.DynamicQuery
{
public abstract class QueryHandlerBase : IInterceptableQueryHandler
{
protected List<IQueryInterceptor> Interceptors { get; } = new List<IQueryInterceptor>();
protected IQueryCriteria Criteria { get; set; }
protected IQueryable QueryableAtStart { get; private set; }
protected IQueryable CurrentQueryable { get; set; }
protected Type QueryableUnderlyingType => QueryableAtStart.ElementType;
protected bool HasGrouping => Criteria.Groups?.Any() == true;
protected bool HasPaging => Criteria.PageSize.HasValue && Criteria.PageSize > 0;
protected virtual void Reset(IQueryable queryable, IQueryCriteria criteria)
{
Criteria = criteria ?? throw new ArgumentNullException("criteria");
QueryableAtStart = queryable ?? throw new ArgumentNullException("queryable");
CurrentQueryable = QueryableAtStart;
}
public virtual void AddInterceptor(IQueryInterceptor interceptor)
{
if (interceptor == null) throw new ArgumentNullException("interceptor");
if (!Interceptors.Contains(interceptor))
Interceptors.Add(interceptor);
}
protected virtual void ApplyNoGroupingPaging<T>()
{
if (!HasPaging)
return;
var q = (IQueryable<T>) CurrentQueryable;
var skip = ((Criteria.Page ?? 1) - 1) * Criteria.PageSize.Value;
CurrentQueryable = q.Skip(skip).Take(Criteria.PageSize.Value);
}
protected virtual void ApplyNoGroupingSorts<T>()
{
if (Criteria.Sorts?.Any() != true)
{
ApplyNoSortInterceptor<T>();
return;
}
Criteria.Sorts.ForEach(sort =>
{
var transformedSort = InterceptSort<T>(sort);
if (transformedSort.Count == 0)
return;
});
}
protected virtual List<ISort> InterceptSort<T>(ISort sort)
{
var ret = Interceptors
.Where(t => t is ISortInterceptor)
.Cast<ISortInterceptor>()
.SelectMany(interceptor => interceptor.InterceptSort(sort));
return ret.Distinct().ToList();
}
protected virtual void ApplyNoSortInterceptor<T>()
{
CurrentQueryable = Interceptors.Where(t => t is INoSortInterceptor)
.Cast<INoSortInterceptor>()
.Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptNoSort(prev));
CurrentQueryable = Interceptors.Where(t => t is INoSortInterceptor<T>)
.Cast<INoSortInterceptor<T>>()
.Aggregate((IQueryable<T>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptNoSort(prev));
}
protected virtual ConditionOperators? ResolveFromOrDefault(FilterType filterType) => filterType.ConditionOperator();
protected virtual ConditionOperators ResolveFrom(FilterType filterType)
{
var ret = ResolveFromOrDefault(filterType);
if (ret == null)
throw new NotSupportedException($"{filterType} is not supported");
return ret.Value;
}
protected virtual void ApplyFilters<T>()
{
if (true != Criteria.Filters?.Any())
return;
CurrentQueryable = CurrentQueryable.Query(whereBuilder =>
{
Criteria.Filters.ForEach(filter => ApplyFilter<T>(whereBuilder, filter));
});
}
protected virtual void ApplyFilter<T>(WhereBuilder whereBuilder, IFilter filter)
{
var transformedFilter = InterceptFilter<T>(filter);
if (transformedFilter is ISimpleFilter)
ApplySimpleFilter<T>(whereBuilder, transformedFilter as ISimpleFilter);
else if (transformedFilter is ICompositeFilter)
AppleCompositeFilter<T>(whereBuilder, transformedFilter as ICompositeFilter);
else
throw new NotSupportedException();
}
protected virtual void AppleCompositeFilter<T>(WhereBuilder whereBuilder, ICompositeFilter filter)
{
whereBuilder.SubQuery(subWhereBuilder => filter.Filters.ForEach(subFilter => ApplyFilter<T>(subWhereBuilder, subFilter)), filter.And == true);
}
protected virtual void ApplySimpleFilter<T>(WhereBuilder whereBuilder, ISimpleFilter filter)
{
var resolvedConditionOperator = ResolveFrom(filter.Type);
whereBuilder.Compare(filter.Path, resolvedConditionOperator, filter.Value, and: filter.And == true);
}
protected virtual IFilter InterceptFilter<T>(IFilter filter)
{
var ret = Interceptors.Where(t => t is IFilterInterceptor)
.Cast<IFilterInterceptor>()
.Aggregate(filter, (previousFilter, interceptor) => interceptor.InterceptFilter(previousFilter));
return ret;
}
protected virtual void ApplyIncludeStrategyInterceptors<T>()
{
CurrentQueryable = Interceptors
.Where(t => t is IIncludeStrategyInterceptor)
.Cast<IIncludeStrategyInterceptor>()
.Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptIncludeStrategy(Criteria, prev));
CurrentQueryable = Interceptors
.Where(t => t is IIncludeStrategyInterceptor<T>)
.Cast<IIncludeStrategyInterceptor<T>>()
.Aggregate((IQueryable<T>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptIncludeStrategy(Criteria, prev));
}
protected virtual void ApplyBeforeFilterInterceptors<T>()
{
CurrentQueryable = Interceptors
.Where(t => t is IBeforeQueryFilterInterceptor)
.Cast<IBeforeQueryFilterInterceptor>()
.Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptBeforeFiltering(Criteria, prev));
CurrentQueryable = Interceptors
.Where(t => t is IBeforeQueryFilterInterceptor<T>)
.Cast<IBeforeQueryFilterInterceptor<T>>()
.Aggregate((IQueryable<T>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptBeforeFiltering(Criteria, prev));
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PoweredSoft.DynamicQuery.Core;
namespace PoweredSoft.DynamicQuery
{
/// <summary>
/// Represents an aggregate result.
/// </summary>
public class AggregateResult : IAggregateResult
{
public string Path { get; set; }
public AggregateType Type { get; set; }
public object Value { get; set; }
}
// part of a result.
public abstract class QueryResult : IQueryResult
{
public List<IAggregateResult> Aggregates { get; set; }
public List<object> Data { get; set; }
public bool ShouldSerializeAggregates() => Aggregates != null;
}
// not grouped.
public class QueryExecutionResult : QueryResult, IQueryExecutionResult
{
public long TotalRecords { get; set; }
public long? NumberOfPages { get; set; }
}
// grouped.
public class GroupQueryResult : QueryResult, IGroupQueryResult
{
public string GroupPath { get; set; }
public object GroupValue { get; set; }
public IEnumerable<IQueryResult> GroupItems => Data.Cast<IQueryResult>();
public bool ShouldSerializeGroupItems() => false;
}
public class GroupedQueryExecutionResult : GroupQueryResult, IQueryExecutionResult
{
public long TotalRecords { get; set; }
public long? NumberOfPages { get; set; }
}
}