@@ -37,6 +37,7 @@ public class SelectQuery : TranslatedQuery
37
37
{
38
38
// private fields
39
39
private LambdaExpression _where ;
40
+ private Type _ofType ;
40
41
private List < OrderByClause > _orderBy ;
41
42
private LambdaExpression _projection ;
42
43
private Expression _skip ;
@@ -56,6 +57,14 @@ public SelectQuery(MongoCollection collection, Type documentType)
56
57
}
57
58
58
59
// public properties
60
+ /// <summary>
61
+ /// Gets the final result type if an OfType query operator was used (otherwise null).
62
+ /// </summary>
63
+ public Type OfType
64
+ {
65
+ get { return _ofType ; }
66
+ }
67
+
59
68
/// <summary>
60
69
/// Gets a list of Expressions that defines the sort order (or null if not specified).
61
70
/// </summary>
@@ -157,6 +166,21 @@ public override object Execute()
157
166
cursor . SetLimit ( ToInt32 ( _take ) ) ;
158
167
}
159
168
169
+ if ( _ofType != null )
170
+ {
171
+ if ( _projection == null )
172
+ {
173
+ var paramExpression = Expression . Parameter ( DocumentType , "x" ) ;
174
+ var convertExpression = Expression . Convert ( paramExpression , _ofType ) ;
175
+ _projection = Expression . Lambda ( convertExpression , paramExpression ) ;
176
+ }
177
+ else
178
+ {
179
+ // TODO: handle projection after OfType
180
+ throw new NotSupportedException ( ) ;
181
+ }
182
+ }
183
+
160
184
IEnumerable enumerable ;
161
185
if ( _projection == null )
162
186
{
@@ -208,6 +232,7 @@ public void Translate(Expression expression)
208
232
}
209
233
210
234
var message = string . Format ( "Don't know how to translate expression: {0}." , ExpressionFormatter . ToString ( expression ) ) ;
235
+ throw new NotSupportedException ( message ) ;
211
236
}
212
237
213
238
// private methods
@@ -1239,10 +1264,39 @@ private void CombinePredicateWithWhereClause(MethodCallExpression methodCallExpr
1239
1264
return ;
1240
1265
}
1241
1266
1267
+ if ( _where . Parameters . Count != 1 )
1268
+ {
1269
+ throw new MongoInternalException ( "Where lambda expression should have one parameter." ) ;
1270
+ }
1242
1271
var whereBody = _where . Body ;
1243
- var predicateBody = ExpressionParameterReplacer . ReplaceParameter ( predicate . Body , predicate . Parameters [ 0 ] , _where . Parameters [ 0 ] ) ;
1272
+ var whereParameter = _where . Parameters [ 0 ] ;
1273
+
1274
+ if ( predicate . Parameters . Count != 1 )
1275
+ {
1276
+ throw new MongoInternalException ( "Predicate lambda expression should have one parameter." ) ;
1277
+ }
1278
+ var predicateBody = predicate . Body ;
1279
+ var predicateParameter = predicate . Parameters [ 0 ] ;
1280
+
1281
+ // when using OfType the parameter types might not match (but they do have to be compatible)
1282
+ ParameterExpression parameter ;
1283
+ if ( predicateParameter . Type . IsAssignableFrom ( whereParameter . Type ) )
1284
+ {
1285
+ predicateBody = ExpressionParameterReplacer . ReplaceParameter ( predicateBody , predicateParameter , whereParameter ) ;
1286
+ parameter = whereParameter ;
1287
+ }
1288
+ else if ( whereParameter . Type . IsAssignableFrom ( predicateParameter . Type ) )
1289
+ {
1290
+ whereBody = ExpressionParameterReplacer . ReplaceParameter ( whereBody , whereParameter , predicateParameter ) ;
1291
+ parameter = predicateParameter ;
1292
+ }
1293
+ else
1294
+ {
1295
+ throw new NotSupportedException ( "Can't combine existing where clause with new predicate because parameter types are incompatible." ) ;
1296
+ }
1297
+
1244
1298
var combinedBody = Expression . AndAlso ( whereBody , predicateBody ) ;
1245
- _where = Expression . Lambda ( combinedBody , _where . Parameters . ToArray ( ) ) ;
1299
+ _where = Expression . Lambda ( combinedBody , parameter ) ;
1246
1300
}
1247
1301
}
1248
1302
@@ -1278,7 +1332,9 @@ private object ExecuteDistinct(IMongoQuery query)
1278
1332
1279
1333
private BsonSerializationInfo GetSerializationInfo ( Expression expression )
1280
1334
{
1281
- var documentSerializer = BsonSerializer . LookupSerializer ( DocumentType ) ;
1335
+ // when using OfType the documentType used by the parameter might be a subclass of the DocumentType from the collection
1336
+ var parameterExpression = ExpressionParameterFinder . FindParameter ( expression ) ;
1337
+ var documentSerializer = BsonSerializer . LookupSerializer ( parameterExpression . Type ) ;
1282
1338
return GetSerializationInfo ( documentSerializer , expression ) ;
1283
1339
}
1284
1340
@@ -1755,6 +1811,9 @@ private void TranslateMethodCall(MethodCallExpression methodCallExpression)
1755
1811
case "Min" :
1756
1812
TranslateMaxMin ( methodCallExpression ) ;
1757
1813
break ;
1814
+ case "OfType" :
1815
+ TranslateOfType ( methodCallExpression ) ;
1816
+ break ;
1758
1817
case "OrderBy" :
1759
1818
case "OrderByDescending" :
1760
1819
TranslateOrderBy ( methodCallExpression ) ;
@@ -1781,6 +1840,63 @@ private void TranslateMethodCall(MethodCallExpression methodCallExpression)
1781
1840
}
1782
1841
}
1783
1842
1843
+ private void TranslateOfType ( MethodCallExpression methodCallExpression )
1844
+ {
1845
+ var method = methodCallExpression . Method ;
1846
+ if ( method . DeclaringType != typeof ( Queryable ) )
1847
+ {
1848
+ var message = string . Format ( "OfType method of class {0} is not supported." , BsonUtils . GetFriendlyTypeName ( method . DeclaringType ) ) ;
1849
+ throw new NotSupportedException ( message ) ;
1850
+ }
1851
+ if ( ! method . IsStatic )
1852
+ {
1853
+ throw new NotSupportedException ( "Expected OfType to be a static method." ) ;
1854
+ }
1855
+ if ( ! method . IsGenericMethod )
1856
+ {
1857
+ throw new NotSupportedException ( "Expected OfType to be a generic method." ) ;
1858
+ }
1859
+ var actualType = method . GetGenericArguments ( ) [ 0 ] ;
1860
+
1861
+ var args = methodCallExpression . Arguments . ToArray ( ) ;
1862
+ if ( args . Length != 1 )
1863
+ {
1864
+ throw new NotSupportedException ( "Expected OfType method to have a single argument." ) ;
1865
+ }
1866
+ var sourceExpression = args [ 0 ] ;
1867
+ if ( ! sourceExpression . Type . IsGenericType )
1868
+ {
1869
+ throw new NotSupportedException ( "Expected source argument to OfType to be a generic type." ) ;
1870
+ }
1871
+ var nominalType = sourceExpression . Type . GetGenericArguments ( ) [ 0 ] ;
1872
+
1873
+ if ( nominalType == actualType )
1874
+ {
1875
+ return ; // nothing to do
1876
+ }
1877
+
1878
+ if ( _projection != null )
1879
+ {
1880
+ throw new NotSupportedException ( "OfType after a projection is not supported." ) ;
1881
+ }
1882
+
1883
+ var discriminatorConvention = BsonDefaultSerializer . LookupDiscriminatorConvention ( nominalType ) ;
1884
+ var discriminator = discriminatorConvention . GetDiscriminator ( nominalType , actualType ) ;
1885
+ if ( discriminator . IsBsonArray )
1886
+ {
1887
+ discriminator = discriminator . AsBsonArray [ discriminator . AsBsonArray . Count - 1 ] ;
1888
+ }
1889
+
1890
+ var injectMethodInfo = typeof ( LinqToMongo ) . GetMethod ( "Inject" ) ;
1891
+ var query = Query . EQ ( "_t" , discriminator ) ;
1892
+ var body = Expression . Call ( injectMethodInfo , Expression . Constant ( query ) ) ;
1893
+ var parameter = Expression . Parameter ( nominalType , "x" ) ;
1894
+ var predicate = Expression . Lambda ( body , parameter ) ;
1895
+ CombinePredicateWithWhereClause ( methodCallExpression , predicate ) ;
1896
+
1897
+ _ofType = actualType ;
1898
+ }
1899
+
1784
1900
private void TranslateOrderBy ( MethodCallExpression methodCallExpression )
1785
1901
{
1786
1902
if ( methodCallExpression . Arguments . Count != 2 )
0 commit comments