11using Community . OData . Linq . Builder . Validators ;
2+ using Community . OData . Linq . Common ;
3+ using Community . OData . Linq . Properties ;
24
35namespace Community . OData . Linq
46{
@@ -21,56 +23,14 @@ namespace Community.OData.Linq
2123
2224 public static class ODataLinqExtensions
2325 {
24- private static readonly FilterQueryValidator Validator =
26+ private static readonly FilterQueryValidator FilterValidator =
2527 new FilterQueryValidator ( new DefaultQuerySettings { EnableFilter = true } ) ;
2628
2729 /// <summary>
2830 /// The simplified options.
2931 /// </summary>
3032 private static readonly ODataSimplifiedOptions SimplifiedOptions = new ODataSimplifiedOptions ( ) ;
3133
32- public static ODataQuery < T > Filter < T > ( this ODataQuery < T > query , string filterText , string entitySetName = null )
33- {
34- IEdmModel edmModel = query . EdmModel ;
35-
36- if ( entitySetName == null )
37- {
38- entitySetName = typeof ( T ) . Name ;
39- }
40-
41- IEdmEntityContainer container =
42- ( IEdmEntityContainer ) edmModel . SchemaElements . Single (
43- e => e . SchemaElementKind == EdmSchemaElementKind . EntityContainer ) ;
44-
45- IEdmEntitySet entitySet = container . FindEntitySet ( entitySetName ) ;
46- ODataPath path = new ODataPath ( new EntitySetSegment ( entitySet ) ) ;
47-
48- ODataQueryOptionParser queryOptionParser = new ODataQueryOptionParser (
49- edmModel ,
50- path ,
51- new Dictionary < string , string > { { "$filter" , filterText } } ,
52- query . ServiceProvider ) ;
53-
54- ODataSettings settings = query . ServiceProvider . GetRequiredService < ODataSettings > ( ) ;
55-
56- // Workaround for strange behavior in QueryOptionsParserConfiguration constructor which set it to false always
57- queryOptionParser . Resolver . EnableCaseInsensitive = settings . EnableCaseInsensitive ;
58-
59- FilterClause filterClause = queryOptionParser . ParseFilter ( ) ;
60- SingleValueNode filterExpression = filterClause . Expression . Accept (
61- new ParameterAliasNodeTranslator ( queryOptionParser . ParameterAliasNodes ) ) as SingleValueNode ;
62- filterExpression = filterExpression ?? new ConstantNode ( null ) ;
63- filterClause = new FilterClause ( filterExpression , filterClause . RangeVariable ) ;
64- Contract . Assert ( filterClause != null ) ;
65-
66- Validator . Validate ( filterClause , settings . ValidationSettings , edmModel ) ;
67-
68- Expression filter = FilterBinder . Bind ( query , filterClause , typeof ( T ) , query . ServiceProvider ) ;
69- var result = ExpressionHelpers . Where ( query , filter , typeof ( T ) ) ;
70-
71- return new ODataQuery < T > ( result , query . ServiceProvider ) ;
72- }
73-
7434 /// <summary>
7535 /// Enable applying OData specific functions to query
7636 /// </summary>
@@ -101,7 +61,7 @@ public static ODataQuery<T> OData<T>(this IQueryable<T> query, Action<ODataSetti
10161
10262 ODataSettings settings = new ODataSettings ( ) ;
10363 configuration ? . Invoke ( settings ) ;
104-
64+
10565 ServiceContainer container = new ServiceContainer ( ) ;
10666 container . AddService ( typeof ( IEdmModel ) , edmModel ) ;
10767 container . AddService ( typeof ( ODataQuerySettings ) , settings . QuerySettings ) ;
@@ -116,5 +76,174 @@ public static ODataQuery<T> OData<T>(this IQueryable<T> query, Action<ODataSetti
11676
11777 return dataQuery ;
11878 }
79+
80+ public static ODataQuery < T > Filter < T > ( this ODataQuery < T > query , string filterText , string entitySetName = null )
81+ {
82+ IEdmModel edmModel = query . EdmModel ;
83+
84+ ODataQueryOptionParser queryOptionParser = GetParser ( query , entitySetName ,
85+ new Dictionary < string , string > { { "$filter" , filterText } } ) ;
86+
87+ ODataSettings settings = query . ServiceProvider . GetRequiredService < ODataSettings > ( ) ;
88+
89+ // Workaround for strange behavior in QueryOptionsParserConfiguration constructor which set it to false always
90+ queryOptionParser . Resolver . EnableCaseInsensitive = settings . EnableCaseInsensitive ;
91+
92+ FilterClause filterClause = queryOptionParser . ParseFilter ( ) ;
93+ SingleValueNode filterExpression = filterClause . Expression . Accept (
94+ new ParameterAliasNodeTranslator ( queryOptionParser . ParameterAliasNodes ) ) as SingleValueNode ;
95+ filterExpression = filterExpression ?? new ConstantNode ( null ) ;
96+ filterClause = new FilterClause ( filterExpression , filterClause . RangeVariable ) ;
97+ Contract . Assert ( filterClause != null ) ;
98+
99+ FilterValidator . Validate ( filterClause , settings . ValidationSettings , edmModel ) ;
100+
101+ Expression filter = FilterBinder . Bind ( query , filterClause , typeof ( T ) , query . ServiceProvider ) ;
102+ var result = ExpressionHelpers . Where ( query , filter , typeof ( T ) ) ;
103+
104+ return new ODataQuery < T > ( result , query . ServiceProvider ) ;
105+ }
106+
107+ public static IOrderedQueryable < T > OrderBy < T > ( this ODataQuery < T > query , string orderbyText , string entitySetName = null )
108+ {
109+ IEdmModel edmModel = query . EdmModel ;
110+
111+ ODataQueryOptionParser queryOptionParser = GetParser ( query , entitySetName ,
112+ new Dictionary < string , string > { { "$orderby" , orderbyText } } ) ;
113+
114+ var orderByClause = queryOptionParser . ParseOrderBy ( ) ;
115+
116+ orderByClause = TranslateParameterAlias ( orderByClause , queryOptionParser ) ;
117+
118+ ODataSettings settings = query . ServiceProvider . GetRequiredService < ODataSettings > ( ) ;
119+
120+ IOrderedQueryable < T > result = ( IOrderedQueryable < T > ) OrderApplyToCore < T > ( query , settings . QuerySettings , orderByClause , edmModel ) ;
121+
122+ return new ODataQueryOrdered < T > ( result , query . ServiceProvider ) ;
123+ }
124+
125+ private static IOrderedQueryable OrderApplyToCore < T > ( ODataQuery < T > query , ODataQuerySettings querySettings , OrderByClause orderByClause , IEdmModel model )
126+ {
127+ ICollection < OrderByNode > nodes = OrderByNode . CreateCollection ( orderByClause ) ;
128+
129+ bool alreadyOrdered = false ;
130+ IQueryable querySoFar = query ;
131+
132+ HashSet < object > propertiesSoFar = new HashSet < object > ( ) ;
133+ HashSet < string > openPropertiesSoFar = new HashSet < string > ( ) ;
134+ bool orderByItSeen = false ;
135+
136+ foreach ( OrderByNode node in nodes )
137+ {
138+ OrderByPropertyNode propertyNode = node as OrderByPropertyNode ;
139+ OrderByOpenPropertyNode openPropertyNode = node as OrderByOpenPropertyNode ;
140+
141+ if ( propertyNode != null )
142+ {
143+ // Use autonomy class to achieve value equality for HasSet.
144+ var edmPropertyWithPath = new { propertyNode . Property , propertyNode . PropertyPath } ;
145+ OrderByDirection direction = propertyNode . Direction ;
146+
147+ // This check prevents queries with duplicate properties (e.g. $orderby=Id,Id,Id,Id...) from causing stack overflows
148+ if ( propertiesSoFar . Contains ( edmPropertyWithPath ) )
149+ {
150+ throw new ODataException ( Error . Format ( SRResources . OrderByDuplicateProperty , edmPropertyWithPath . PropertyPath ) ) ;
151+ }
152+
153+ propertiesSoFar . Add ( edmPropertyWithPath ) ;
154+
155+ if ( propertyNode . OrderByClause != null )
156+ {
157+ querySoFar = AddOrderByQueryForProperty ( query , querySettings , propertyNode . OrderByClause , querySoFar , direction , alreadyOrdered ) ;
158+ }
159+ else
160+ {
161+ querySoFar = ExpressionHelpers . OrderByProperty ( querySoFar , model , edmPropertyWithPath . Property , direction , typeof ( T ) , alreadyOrdered ) ;
162+ }
163+
164+ alreadyOrdered = true ;
165+ }
166+ else if ( openPropertyNode != null )
167+ {
168+ // This check prevents queries with duplicate properties (e.g. $orderby=Id,Id,Id,Id...) from causing stack overflows
169+ if ( openPropertiesSoFar . Contains ( openPropertyNode . PropertyName ) )
170+ {
171+ throw new ODataException ( Error . Format ( SRResources . OrderByDuplicateProperty , openPropertyNode . PropertyPath ) ) ;
172+ }
173+
174+ openPropertiesSoFar . Add ( openPropertyNode . PropertyName ) ;
175+ Contract . Assert ( openPropertyNode . OrderByClause != null ) ;
176+ querySoFar = AddOrderByQueryForProperty ( query , querySettings , openPropertyNode . OrderByClause , querySoFar , openPropertyNode . Direction , alreadyOrdered ) ;
177+ alreadyOrdered = true ;
178+ }
179+ else
180+ {
181+ // This check prevents queries with duplicate nodes (e.g. $orderby=$it,$it,$it,$it...) from causing stack overflows
182+ if ( orderByItSeen )
183+ {
184+ throw new ODataException ( Error . Format ( SRResources . OrderByDuplicateIt ) ) ;
185+ }
186+
187+ querySoFar = ExpressionHelpers . OrderByIt ( querySoFar , node . Direction , typeof ( T ) , alreadyOrdered ) ;
188+ alreadyOrdered = true ;
189+ orderByItSeen = true ;
190+ }
191+ }
192+
193+ return querySoFar as IOrderedQueryable ;
194+ }
195+
196+ private static IQueryable AddOrderByQueryForProperty < T > ( ODataQuery < T > query , ODataQuerySettings querySettings ,
197+ OrderByClause orderbyClause , IQueryable querySoFar , OrderByDirection direction , bool alreadyOrdered )
198+ {
199+ //Context.UpdateQuerySettings(querySettings, query);
200+
201+ LambdaExpression orderByExpression =
202+ FilterBinder . Bind ( query , orderbyClause , typeof ( T ) , query . ServiceProvider ) ;
203+ querySoFar = ExpressionHelpers . OrderBy ( querySoFar , orderByExpression , direction , typeof ( T ) ,
204+ alreadyOrdered ) ;
205+ return querySoFar ;
206+ }
207+
208+ private static OrderByClause TranslateParameterAlias ( OrderByClause orderBy , ODataQueryOptionParser queryOptionParser )
209+ {
210+ if ( orderBy == null )
211+ {
212+ return null ;
213+ }
214+
215+ SingleValueNode orderByExpression = orderBy . Expression . Accept (
216+ new ParameterAliasNodeTranslator ( queryOptionParser . ParameterAliasNodes ) ) as SingleValueNode ;
217+ orderByExpression = orderByExpression ?? new ConstantNode ( null , "null" ) ;
218+
219+ return new OrderByClause (
220+ TranslateParameterAlias ( orderBy . ThenBy , queryOptionParser ) ,
221+ orderByExpression ,
222+ orderBy . Direction ,
223+ orderBy . RangeVariable ) ;
224+ }
225+
226+ private static ODataQueryOptionParser GetParser < T > ( ODataQuery < T > query , string entitySetName , Dictionary < string , string > raws )
227+ {
228+ IEdmModel edmModel = query . EdmModel ;
229+
230+ if ( entitySetName == null )
231+ {
232+ entitySetName = typeof ( T ) . Name ;
233+ }
234+
235+ IEdmEntityContainer container =
236+ ( IEdmEntityContainer ) edmModel . SchemaElements . Single (
237+ e => e . SchemaElementKind == EdmSchemaElementKind . EntityContainer ) ;
238+
239+ IEdmEntitySet entitySet = container . FindEntitySet ( entitySetName ) ;
240+ ODataPath path = new ODataPath ( new EntitySetSegment ( entitySet ) ) ;
241+
242+ return new ODataQueryOptionParser (
243+ edmModel ,
244+ path ,
245+ raws ,
246+ query . ServiceProvider ) ;
247+ }
119248 }
120249}
0 commit comments