ProjectionDefinitionBuilder.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. /* Copyright 2010-present MongoDB Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Linq;
  18. using System.Linq.Expressions;
  19. using MongoDB.Bson;
  20. using MongoDB.Bson.Serialization;
  21. using MongoDB.Driver.Core.Misc;
  22. namespace MongoDB.Driver
  23. {
  24. /// <summary>
  25. /// Extension methods for projections.
  26. /// </summary>
  27. public static class ProjectionDefinitionExtensions
  28. {
  29. /// <summary>
  30. /// Combines an existing projection with a projection that filters the contents of an array.
  31. /// </summary>
  32. /// <typeparam name="TDocument">The type of the document.</typeparam>
  33. /// <typeparam name="TItem">The type of the item.</typeparam>
  34. /// <param name="projection">The projection.</param>
  35. /// <param name="field">The field.</param>
  36. /// <param name="filter">The filter.</param>
  37. /// <returns>
  38. /// A combined projection.
  39. /// </returns>
  40. public static ProjectionDefinition<TDocument> ElemMatch<TDocument, TItem>(this ProjectionDefinition<TDocument> projection, FieldDefinition<TDocument> field, FilterDefinition<TItem> filter)
  41. {
  42. var builder = Builders<TDocument>.Projection;
  43. return builder.Combine(projection, builder.ElemMatch(field, filter));
  44. }
  45. /// <summary>
  46. /// Combines an existing projection with a projection that filters the contents of an array.
  47. /// </summary>
  48. /// <typeparam name="TDocument">The type of the document.</typeparam>
  49. /// <typeparam name="TItem">The type of the item.</typeparam>
  50. /// <param name="projection">The projection.</param>
  51. /// <param name="field">The field.</param>
  52. /// <param name="filter">The filter.</param>
  53. /// <returns>
  54. /// A combined projection.
  55. /// </returns>
  56. public static ProjectionDefinition<TDocument> ElemMatch<TDocument, TItem>(this ProjectionDefinition<TDocument> projection, Expression<Func<TDocument, IEnumerable<TItem>>> field, FilterDefinition<TItem> filter)
  57. {
  58. var builder = Builders<TDocument>.Projection;
  59. return builder.Combine(projection, builder.ElemMatch(field, filter));
  60. }
  61. /// <summary>
  62. /// Combines an existing projection with a projection that filters the contents of an array.
  63. /// </summary>
  64. /// <typeparam name="TDocument">The type of the document.</typeparam>
  65. /// <typeparam name="TItem">The type of the item.</typeparam>
  66. /// <param name="projection">The projection.</param>
  67. /// <param name="field">The field.</param>
  68. /// <param name="filter">The filter.</param>
  69. /// <returns>
  70. /// A combined projection.
  71. /// </returns>
  72. public static ProjectionDefinition<TDocument> ElemMatch<TDocument, TItem>(this ProjectionDefinition<TDocument> projection, Expression<Func<TDocument, IEnumerable<TItem>>> field, Expression<Func<TItem, bool>> filter)
  73. {
  74. var builder = Builders<TDocument>.Projection;
  75. return builder.Combine(projection, builder.ElemMatch(field, filter));
  76. }
  77. /// <summary>
  78. /// Combines an existing projection with a projection that excludes a field.
  79. /// </summary>
  80. /// <typeparam name="TDocument">The type of the document.</typeparam>
  81. /// <param name="projection">The projection.</param>
  82. /// <param name="field">The field.</param>
  83. /// <returns>
  84. /// A combined projection.
  85. /// </returns>
  86. public static ProjectionDefinition<TDocument> Exclude<TDocument>(this ProjectionDefinition<TDocument> projection, FieldDefinition<TDocument> field)
  87. {
  88. var builder = Builders<TDocument>.Projection;
  89. return builder.Combine(projection, builder.Exclude(field));
  90. }
  91. /// <summary>
  92. /// Combines an existing projection with a projection that excludes a field.
  93. /// </summary>
  94. /// <typeparam name="TDocument">The type of the document.</typeparam>
  95. /// <param name="projection">The projection.</param>
  96. /// <param name="field">The field.</param>
  97. /// <returns>
  98. /// A combined projection.
  99. /// </returns>
  100. public static ProjectionDefinition<TDocument> Exclude<TDocument>(this ProjectionDefinition<TDocument> projection, Expression<Func<TDocument, object>> field)
  101. {
  102. var builder = Builders<TDocument>.Projection;
  103. return builder.Combine(projection, builder.Exclude(field));
  104. }
  105. /// <summary>
  106. /// Combines an existing projection with a projection that includes a field.
  107. /// </summary>
  108. /// <typeparam name="TDocument">The type of the document.</typeparam>
  109. /// <param name="projection">The projection.</param>
  110. /// <param name="field">The field.</param>
  111. /// <returns>
  112. /// A combined projection.
  113. /// </returns>
  114. public static ProjectionDefinition<TDocument> Include<TDocument>(this ProjectionDefinition<TDocument> projection, FieldDefinition<TDocument> field)
  115. {
  116. var builder = Builders<TDocument>.Projection;
  117. return builder.Combine(projection, builder.Include(field));
  118. }
  119. /// <summary>
  120. /// Combines an existing projection with a projection that includes a field.
  121. /// </summary>
  122. /// <typeparam name="TDocument">The type of the document.</typeparam>
  123. /// <param name="projection">The projection.</param>
  124. /// <param name="field">The field.</param>
  125. /// <returns>
  126. /// A combined projection.
  127. /// </returns>
  128. public static ProjectionDefinition<TDocument> Include<TDocument>(this ProjectionDefinition<TDocument> projection, Expression<Func<TDocument, object>> field)
  129. {
  130. var builder = Builders<TDocument>.Projection;
  131. return builder.Combine(projection, builder.Include(field));
  132. }
  133. /// <summary>
  134. /// Combines an existing projection with a text score projection.
  135. /// </summary>
  136. /// <typeparam name="TDocument">The type of the document.</typeparam>
  137. /// <param name="projection">The projection.</param>
  138. /// <param name="field">The field.</param>
  139. /// <returns>
  140. /// A combined projection.
  141. /// </returns>
  142. public static ProjectionDefinition<TDocument> MetaTextScore<TDocument>(this ProjectionDefinition<TDocument> projection, string field)
  143. {
  144. var builder = Builders<TDocument>.Projection;
  145. return builder.Combine(projection, builder.MetaTextScore(field));
  146. }
  147. /// <summary>
  148. /// Combines an existing projection with an array slice projection.
  149. /// </summary>
  150. /// <typeparam name="TDocument">The type of the document.</typeparam>
  151. /// <param name="projection">The projection.</param>
  152. /// <param name="field">The field.</param>
  153. /// <param name="skip">The skip.</param>
  154. /// <param name="limit">The limit.</param>
  155. /// <returns>
  156. /// A combined projection.
  157. /// </returns>
  158. public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDefinition<TDocument> projection, FieldDefinition<TDocument> field, int skip, int? limit = null)
  159. {
  160. var builder = Builders<TDocument>.Projection;
  161. return builder.Combine(projection, builder.Slice(field, skip, limit));
  162. }
  163. /// <summary>
  164. /// Combines an existing projection with an array slice projection.
  165. /// </summary>
  166. /// <typeparam name="TDocument">The type of the document.</typeparam>
  167. /// <param name="projection">The projection.</param>
  168. /// <param name="field">The field.</param>
  169. /// <param name="skip">The skip.</param>
  170. /// <param name="limit">The limit.</param>
  171. /// <returns>
  172. /// A combined projection.
  173. /// </returns>
  174. public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDefinition<TDocument> projection, Expression<Func<TDocument, object>> field, int skip, int? limit = null)
  175. {
  176. var builder = Builders<TDocument>.Projection;
  177. return builder.Combine(projection, builder.Slice(field, skip, limit));
  178. }
  179. }
  180. /// <summary>
  181. /// A builder for a projection.
  182. /// </summary>
  183. /// <typeparam name="TSource">The type of the source.</typeparam>
  184. public sealed class ProjectionDefinitionBuilder<TSource>
  185. {
  186. /// <summary>
  187. /// Creates a client side projection that is implemented solely by using a different serializer.
  188. /// </summary>
  189. /// <typeparam name="TProjection">The type of the projection.</typeparam>
  190. /// <param name="projectionSerializer">The projection serializer.</param>
  191. /// <returns>A client side deserialization projection.</returns>
  192. public ProjectionDefinition<TSource, TProjection> As<TProjection>(IBsonSerializer<TProjection> projectionSerializer = null)
  193. {
  194. return new ClientSideDeserializationProjectionDefinition<TSource, TProjection>(projectionSerializer);
  195. }
  196. /// <summary>
  197. /// Combines the specified projections.
  198. /// </summary>
  199. /// <param name="projections">The projections.</param>
  200. /// <returns>
  201. /// A combined projection.
  202. /// </returns>
  203. public ProjectionDefinition<TSource> Combine(params ProjectionDefinition<TSource>[] projections)
  204. {
  205. return Combine((IEnumerable<ProjectionDefinition<TSource>>)projections);
  206. }
  207. /// <summary>
  208. /// Combines the specified projections.
  209. /// </summary>
  210. /// <param name="projections">The projections.</param>
  211. /// <returns>
  212. /// A combined projection.
  213. /// </returns>
  214. public ProjectionDefinition<TSource> Combine(IEnumerable<ProjectionDefinition<TSource>> projections)
  215. {
  216. return new CombinedProjectionDefinition<TSource>(projections);
  217. }
  218. /// <summary>
  219. /// Creates a projection that filters the contents of an array.
  220. /// </summary>
  221. /// <typeparam name="TItem">The type of the item.</typeparam>
  222. /// <param name="field">The field.</param>
  223. /// <param name="filter">The filter.</param>
  224. /// <returns>
  225. /// An array filtering projection.
  226. /// </returns>
  227. public ProjectionDefinition<TSource> ElemMatch<TItem>(FieldDefinition<TSource> field, FilterDefinition<TItem> filter)
  228. {
  229. return new ElementMatchProjectionDefinition<TSource, TItem>(field, filter);
  230. }
  231. /// <summary>
  232. /// Creates a projection that filters the contents of an array.
  233. /// </summary>
  234. /// <typeparam name="TItem">The type of the item.</typeparam>
  235. /// <param name="field">The field.</param>
  236. /// <param name="filter">The filter.</param>
  237. /// <returns>
  238. /// An array filtering projection.
  239. /// </returns>
  240. public ProjectionDefinition<TSource> ElemMatch<TItem>(Expression<Func<TSource, IEnumerable<TItem>>> field, FilterDefinition<TItem> filter)
  241. {
  242. return ElemMatch(new ExpressionFieldDefinition<TSource>(field), filter);
  243. }
  244. /// <summary>
  245. /// Creates a projection that filters the contents of an array.
  246. /// </summary>
  247. /// <typeparam name="TItem">The type of the item.</typeparam>
  248. /// <param name="field">The field.</param>
  249. /// <param name="filter">The filter.</param>
  250. /// <returns>
  251. /// An array filtering projection.
  252. /// </returns>
  253. public ProjectionDefinition<TSource> ElemMatch<TItem>(Expression<Func<TSource, IEnumerable<TItem>>> field, Expression<Func<TItem, bool>> filter)
  254. {
  255. return ElemMatch(new ExpressionFieldDefinition<TSource>(field), new ExpressionFilterDefinition<TItem>(filter));
  256. }
  257. /// <summary>
  258. /// Creates a projection that excludes a field.
  259. /// </summary>
  260. /// <param name="field">The field.</param>
  261. /// <returns>
  262. /// An exclusion projection.
  263. /// </returns>
  264. public ProjectionDefinition<TSource> Exclude(FieldDefinition<TSource> field)
  265. {
  266. return new SingleFieldProjectionDefinition<TSource>(field, 0);
  267. }
  268. /// <summary>
  269. /// Creates a projection that excludes a field.
  270. /// </summary>
  271. /// <param name="field">The field.</param>
  272. /// <returns>
  273. /// An exclusion projection.
  274. /// </returns>
  275. public ProjectionDefinition<TSource> Exclude(Expression<Func<TSource, object>> field)
  276. {
  277. return Exclude(new ExpressionFieldDefinition<TSource>(field));
  278. }
  279. /// <summary>
  280. /// Creates a projection based on the expression.
  281. /// </summary>
  282. /// <typeparam name="TProjection">The type of the result.</typeparam>
  283. /// <param name="expression">The expression.</param>
  284. /// <returns>
  285. /// An expression projection.
  286. /// </returns>
  287. public ProjectionDefinition<TSource, TProjection> Expression<TProjection>(Expression<Func<TSource, TProjection>> expression)
  288. {
  289. return new FindExpressionProjectionDefinition<TSource, TProjection>(expression);
  290. }
  291. /// <summary>
  292. /// Creates a projection that includes a field.
  293. /// </summary>
  294. /// <param name="field">The field.</param>
  295. /// <returns>
  296. /// An inclusion projection.
  297. /// </returns>
  298. public ProjectionDefinition<TSource> Include(FieldDefinition<TSource> field)
  299. {
  300. return new SingleFieldProjectionDefinition<TSource>(field, 1);
  301. }
  302. /// <summary>
  303. /// Creates a projection that includes a field.
  304. /// </summary>
  305. /// <param name="field">The field.</param>
  306. /// <returns>
  307. /// An inclusion projection.
  308. /// </returns>
  309. public ProjectionDefinition<TSource> Include(Expression<Func<TSource, object>> field)
  310. {
  311. return Include(new ExpressionFieldDefinition<TSource>(field));
  312. }
  313. /// <summary>
  314. /// Creates a text score projection.
  315. /// </summary>
  316. /// <param name="field">The field.</param>
  317. /// <returns>
  318. /// A text score projection.
  319. /// </returns>
  320. public ProjectionDefinition<TSource> MetaTextScore(string field)
  321. {
  322. return new SingleFieldProjectionDefinition<TSource>(field, new BsonDocument("$meta", "textScore"));
  323. }
  324. /// <summary>
  325. /// Creates an array slice projection.
  326. /// </summary>
  327. /// <param name="field">The field.</param>
  328. /// <param name="skip">The skip.</param>
  329. /// <param name="limit">The limit.</param>
  330. /// <returns>
  331. /// An array slice projection.
  332. /// </returns>
  333. public ProjectionDefinition<TSource> Slice(FieldDefinition<TSource> field, int skip, int? limit = null)
  334. {
  335. var value = limit.HasValue ? (BsonValue)new BsonArray { skip, limit.Value } : skip;
  336. return new SingleFieldProjectionDefinition<TSource>(field, new BsonDocument("$slice", value));
  337. }
  338. /// <summary>
  339. /// Creates an array slice projection.
  340. /// </summary>
  341. /// <param name="field">The field.</param>
  342. /// <param name="skip">The skip.</param>
  343. /// <param name="limit">The limit.</param>
  344. /// <returns>
  345. /// An array slice projection.
  346. /// </returns>
  347. public ProjectionDefinition<TSource> Slice(Expression<Func<TSource, object>> field, int skip, int? limit = null)
  348. {
  349. return Slice(new ExpressionFieldDefinition<TSource>(field), skip, limit);
  350. }
  351. }
  352. internal sealed class CombinedProjectionDefinition<TSource> : ProjectionDefinition<TSource>
  353. {
  354. private readonly List<ProjectionDefinition<TSource>> _projections;
  355. public CombinedProjectionDefinition(IEnumerable<ProjectionDefinition<TSource>> projections)
  356. {
  357. _projections = Ensure.IsNotNull(projections, nameof(projections)).ToList();
  358. }
  359. public override BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry)
  360. {
  361. var document = new BsonDocument();
  362. foreach (var projection in _projections)
  363. {
  364. var renderedProjection = projection.Render(sourceSerializer, serializerRegistry);
  365. foreach (var element in renderedProjection.Elements)
  366. {
  367. // last one wins
  368. document.Remove(element.Name);
  369. document.Add(element);
  370. }
  371. }
  372. return document;
  373. }
  374. }
  375. internal sealed class ElementMatchProjectionDefinition<TSource, TItem> : ProjectionDefinition<TSource>
  376. {
  377. private readonly FieldDefinition<TSource> _field;
  378. private readonly FilterDefinition<TItem> _filter;
  379. public ElementMatchProjectionDefinition(FieldDefinition<TSource> field, FilterDefinition<TItem> filter)
  380. {
  381. _field = Ensure.IsNotNull(field, nameof(field));
  382. _filter = filter;
  383. }
  384. public override BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry)
  385. {
  386. var renderedField = _field.Render(sourceSerializer, serializerRegistry);
  387. IBsonSerializer<TItem> itemSerializer;
  388. if (renderedField.FieldSerializer != null)
  389. {
  390. var arraySerializer = renderedField.FieldSerializer as IBsonArraySerializer;
  391. BsonSerializationInfo itemSerializationInfo;
  392. if (arraySerializer == null || !arraySerializer.TryGetItemSerializationInfo(out itemSerializationInfo))
  393. {
  394. var message = string.Format("The serializer for field '{0}' must implement IBsonArraySerializer and provide item serialization info.", renderedField.FieldName);
  395. throw new InvalidOperationException(message);
  396. }
  397. itemSerializer = (IBsonSerializer<TItem>)itemSerializationInfo.Serializer;
  398. }
  399. else
  400. {
  401. itemSerializer = serializerRegistry.GetSerializer<TItem>();
  402. }
  403. var renderedFilter = _filter.Render(itemSerializer, serializerRegistry);
  404. return new BsonDocument(renderedField.FieldName, new BsonDocument("$elemMatch", renderedFilter));
  405. }
  406. }
  407. internal sealed class PositionalOperatorProjectionDefinition<TSource> : ProjectionDefinition<TSource>
  408. {
  409. private readonly FieldDefinition<TSource> _field;
  410. public PositionalOperatorProjectionDefinition(FieldDefinition<TSource> field)
  411. {
  412. _field = Ensure.IsNotNull(field, nameof(field));
  413. }
  414. public override BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry)
  415. {
  416. var renderedField = _field.Render(sourceSerializer, serializerRegistry);
  417. return new BsonDocument(renderedField.FieldName + ".$", 1);
  418. }
  419. }
  420. internal sealed class SingleFieldProjectionDefinition<TSource> : ProjectionDefinition<TSource>
  421. {
  422. private readonly FieldDefinition<TSource> _field;
  423. private readonly BsonValue _value;
  424. public SingleFieldProjectionDefinition(FieldDefinition<TSource> field, BsonValue value)
  425. {
  426. _field = Ensure.IsNotNull(field, nameof(field));
  427. _value = Ensure.IsNotNull(value, nameof(value));
  428. }
  429. public override BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry)
  430. {
  431. var renderedField = _field.Render(sourceSerializer, serializerRegistry);
  432. return new BsonDocument(renderedField.FieldName, _value);
  433. }
  434. }
  435. }