@@ -32,32 +32,31 @@ public override string GetQueryText(Expression expression)
32
32
return Translate ( expression ) ;
33
33
}
34
34
35
- public override object Execute ( Expression e , bool completeResponse )
35
+ public override object Execute ( Expression expression , bool completeResponse )
36
36
{
37
- MethodInfo _filterMethodInfo = null ;
38
- Expression [ ] _filteringExpressions = Array . Empty < Expression > ( ) ;
39
- if ( e is MethodCallExpression m )
37
+ // Remove from the expressions tree all IQueryable methods not supported by CouchDB and put them into the list
38
+ var unsupportedMethodCallExpressions = new List < MethodCallExpression > ( ) ;
39
+ expression = RemoveUnsupportedMethodExpressions ( expression , out var hasUnsupportedMethods , unsupportedMethodCallExpressions ) ;
40
+
41
+ var body = Translate ( expression ) ;
42
+ Type elementType = TypeSystem . GetElementType ( expression . Type ) ;
43
+
44
+ // Create generic GetCouchList method and invoke it, sending the request to CouchDB
45
+ MethodInfo method = typeof ( CouchQueryProvider ) . GetMethod ( nameof ( CouchQueryProvider . GetCouchList ) ) ;
46
+ MethodInfo generic = method . MakeGenericMethod ( elementType ) ;
47
+ var result = generic . Invoke ( this , new [ ] { body } ) ;
48
+
49
+ // If no unsupported methods, return the result
50
+ if ( ! hasUnsupportedMethods )
40
51
{
41
- if (
42
- m . Method . Name == "First" ||
43
- m . Method . Name == "FirstOrDefault" ||
44
- m . Method . Name == "Last" ||
45
- m . Method . Name == "LastOrDefault" ||
46
- m . Method . Name == "Single" ||
47
- m . Method . Name == "SingleOrDefault" )
48
- {
49
- _filterMethodInfo = m . Method ;
50
- _filteringExpressions = m . Arguments . Skip ( 1 ) . ToArray ( ) ;
51
- e = m . Arguments [ 0 ] ;
52
- }
52
+ return result ;
53
53
}
54
54
55
- var body = Translate ( e ) ;
56
- Type elementType = TypeSystem . GetElementType ( e . Type ) ;
57
-
58
- MethodInfo method = typeof ( CouchQueryProvider ) . GetMethod ( nameof ( CouchQueryProvider . GetCouchListOrFiltered ) ) ;
59
- MethodInfo generic = method . MakeGenericMethod ( elementType ) ;
60
- var result = generic . Invoke ( this , new [ ] { body , ( object ) _filterMethodInfo , _filteringExpressions } ) ;
55
+ // For every unsupported method expression, execute it on the result
56
+ foreach ( MethodCallExpression inMemoryCall in unsupportedMethodCallExpressions )
57
+ {
58
+ result = InvokeUnsupportedMethodCallExpression ( result , inMemoryCall ) ;
59
+ }
61
60
return result ;
62
61
}
63
62
@@ -69,9 +68,9 @@ private string Translate(Expression e)
69
68
70
69
return new QueryTranslator ( _settings ) . Translate ( e ) ;
71
70
}
72
-
73
- public object GetCouchListOrFiltered < T > ( string body , MethodInfo filteringMethodInfo , Expression [ ] filteringExpressions )
74
- {
71
+
72
+ public object GetCouchList < T > ( string body )
73
+ {
75
74
FindResult < T > result = _flurlClient
76
75
. Request ( _connectionString )
77
76
. AppendPathSegments ( _db , "_find" )
@@ -80,49 +79,84 @@ public object GetCouchListOrFiltered<T>(string body, MethodInfo filteringMethodI
80
79
. SendRequest ( ) ;
81
80
82
81
var couchList = new CouchList < T > ( result . Docs . ToList ( ) , result . Bookmark , result . ExecutionStats ) ;
82
+ return couchList ;
83
+ }
83
84
84
- if ( filteringMethodInfo == null )
85
+ private Expression RemoveUnsupportedMethodExpressions ( Expression expression , out bool hasUnsupportedMethods , IList < MethodCallExpression > unsupportedMethodCallExpressions )
86
+ {
87
+ if ( unsupportedMethodCallExpressions == null )
85
88
{
86
- return couchList ;
89
+ throw new ArgumentNullException ( nameof ( unsupportedMethodCallExpressions ) ) ;
87
90
}
88
91
89
- var filteringMethods = typeof ( Enumerable ) . GetMethods ( )
90
- . Where ( m =>
91
- m . Name == filteringMethodInfo . Name &&
92
- m . GetParameters ( ) . Length - 1 == filteringExpressions . Length )
93
- . OrderBy ( m => m . GetParameters ( ) . Length ) . ToList ( ) ;
94
-
95
-
96
- var invokeParameter = new object [ filteringExpressions . Length + 1 ] ;
97
- invokeParameter [ 0 ] = couchList ;
98
-
99
- bool IsRightOverload ( MethodInfo m )
92
+ // Search for method calls to run in-memory,
93
+ // Once one is found all method calls after that must run in-memory.
94
+ // The expression to translate in JSON ends with the last not in-memory call.
95
+ bool IsUnsupportedMethodCallExpression ( Expression ex )
100
96
{
101
- ParameterInfo [ ] parameters = m . GetParameters ( ) ;
102
- for ( var i = 0 ; i < filteringExpressions . Length ; i ++ )
97
+ if ( ex is MethodCallExpression m )
103
98
{
104
- var lamdaExpression = filteringExpressions [ i ] as UnaryExpression ;
105
- if ( lamdaExpression == null )
99
+ Expression nextCall = m . Arguments [ 0 ] ;
100
+ // Check if the next expression is unsupported
101
+ var isUnsupported = IsUnsupportedMethodCallExpression ( nextCall ) ;
102
+ if ( isUnsupported )
106
103
{
107
- return false ;
104
+ unsupportedMethodCallExpressions . Add ( m ) ;
105
+ return isUnsupported ;
108
106
}
109
-
110
- if ( lamdaExpression . Operand . Type != parameters [ i + 1 ] . ParameterType )
107
+ // If the next call is supported and the current is not in the supported list
108
+ if ( ! QueryTranslator . NativeQueryableMethods . Contains ( m . Method . Name ) )
111
109
{
112
- return false ;
110
+ unsupportedMethodCallExpressions . Add ( m ) ;
111
+ expression = nextCall ;
112
+ return true ;
113
113
}
114
- invokeParameter [ i + 1 ] = lamdaExpression . Operand ;
115
114
}
116
- return true ;
115
+ return false ;
117
116
}
118
117
119
- MethodInfo rightOverload = filteringMethods . Single ( IsRightOverload ) ;
120
-
121
- MethodInfo enumerableGenericFilteringMethod = rightOverload . MakeGenericMethod ( typeof ( T ) ) ;
122
-
118
+ hasUnsupportedMethods = IsUnsupportedMethodCallExpression ( expression ) ;
119
+ return expression ;
120
+ }
123
121
124
- var filtered = enumerableGenericFilteringMethod . Invoke ( null , invokeParameter ) ;
122
+ private object InvokeUnsupportedMethodCallExpression ( object result , MethodCallExpression methodCallExpression )
123
+ {
124
+ MethodInfo queryableMethodInfo = methodCallExpression . Method ;
125
+ Expression [ ] queryableMethodArguments = methodCallExpression . Arguments . ToArray ( ) ;
126
+ // Find the equivalent method in Enumerable
127
+ MethodInfo enumarableMethodInfo = typeof ( Enumerable ) . GetMethods ( ) . Single ( enumerableMethodInfo =>
128
+ {
129
+ return
130
+ queryableMethodInfo . Name == enumerableMethodInfo . Name &&
131
+ ReflectionComparator . IsCompatible ( queryableMethodInfo , enumerableMethodInfo ) ;
132
+ } ) ;
133
+
134
+ // Add the list as first parameter of the call
135
+ var invokeParameter = new List < object > { result } ;
136
+ // Convert everty other parameter expression to real values
137
+ IEnumerable < object > enumerableParameters = queryableMethodArguments . Skip ( 1 ) . Select ( GetArgumentValueFromExpression ) ;
138
+ // Add the other parameter to the complete list
139
+ invokeParameter . AddRange ( enumerableParameters ) ;
140
+
141
+ Type [ ] requestedGenericParameters = enumarableMethodInfo . GetGenericMethodDefinition ( ) . GetGenericArguments ( ) ;
142
+ Type [ ] genericParameters = queryableMethodInfo . GetGenericArguments ( ) ;
143
+ Type [ ] usableParameters = genericParameters . Take ( requestedGenericParameters . Length ) . ToArray ( ) ;
144
+ MethodInfo enumarableGenericMethod = enumarableMethodInfo . MakeGenericMethod ( usableParameters ) ;
145
+ var filtered = enumarableGenericMethod . Invoke ( null , invokeParameter . ToArray ( ) ) ;
125
146
return filtered ;
126
147
}
148
+
149
+ private object GetArgumentValueFromExpression ( Expression e )
150
+ {
151
+ if ( e is ConstantExpression c )
152
+ {
153
+ return c . Value ;
154
+ }
155
+ if ( e is UnaryExpression u && u . Operand is LambdaExpression l )
156
+ {
157
+ return l . Compile ( ) ;
158
+ }
159
+ throw new NotImplementedException ( $ "Expression of type { e . NodeType } not supported.") ;
160
+ }
127
161
}
128
162
}
0 commit comments