forked from beyond-code-github/LinqToQuerystring
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathExtensions.cs
More file actions
205 lines (170 loc) · 8.36 KB
/
Extensions.cs
File metadata and controls
205 lines (170 loc) · 8.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
namespace LinqToQuerystring.Core
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Antlr.Runtime;
using Antlr.Runtime.Tree;
using LinqToQuerystring.Core.TreeNodes;
using LinqToQuerystring.Core.TreeNodes.Base;
public static class Extensions
{
public static TResult LinqToQuerystring<T, TResult>(
this IQueryable<T> query, string queryString = "", bool forceDynamicProperties = false, int maxPageSize = -1) =>
(TResult)query.LinqToQuerystring(typeof(T), Context.GlobalContext, queryString, forceDynamicProperties, maxPageSize);
public static IQueryable<T> LinqToQuerystring<T>(
this IQueryable<T> query, string queryString = "", bool forceDynamicProperties = false, int maxPageSize = -1) =>
(IQueryable<T>)query.LinqToQuerystring(typeof(T), Context.GlobalContext, queryString, forceDynamicProperties, maxPageSize);
public static object LinqToQuerystring(
this IQueryable query, Type inputType, string queryString = "", bool forceDynamicProperties = false, int maxPageSize = -1) =>
query.LinqToQuerystring(inputType, Context.GlobalContext, queryString, forceDynamicProperties, maxPageSize);
public static TResult LinqToQuerystring<T, TResult>(this IQueryable<T> query, Context context, string queryString = "", bool forceDynamicProperties = false, int maxPageSize = -1)
{
return (TResult)LinqToQuerystring(query, typeof(T), context, queryString, forceDynamicProperties, maxPageSize);
}
public static IQueryable<T> LinqToQuerystring<T>(this IQueryable<T> query, Context context, string queryString = "", bool forceDynamicProperties = false, int maxPageSize = -1)
{
return (IQueryable<T>)LinqToQuerystring(query, typeof(T), context, queryString, forceDynamicProperties, maxPageSize);
}
public static object LinqToQuerystring(this IQueryable query, Type inputType, Context context, string queryString = "", bool forceDynamicProperties = false, int maxPageSize = -1)
{
var queryResult = query;
var constrainedQuery = query;
if (query == null)
{
throw new ArgumentNullException("query", "Query cannot be null");
}
if (queryString == null)
{
throw new ArgumentNullException("queryString", "Query String cannot be null");
}
if (queryString.StartsWith("?"))
{
queryString = queryString.Substring(1);
}
var odataQueries = queryString.Split('&').Where(o => o.StartsWith("$")).ToList();
if (maxPageSize > 0)
{
var top = odataQueries.FirstOrDefault(o => o.StartsWith("$top"));
if (top != null)
{
int pagesize;
if (!int.TryParse(top.Split('=')[1], out pagesize) || pagesize >= maxPageSize)
{
odataQueries.Remove(top);
odataQueries.Add("$top=" + maxPageSize);
}
}
else
{
odataQueries.Add("$top=" + maxPageSize);
}
}
var odataQuerystring = Uri.UnescapeDataString(string.Join("&", odataQueries.ToArray()));
var input = new ANTLRReaderStream(new StringReader(odataQuerystring));
var lexer = new LinqToQuerystringLexer(input);
var tokStream = new CommonTokenStream(lexer);
var parser = new LinqToQuerystringParser(tokStream) { TreeAdaptor = new TreeNodeFactory(inputType, context, forceDynamicProperties) };
var result = parser.prog();
var singleNode = result.Tree as TreeNode;
if (singleNode != null && !(singleNode is IdentifierNode))
{
if (!(singleNode is SelectNode) && !(singleNode is InlineCountNode))
{
BuildQuery(singleNode, ref queryResult, ref constrainedQuery, context);
return constrainedQuery;
}
if (singleNode is SelectNode)
{
return ProjectQuery(constrainedQuery, singleNode);
}
return PackageResults(queryResult, constrainedQuery);
}
var tree = result.Tree as CommonTree;
if (tree != null)
{
var children = tree.Children.Cast<TreeNode>().ToList();
children.Sort();
// These should always come first
foreach (var node in children.Where(o => !(o is SelectNode) && !(o is InlineCountNode)))
{
BuildQuery(node, ref queryResult, ref constrainedQuery, context);
}
var selectNode = children.FirstOrDefault(o => o is SelectNode);
if (selectNode != null)
{
constrainedQuery = ProjectQuery(constrainedQuery, selectNode);
}
var inlineCountNode = children.FirstOrDefault(o => o is InlineCountNode);
if (inlineCountNode != null)
{
return PackageResults(queryResult, constrainedQuery);
}
}
return constrainedQuery;
}
private static void BuildQuery(TreeNode node, ref IQueryable queryResult, ref IQueryable constrainedQuery, Context context)
{
var type = queryResult.Provider.GetType().Name;
var mappings = (!string.IsNullOrEmpty(type) && context.CustomNodes.ContainsKey(type))
? context.CustomNodes[type]
: null;
if (mappings != null)
{
node = mappings.MapNode(node, queryResult.Expression);
}
if (!(node is TopNode) && !(node is SkipNode))
{
var modifier = node as QueryModifier;
if (modifier != null)
{
queryResult = modifier.ModifyQuery(queryResult);
}
else
{
queryResult = queryResult.Provider.CreateQuery(
node.BuildLinqExpression(queryResult, queryResult.Expression));
}
}
var queryModifier = node as QueryModifier;
if (queryModifier != null)
{
constrainedQuery = queryModifier.ModifyQuery(constrainedQuery);
}
else
{
constrainedQuery =
constrainedQuery.Provider.CreateQuery(
node.BuildLinqExpression(constrainedQuery, constrainedQuery.Expression));
}
}
private static IQueryable ProjectQuery(IQueryable constrainedQuery, TreeNode node)
{
// TODO: Find a solution to the following:
// Currently the only way to perform the SELECT part of the query is to call ToList and then project onto a dictionary. Two main problems:
// 1. Linq to Entities does not support projection onto list initialisers with more than one value
// 2. We cannot build an anonymous type using expression trees as there is compiler magic that must happen.
// There is a solution involving reflection.emit, but is it worth it? Not sure...
var result = constrainedQuery.GetEnumeratedQuery().AsQueryable();
return
result.Provider.CreateQuery<Dictionary<string, object>>(
node.BuildLinqExpression(result, result.Expression));
}
private static object PackageResults(IQueryable query, IQueryable constrainedQuery)
{
var result = query.GetEnumeratedQuery();
return new Dictionary<string, object> { { "Count", result.Count() }, { "Results", constrainedQuery } };
}
public static IEnumerable<object> GetEnumeratedQuery(this IQueryable query)
{
return Iterate(query.GetEnumerator()).Cast<object>().ToList();
}
static IEnumerable Iterate(this IEnumerator iterator)
{
while (iterator.MoveNext())
yield return iterator.Current;
}
}
}