Skip to content

Commit 5d173c0

Browse files
committed
refactor(query-set): move querying logic into the EntityRepository
1 parent 3ef7af6 commit 5d173c0

File tree

8 files changed

+187
-94
lines changed

8 files changed

+187
-94
lines changed

src/JsonApiDotNetCore/Controllers/JsonApiController.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,14 @@ private IQueryable<T> ApplyQuery(IQueryable<T> entities)
162162
{
163163
if(!HttpContext.Request.Query.Any())
164164
return entities;
165+
166+
var querySet = new QuerySet<T>( _jsonApiContext);
165167

166-
return new QuerySet<T>( _jsonApiContext)
167-
.ApplyQuery(entities);
168+
entities = _entities.Filter(entities, querySet.Filter);
169+
170+
entities = _entities.Sort(entities, querySet.SortParameters);
171+
172+
return entities;
168173
}
169174
}
170175
}

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
using System.Collections.Generic;
12
using System.Linq;
23
using System.Threading.Tasks;
34
using JsonApiDotNetCore.Extensions;
5+
using JsonApiDotNetCore.Internal.Query;
46
using JsonApiDotNetCore.Models;
57
using JsonApiDotNetCore.Services;
68
using Microsoft.EntityFrameworkCore;
@@ -41,31 +43,54 @@ public DefaultEntityRepository(
4143
_logger = loggerFactory.CreateLogger<DefaultEntityRepository<TEntity, TId>>();
4244
}
4345

44-
public IQueryable<TEntity> Get()
46+
public virtual IQueryable<TEntity> Get()
4547
{
4648
return _dbSet;
4749
}
4850

49-
public async Task<TEntity> GetAsync(TId id)
51+
public virtual IQueryable<TEntity> Filter(IQueryable<TEntity> entities, FilterQuery filterQuery)
52+
{
53+
if(filterQuery == null)
54+
return entities;
55+
56+
return entities
57+
.Filter(filterQuery);
58+
}
59+
60+
public virtual IQueryable<TEntity> Sort(IQueryable<TEntity> entities, List<SortQuery> sortQueries)
61+
{
62+
if(sortQueries == null || sortQueries.Count == 0)
63+
return entities;
64+
65+
var orderedEntities = entities.Sort(sortQueries[0]);
66+
67+
if(sortQueries.Count() > 1)
68+
for(var i=1; i < sortQueries.Count(); i++)
69+
orderedEntities = orderedEntities.Sort(sortQueries[i]);
70+
71+
return orderedEntities;
72+
}
73+
74+
public virtual async Task<TEntity> GetAsync(TId id)
5075
{
5176
return await _dbSet.FirstOrDefaultAsync(e => e.Id.Equals(id));
5277
}
5378

54-
public async Task<TEntity> GetAndIncludeAsync(TId id, string relationshipName)
79+
public virtual async Task<TEntity> GetAndIncludeAsync(TId id, string relationshipName)
5580
{
5681
return await _dbSet
5782
.Include(relationshipName)
5883
.FirstOrDefaultAsync(e => e.Id.Equals(id));
5984
}
6085

61-
public async Task<TEntity> CreateAsync(TEntity entity)
86+
public virtual async Task<TEntity> CreateAsync(TEntity entity)
6287
{
6388
_dbSet.Add(entity);
6489
await _context.SaveChangesAsync();
6590
return entity;
6691
}
6792

68-
public async Task<TEntity> UpdateAsync(TId id, TEntity entity)
93+
public virtual async Task<TEntity> UpdateAsync(TId id, TEntity entity)
6994
{
7095
var oldEntity = await GetAsync(id);
7196

@@ -82,7 +107,7 @@ public async Task<TEntity> UpdateAsync(TId id, TEntity entity)
82107
return oldEntity;
83108
}
84109

85-
public async Task<bool> DeleteAsync(TId id)
110+
public virtual async Task<bool> DeleteAsync(TId id)
86111
{
87112
var entity = await GetAsync(id);
88113

src/JsonApiDotNetCore/Data/IEntityRepository.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
using System.Collections.Generic;
12
using System.Linq;
23
using System.Threading.Tasks;
4+
using JsonApiDotNetCore.Internal.Query;
35
using JsonApiDotNetCore.Models;
46

57
namespace JsonApiDotNetCore.Data
@@ -15,6 +17,10 @@ public interface IEntityRepository<TEntity, in TId>
1517
{
1618
IQueryable<TEntity> Get();
1719

20+
IQueryable<TEntity> Filter(IQueryable<TEntity> entities, FilterQuery filterQuery);
21+
22+
IQueryable<TEntity> Sort(IQueryable<TEntity> entities, List<SortQuery> sortQueries);
23+
1824
Task<TEntity> GetAsync(TId id);
1925

2026
Task<TEntity> GetAndIncludeAsync(TId id, string relationshipName);

src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using JsonApiDotNetCore.Internal;
33
using Microsoft.AspNetCore.Builder;
4-
using Microsoft.AspNetCore.Mvc.Infrastructure;
54
using Microsoft.AspNetCore.Mvc.Internal;
65
using Microsoft.AspNetCore.Routing;
76
using Microsoft.Extensions.DependencyInjection;
Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,93 @@
11

2+
using System;
23
using System.Linq;
34
using System.Linq.Expressions;
45
using System.Reflection;
6+
using JsonApiDotNetCore.Internal.Query;
57

68
namespace JsonApiDotNetCore.Extensions
79
{
810
public static class IQueryableExtensions
911
{
10-
public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string propertyName)
12+
public static IOrderedQueryable<TSource> Sort<TSource>(this IQueryable<TSource> source, SortQuery sortQuery)
1113
{
12-
// LAMBDA: x => x.[PropertyName]
13-
var parameter = Expression.Parameter(typeof(TSource), "x");
14-
Expression property = Expression.Property(parameter, propertyName);
15-
var lambda = Expression.Lambda(property, parameter);
14+
if(sortQuery.Direction == SortDirection.Descending)
15+
return source.OrderByDescending(sortQuery.SortedAttribute.InternalAttributeName);
16+
17+
return source.OrderBy(sortQuery.SortedAttribute.InternalAttributeName);
18+
}
1619

17-
// REFLECTION: source.OrderBy(x => x.Property)
18-
var orderByMethod = typeof(Queryable).GetMethods().First(x => x.Name == "OrderBy" && x.GetParameters().Length == 2);
19-
var orderByGeneric = orderByMethod.MakeGenericMethod(typeof(TSource), property.Type);
20-
var result = orderByGeneric.Invoke(null, new object[] { source, lambda });
20+
public static IOrderedQueryable<TSource> Sort<TSource>(this IOrderedQueryable<TSource> source, SortQuery sortQuery)
21+
{
22+
if(sortQuery.Direction == SortDirection.Descending)
23+
return source.ThenByDescending(sortQuery.SortedAttribute.InternalAttributeName);
24+
25+
return source.ThenBy(sortQuery.SortedAttribute.InternalAttributeName);
26+
}
2127

22-
return (IOrderedQueryable<TSource>)result;
28+
public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string propertyName)
29+
{
30+
return CallGenericOrderMethod(source, propertyName, "OrderBy");
2331
}
2432

2533
public static IOrderedQueryable<TSource> OrderByDescending<TSource>(this IQueryable<TSource> source, string propertyName)
2634
{
27-
// LAMBDA: x => x.[PropertyName]
35+
return CallGenericOrderMethod(source, propertyName, "OrderByDescending");
36+
}
37+
38+
public static IOrderedQueryable<TSource> ThenBy<TSource>(this IOrderedQueryable<TSource> source, string propertyName)
39+
{
40+
return CallGenericOrderMethod(source, propertyName, "ThenBy");
41+
}
42+
43+
public static IOrderedQueryable<TSource> ThenByDescending<TSource>(this IOrderedQueryable<TSource> source, string propertyName)
44+
{
45+
return CallGenericOrderMethod(source, propertyName, "ThenByDescending");
46+
}
47+
48+
private static IOrderedQueryable<TSource> CallGenericOrderMethod<TSource>(IQueryable<TSource> source, string propertyName, string method)
49+
{
50+
// {x}
2851
var parameter = Expression.Parameter(typeof(TSource), "x");
29-
Expression property = Expression.Property(parameter, propertyName);
52+
// {x.propertyName}
53+
var property = Expression.Property(parameter, propertyName);
54+
// {x=>x.propertyName}
3055
var lambda = Expression.Lambda(property, parameter);
3156

3257
// REFLECTION: source.OrderBy(x => x.Property)
33-
var orderByMethod = typeof(Queryable).GetMethods().First(x => x.Name == "OrderByDescending" && x.GetParameters().Length == 2);
58+
var orderByMethod = typeof(Queryable).GetMethods().First(x => x.Name == method && x.GetParameters().Length == 2);
3459
var orderByGeneric = orderByMethod.MakeGenericMethod(typeof(TSource), property.Type);
3560
var result = orderByGeneric.Invoke(null, new object[] { source, lambda });
3661

3762
return (IOrderedQueryable<TSource>)result;
3863
}
64+
65+
public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> source, FilterQuery filterQuery)
66+
{
67+
if(filterQuery == null)
68+
return source;
69+
70+
var concreteType = typeof(TSource);
71+
var property = concreteType.GetProperty(filterQuery.FilteredAttribute.InternalAttributeName);
72+
73+
if (property == null)
74+
throw new ArgumentException($"'{filterQuery.FilteredAttribute.InternalAttributeName}' is not a valid property of '{concreteType}'");
75+
76+
// convert the incoming value to the target value type
77+
// "1" -> 1
78+
var convertedValue = Convert.ChangeType(filterQuery.PropertyValue, property.PropertyType);
79+
// {model}
80+
var parameter = Expression.Parameter(concreteType, "model");
81+
// {model.Id}
82+
var left = Expression.PropertyOrField(parameter, property.Name);
83+
// {1}
84+
var right = Expression.Constant(convertedValue, property.PropertyType);
85+
// {model.Id == 1}
86+
var body = Expression.Equal(left, right);
87+
88+
var lambda = Expression.Lambda<Func<TSource, bool>>(body, parameter);
89+
90+
return source.Where(lambda);
91+
}
3992
}
4093
}

src/JsonApiDotNetCore/Internal/JsonApiRouteHandler.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,7 @@ public JsonApiRouteHandler(
3636
public VirtualPathData GetVirtualPath(VirtualPathContext context)
3737
{
3838
if (context == null)
39-
{
4039
throw new ArgumentNullException(nameof(context));
41-
}
4240

4341
// We return null here because we're not responsible for generating the url, the route is.
4442
return null;
@@ -47,9 +45,7 @@ public VirtualPathData GetVirtualPath(VirtualPathContext context)
4745
public Task RouteAsync(RouteContext context)
4846
{
4947
if (context == null)
50-
{
5148
throw new ArgumentNullException(nameof(context));
52-
}
5349

5450
var candidates = _actionSelector.SelectCandidates(context);
5551
if (candidates == null || candidates.Count == 0)

src/JsonApiDotNetCore/Internal/Query/QuerySet.cs

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
using System;
21
using System.Collections.Generic;
32
using System.Linq;
4-
using System.Linq.Expressions;
5-
using System.Reflection;
63
using JsonApiDotNetCore.Services;
7-
using JsonApiDotNetCore.Extensions;
84

95
namespace JsonApiDotNetCore.Internal.Query
106
{
@@ -79,61 +75,5 @@ private AttrAttribute GetAttribute(string propertyName)
7975
attr.InternalAttributeName.ToLower() == propertyName.ToLower()
8076
);
8177
}
82-
83-
public IQueryable<T> ApplyQuery(IQueryable<T> entities)
84-
{
85-
entities = ApplyFilter(entities);
86-
entities = ApplySort(entities);
87-
return entities;
88-
}
89-
90-
private IQueryable<T> ApplyFilter(IQueryable<T> entities)
91-
{
92-
if(Filter == null)
93-
return entities;
94-
95-
var expression = GetEqualityExpressionForProperty(entities,
96-
Filter.FilteredAttribute.InternalAttributeName, Filter.PropertyValue);
97-
98-
return entities.Where(expression);
99-
}
100-
101-
private IQueryable<T> ApplySort(IQueryable<T> entities)
102-
{
103-
if(SortParameters == null || SortParameters.Count == 0)
104-
return entities;
105-
106-
SortParameters.ForEach(sortParam => {
107-
if(sortParam.Direction == SortDirection.Ascending)
108-
entities = entities.OrderBy(sortParam.SortedAttribute.InternalAttributeName);
109-
else
110-
entities = entities.OrderByDescending(sortParam.SortedAttribute.InternalAttributeName);
111-
});
112-
113-
return entities;
114-
}
115-
116-
private Expression<Func<T, bool>> GetEqualityExpressionForProperty(IQueryable<T> query, string param, object value)
117-
{
118-
var currentType = query.ElementType;
119-
var property = currentType.GetProperty(param);
120-
121-
if (property == null)
122-
throw new ArgumentException($"'{param}' is not a valid property of '{currentType}'");
123-
124-
// convert the incoming value to the target value type
125-
// "1" -> 1
126-
var convertedValue = Convert.ChangeType(value, property.PropertyType);
127-
// {model}
128-
var prm = Expression.Parameter(currentType, "model");
129-
// {model.Id}
130-
var left = Expression.PropertyOrField(prm, property.Name);
131-
// {1}
132-
var right = Expression.Constant(convertedValue, property.PropertyType);
133-
// {model.Id == 1}
134-
var body = Expression.Equal(left, right);
135-
136-
return Expression.Lambda<Func<T, bool>>(body, prm);
137-
}
13878
}
13979
}

0 commit comments

Comments
 (0)