GridFSBucket.cs 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027
  1. /* Copyright 2016-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.Diagnostics.CodeAnalysis;
  18. using System.IO;
  19. using System.Linq;
  20. using System.Threading;
  21. using System.Threading.Tasks;
  22. using MongoDB.Bson;
  23. using MongoDB.Bson.IO;
  24. using MongoDB.Bson.Serialization;
  25. using MongoDB.Bson.Serialization.Serializers;
  26. using MongoDB.Driver.Core.Bindings;
  27. using MongoDB.Driver.Core.Clusters;
  28. using MongoDB.Driver.Core.Clusters.ServerSelectors;
  29. using MongoDB.Driver.Core.Misc;
  30. using MongoDB.Driver.Core.Operations;
  31. using MongoDB.Driver.Core.WireProtocol.Messages.Encoders;
  32. namespace MongoDB.Driver.GridFS
  33. {
  34. /// <summary>
  35. /// Represents a GridFS bucket.
  36. /// </summary>
  37. /// <typeparam name="TFileId">The type of the file identifier.</typeparam>
  38. [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] // we can get away with not calling Dispose on our SemaphoreSlim
  39. public class GridFSBucket<TFileId> : IGridFSBucket<TFileId>
  40. {
  41. // fields
  42. private readonly ICluster _cluster;
  43. private readonly IMongoDatabase _database;
  44. private bool _ensureIndexesDone;
  45. private SemaphoreSlim _ensureIndexesSemaphore = new SemaphoreSlim(1);
  46. private readonly IBsonSerializer<GridFSFileInfo<TFileId>> _fileInfoSerializer;
  47. private readonly BsonSerializationInfo _idSerializationInfo;
  48. private readonly ImmutableGridFSBucketOptions _options;
  49. // constructors
  50. /// <summary>
  51. /// Initializes a new instance of the <see cref="GridFSBucket" /> class.
  52. /// </summary>
  53. /// <param name="database">The database.</param>
  54. /// <param name="options">The options.</param>
  55. public GridFSBucket(IMongoDatabase database, GridFSBucketOptions options = null)
  56. {
  57. _database = Ensure.IsNotNull(database, nameof(database));
  58. _options = options == null ? ImmutableGridFSBucketOptions.Defaults : new ImmutableGridFSBucketOptions(options);
  59. _cluster = database.Client.Cluster;
  60. var idSerializer = _options.SerializerRegistry.GetSerializer<TFileId>();
  61. _idSerializationInfo = new BsonSerializationInfo("_id", idSerializer, typeof(TFileId));
  62. _fileInfoSerializer = new GridFSFileInfoSerializer<TFileId>(idSerializer);
  63. }
  64. // properties
  65. /// <inheritdoc />
  66. public IMongoDatabase Database
  67. {
  68. get { return _database; }
  69. }
  70. /// <inheritdoc />
  71. public ImmutableGridFSBucketOptions Options
  72. {
  73. get { return _options; }
  74. }
  75. // methods
  76. /// <inheritdoc />
  77. public void Delete(TFileId id, CancellationToken cancellationToken = default(CancellationToken))
  78. {
  79. Ensure.IsNotNull((object)id, nameof(id));
  80. using (var binding = GetSingleServerReadWriteBinding(cancellationToken))
  81. {
  82. var filesCollectionDeleteOperation = CreateDeleteFileOperation(id);
  83. var filesCollectionDeleteResult = filesCollectionDeleteOperation.Execute(binding, cancellationToken);
  84. var chunksDeleteOperation = CreateDeleteChunksOperation(id);
  85. chunksDeleteOperation.Execute(binding, cancellationToken);
  86. if (filesCollectionDeleteResult.DeletedCount == 0)
  87. {
  88. throw new GridFSFileNotFoundException(_idSerializationInfo.SerializeValue(id));
  89. }
  90. }
  91. }
  92. /// <inheritdoc />
  93. public async Task DeleteAsync(TFileId id, CancellationToken cancellationToken = default(CancellationToken))
  94. {
  95. Ensure.IsNotNull((object)id, nameof(id));
  96. using (var binding = await GetSingleServerReadWriteBindingAsync(cancellationToken).ConfigureAwait(false))
  97. {
  98. var filesCollectionDeleteOperation = CreateDeleteFileOperation(id);
  99. var filesCollectionDeleteResult = await filesCollectionDeleteOperation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false);
  100. var chunksDeleteOperation = CreateDeleteChunksOperation(id);
  101. await chunksDeleteOperation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false);
  102. if (filesCollectionDeleteResult.DeletedCount == 0)
  103. {
  104. throw new GridFSFileNotFoundException(_idSerializationInfo.SerializeValue(id));
  105. }
  106. }
  107. }
  108. /// <inheritdoc />
  109. public byte[] DownloadAsBytes(TFileId id, GridFSDownloadOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  110. {
  111. Ensure.IsNotNull((object)id, nameof(id));
  112. options = options ?? new GridFSDownloadOptions();
  113. using (var binding = GetSingleServerReadBinding(cancellationToken))
  114. {
  115. var fileInfo = GetFileInfo(binding, id, cancellationToken);
  116. return DownloadAsBytesHelper(binding, fileInfo, options, cancellationToken);
  117. }
  118. }
  119. /// <inheritdoc />
  120. public async Task<byte[]> DownloadAsBytesAsync(TFileId id, GridFSDownloadOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  121. {
  122. Ensure.IsNotNull((object)id, nameof(id));
  123. options = options ?? new GridFSDownloadOptions();
  124. using (var binding = await GetSingleServerReadBindingAsync(cancellationToken).ConfigureAwait(false))
  125. {
  126. var fileInfo = await GetFileInfoAsync(binding, id, cancellationToken).ConfigureAwait(false);
  127. return await DownloadAsBytesHelperAsync(binding, fileInfo, options, cancellationToken).ConfigureAwait(false);
  128. }
  129. }
  130. /// <inheritdoc />
  131. public byte[] DownloadAsBytesByName(string filename, GridFSDownloadByNameOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  132. {
  133. Ensure.IsNotNull(filename, nameof(filename));
  134. options = options ?? new GridFSDownloadByNameOptions();
  135. using (var binding = GetSingleServerReadBinding(cancellationToken))
  136. {
  137. var fileInfo = GetFileInfoByName(binding, filename, options.Revision, cancellationToken);
  138. return DownloadAsBytesHelper(binding, fileInfo, options, cancellationToken);
  139. }
  140. }
  141. /// <inheritdoc />
  142. public async Task<byte[]> DownloadAsBytesByNameAsync(string filename, GridFSDownloadByNameOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  143. {
  144. Ensure.IsNotNull(filename, nameof(filename));
  145. options = options ?? new GridFSDownloadByNameOptions();
  146. using (var binding = await GetSingleServerReadBindingAsync(cancellationToken).ConfigureAwait(false))
  147. {
  148. var fileInfo = await GetFileInfoByNameAsync(binding, filename, options.Revision, cancellationToken).ConfigureAwait(false);
  149. return await DownloadAsBytesHelperAsync(binding, fileInfo, options, cancellationToken).ConfigureAwait(false);
  150. }
  151. }
  152. /// <inheritdoc />
  153. public void DownloadToStream(TFileId id, Stream destination, GridFSDownloadOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  154. {
  155. Ensure.IsNotNull((object)id, nameof(id));
  156. Ensure.IsNotNull(destination, nameof(destination));
  157. options = options ?? new GridFSDownloadOptions();
  158. using (var binding = GetSingleServerReadBinding(cancellationToken))
  159. {
  160. var fileInfo = GetFileInfo(binding, id, cancellationToken);
  161. DownloadToStreamHelper(binding, fileInfo, destination, options, cancellationToken);
  162. }
  163. }
  164. /// <inheritdoc />
  165. public async Task DownloadToStreamAsync(TFileId id, Stream destination, GridFSDownloadOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  166. {
  167. Ensure.IsNotNull((object)id, nameof(id));
  168. Ensure.IsNotNull(destination, nameof(destination));
  169. options = options ?? new GridFSDownloadOptions();
  170. using (var binding = await GetSingleServerReadBindingAsync(cancellationToken).ConfigureAwait(false))
  171. {
  172. var fileInfo = await GetFileInfoAsync(binding, id, cancellationToken).ConfigureAwait(false);
  173. await DownloadToStreamHelperAsync(binding, fileInfo, destination, options, cancellationToken).ConfigureAwait(false);
  174. }
  175. }
  176. /// <inheritdoc />
  177. public void DownloadToStreamByName(string filename, Stream destination, GridFSDownloadByNameOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  178. {
  179. Ensure.IsNotNull(filename, nameof(filename));
  180. Ensure.IsNotNull(destination, nameof(destination));
  181. options = options ?? new GridFSDownloadByNameOptions();
  182. using (var binding = GetSingleServerReadBinding(cancellationToken))
  183. {
  184. var fileInfo = GetFileInfoByName(binding, filename, options.Revision, cancellationToken);
  185. DownloadToStreamHelper(binding, fileInfo, destination, options, cancellationToken);
  186. }
  187. }
  188. /// <inheritdoc />
  189. public async Task DownloadToStreamByNameAsync(string filename, Stream destination, GridFSDownloadByNameOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  190. {
  191. Ensure.IsNotNull(filename, nameof(filename));
  192. Ensure.IsNotNull(destination, nameof(destination));
  193. options = options ?? new GridFSDownloadByNameOptions();
  194. using (var binding = await GetSingleServerReadBindingAsync(cancellationToken).ConfigureAwait(false))
  195. {
  196. var fileInfo = await GetFileInfoByNameAsync(binding, filename, options.Revision, cancellationToken).ConfigureAwait(false);
  197. await DownloadToStreamHelperAsync(binding, fileInfo, destination, options, cancellationToken).ConfigureAwait(false);
  198. }
  199. }
  200. /// <inheritdoc />
  201. public void Drop(CancellationToken cancellationToken = default(CancellationToken))
  202. {
  203. var filesCollectionNamespace = this.GetFilesCollectionNamespace();
  204. var chunksCollectionNamespace = this.GetChunksCollectionNamespace();
  205. var messageEncoderSettings = this.GetMessageEncoderSettings();
  206. using (var binding = GetSingleServerReadWriteBinding(cancellationToken))
  207. {
  208. var filesCollectionDropOperation = CreateDropCollectionOperation(filesCollectionNamespace, messageEncoderSettings);
  209. filesCollectionDropOperation.Execute(binding, cancellationToken);
  210. var chunksCollectionDropOperation = CreateDropCollectionOperation(chunksCollectionNamespace, messageEncoderSettings);
  211. chunksCollectionDropOperation.Execute(binding, cancellationToken);
  212. }
  213. }
  214. /// <inheritdoc />
  215. public async Task DropAsync(CancellationToken cancellationToken = default(CancellationToken))
  216. {
  217. var filesCollectionNamespace = this.GetFilesCollectionNamespace();
  218. var chunksCollectionNamespace = this.GetChunksCollectionNamespace();
  219. var messageEncoderSettings = this.GetMessageEncoderSettings();
  220. using (var binding = await GetSingleServerReadWriteBindingAsync(cancellationToken).ConfigureAwait(false))
  221. {
  222. var filesCollectionDropOperation = CreateDropCollectionOperation(filesCollectionNamespace, messageEncoderSettings);
  223. await filesCollectionDropOperation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false);
  224. var chunksCollectionDropOperation = CreateDropCollectionOperation(chunksCollectionNamespace, messageEncoderSettings);
  225. await chunksCollectionDropOperation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false);
  226. }
  227. }
  228. /// <inheritdoc />
  229. public IAsyncCursor<GridFSFileInfo<TFileId>> Find(FilterDefinition<GridFSFileInfo<TFileId>> filter, GridFSFindOptions<TFileId> options = null, CancellationToken cancellationToken = default(CancellationToken))
  230. {
  231. Ensure.IsNotNull(filter, nameof(filter));
  232. options = options ?? new GridFSFindOptions<TFileId>();
  233. var operation = CreateFindOperation(filter, options);
  234. using (var binding = GetSingleServerReadBinding(cancellationToken))
  235. {
  236. return operation.Execute(binding, cancellationToken);
  237. }
  238. }
  239. /// <inheritdoc />
  240. public async Task<IAsyncCursor<GridFSFileInfo<TFileId>>> FindAsync(FilterDefinition<GridFSFileInfo<TFileId>> filter, GridFSFindOptions<TFileId> options = null, CancellationToken cancellationToken = default(CancellationToken))
  241. {
  242. Ensure.IsNotNull(filter, nameof(filter));
  243. options = options ?? new GridFSFindOptions<TFileId>();
  244. var operation = CreateFindOperation(filter, options);
  245. using (var binding = await GetSingleServerReadBindingAsync(cancellationToken).ConfigureAwait(false))
  246. {
  247. return await operation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false);
  248. }
  249. }
  250. /// <inheritdoc />
  251. public GridFSDownloadStream<TFileId> OpenDownloadStream(TFileId id, GridFSDownloadOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  252. {
  253. Ensure.IsNotNull((object)id, nameof(id));
  254. options = options ?? new GridFSDownloadOptions();
  255. using (var binding = GetSingleServerReadBinding(cancellationToken))
  256. {
  257. var fileInfo = GetFileInfo(binding, id, cancellationToken);
  258. return CreateDownloadStream(binding.Fork(), fileInfo, options, cancellationToken);
  259. }
  260. }
  261. /// <inheritdoc />
  262. public async Task<GridFSDownloadStream<TFileId>> OpenDownloadStreamAsync(TFileId id, GridFSDownloadOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  263. {
  264. Ensure.IsNotNull((object)id, nameof(id));
  265. options = options ?? new GridFSDownloadOptions();
  266. using (var binding = await GetSingleServerReadBindingAsync(cancellationToken).ConfigureAwait(false))
  267. {
  268. var fileInfo = await GetFileInfoAsync(binding, id, cancellationToken).ConfigureAwait(false);
  269. return CreateDownloadStream(binding.Fork(), fileInfo, options, cancellationToken);
  270. }
  271. }
  272. /// <inheritdoc />
  273. public GridFSDownloadStream<TFileId> OpenDownloadStreamByName(string filename, GridFSDownloadByNameOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  274. {
  275. Ensure.IsNotNull(filename, nameof(filename));
  276. options = options ?? new GridFSDownloadByNameOptions();
  277. using (var binding = GetSingleServerReadBinding(cancellationToken))
  278. {
  279. var fileInfo = GetFileInfoByName(binding, filename, options.Revision, cancellationToken);
  280. return CreateDownloadStream(binding.Fork(), fileInfo, options);
  281. }
  282. }
  283. /// <inheritdoc />
  284. public async Task<GridFSDownloadStream<TFileId>> OpenDownloadStreamByNameAsync(string filename, GridFSDownloadByNameOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  285. {
  286. Ensure.IsNotNull(filename, nameof(filename));
  287. options = options ?? new GridFSDownloadByNameOptions();
  288. using (var binding = await GetSingleServerReadBindingAsync(cancellationToken).ConfigureAwait(false))
  289. {
  290. var fileInfo = await GetFileInfoByNameAsync(binding, filename, options.Revision, cancellationToken).ConfigureAwait(false);
  291. return CreateDownloadStream(binding.Fork(), fileInfo, options);
  292. }
  293. }
  294. /// <inheritdoc />
  295. public GridFSUploadStream<TFileId> OpenUploadStream(TFileId id, string filename, GridFSUploadOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  296. {
  297. Ensure.IsNotNull((object)id, nameof(id));
  298. Ensure.IsNotNull(filename, nameof(filename));
  299. options = options ?? new GridFSUploadOptions();
  300. using (var binding = GetSingleServerReadWriteBinding(cancellationToken))
  301. {
  302. EnsureIndexes(binding, cancellationToken);
  303. return CreateUploadStream(binding, id, filename, options);
  304. }
  305. }
  306. /// <inheritdoc />
  307. public async Task<GridFSUploadStream<TFileId>> OpenUploadStreamAsync(TFileId id, string filename, GridFSUploadOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  308. {
  309. Ensure.IsNotNull((object)id, nameof(id));
  310. Ensure.IsNotNull(filename, nameof(filename));
  311. options = options ?? new GridFSUploadOptions();
  312. using (var binding = await GetSingleServerReadWriteBindingAsync(cancellationToken).ConfigureAwait(false))
  313. {
  314. await EnsureIndexesAsync(binding, cancellationToken).ConfigureAwait(false);
  315. return CreateUploadStream(binding, id, filename, options);
  316. }
  317. }
  318. /// <inheritdoc />
  319. public void Rename(TFileId id, string newFilename, CancellationToken cancellationToken = default(CancellationToken))
  320. {
  321. Ensure.IsNotNull((object)id, nameof(id));
  322. Ensure.IsNotNull(newFilename, nameof(newFilename));
  323. var renameOperation = CreateRenameOperation(id, newFilename);
  324. using (var binding = GetSingleServerReadWriteBinding(cancellationToken))
  325. {
  326. var result = renameOperation.Execute(binding, cancellationToken);
  327. if (result.IsModifiedCountAvailable && result.ModifiedCount == 0)
  328. {
  329. throw new GridFSFileNotFoundException(_idSerializationInfo.SerializeValue(id));
  330. }
  331. }
  332. }
  333. /// <inheritdoc />
  334. public async Task RenameAsync(TFileId id, string newFilename, CancellationToken cancellationToken = default(CancellationToken))
  335. {
  336. Ensure.IsNotNull((object)id, nameof(id));
  337. Ensure.IsNotNull(newFilename, nameof(newFilename));
  338. var renameOperation = CreateRenameOperation(id, newFilename);
  339. using (var binding = await GetSingleServerReadWriteBindingAsync(cancellationToken).ConfigureAwait(false))
  340. {
  341. var result = await renameOperation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false);
  342. if (result.IsModifiedCountAvailable && result.ModifiedCount == 0)
  343. {
  344. throw new GridFSFileNotFoundException(_idSerializationInfo.SerializeValue(id));
  345. }
  346. }
  347. }
  348. /// <inheritdoc />
  349. public void UploadFromBytes(TFileId id, string filename, byte[] source, GridFSUploadOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  350. {
  351. Ensure.IsNotNull((object)id, nameof(id));
  352. Ensure.IsNotNull(filename, nameof(filename));
  353. Ensure.IsNotNull(source, nameof(source));
  354. options = options ?? new GridFSUploadOptions();
  355. using (var sourceStream = new MemoryStream(source))
  356. {
  357. UploadFromStream(id, filename, sourceStream, options, cancellationToken);
  358. }
  359. }
  360. /// <inheritdoc />
  361. public async Task UploadFromBytesAsync(TFileId id, string filename, byte[] source, GridFSUploadOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  362. {
  363. Ensure.IsNotNull((object)id, nameof(id));
  364. Ensure.IsNotNull(filename, nameof(filename));
  365. Ensure.IsNotNull(source, nameof(source));
  366. options = options ?? new GridFSUploadOptions();
  367. using (var sourceStream = new MemoryStream(source))
  368. {
  369. await UploadFromStreamAsync(id, filename, sourceStream, options, cancellationToken).ConfigureAwait(false);
  370. }
  371. }
  372. /// <inheritdoc />
  373. public void UploadFromStream(TFileId id, string filename, Stream source, GridFSUploadOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  374. {
  375. Ensure.IsNotNull((object)id, nameof(id));
  376. Ensure.IsNotNull(filename, nameof(filename));
  377. Ensure.IsNotNull(source, nameof(source));
  378. options = options ?? new GridFSUploadOptions();
  379. using (var destination = OpenUploadStream(id, filename, options, cancellationToken))
  380. {
  381. var chunkSizeBytes = options.ChunkSizeBytes ?? _options.ChunkSizeBytes;
  382. var buffer = new byte[chunkSizeBytes];
  383. while (true)
  384. {
  385. int bytesRead = 0;
  386. try
  387. {
  388. bytesRead = source.Read(buffer, 0, buffer.Length);
  389. }
  390. catch
  391. {
  392. try
  393. {
  394. destination.Abort();
  395. }
  396. catch
  397. {
  398. // ignore any exceptions because we're going to rethrow the original exception
  399. }
  400. throw;
  401. }
  402. if (bytesRead == 0)
  403. {
  404. break;
  405. }
  406. destination.Write(buffer, 0, bytesRead);
  407. }
  408. destination.Close(cancellationToken);
  409. }
  410. }
  411. /// <inheritdoc />
  412. public async Task UploadFromStreamAsync(TFileId id, string filename, Stream source, GridFSUploadOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
  413. {
  414. Ensure.IsNotNull((object)id, nameof(id));
  415. Ensure.IsNotNull(filename, nameof(filename));
  416. Ensure.IsNotNull(source, nameof(source));
  417. options = options ?? new GridFSUploadOptions();
  418. using (var destination = await OpenUploadStreamAsync(id, filename, options, cancellationToken).ConfigureAwait(false))
  419. {
  420. var chunkSizeBytes = options.ChunkSizeBytes ?? _options.ChunkSizeBytes;
  421. var buffer = new byte[chunkSizeBytes];
  422. while (true)
  423. {
  424. int bytesRead = 0;
  425. Exception sourceException = null;
  426. try
  427. {
  428. bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
  429. }
  430. catch (Exception ex)
  431. {
  432. // cannot await in the body of a catch clause
  433. sourceException = ex;
  434. }
  435. if (sourceException != null)
  436. {
  437. try
  438. {
  439. await destination.AbortAsync().ConfigureAwait(false);
  440. }
  441. catch
  442. {
  443. // ignore any exceptions because we're going to rethrow the original exception
  444. }
  445. throw sourceException;
  446. }
  447. if (bytesRead == 0)
  448. {
  449. break;
  450. }
  451. await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
  452. }
  453. await destination.CloseAsync(cancellationToken).ConfigureAwait(false);
  454. }
  455. }
  456. // private methods
  457. private bool ChunksCollectionIndexesExist(List<BsonDocument> indexes)
  458. {
  459. var key = new BsonDocument { { "files_id", 1 }, { "n", 1 } };
  460. return IndexExists(indexes, key);
  461. }
  462. private bool ChunksCollectionIndexesExist(IReadBindingHandle binding, CancellationToken cancellationToken)
  463. {
  464. var indexes = ListIndexes(binding, this.GetChunksCollectionNamespace(), cancellationToken);
  465. return ChunksCollectionIndexesExist(indexes);
  466. }
  467. private async Task<bool> ChunksCollectionIndexesExistAsync(IReadBindingHandle binding, CancellationToken cancellationToken)
  468. {
  469. var indexes = await ListIndexesAsync(binding, this.GetChunksCollectionNamespace(), cancellationToken).ConfigureAwait(false);
  470. return ChunksCollectionIndexesExist(indexes);
  471. }
  472. private void CreateChunksCollectionIndexes(IReadWriteBindingHandle binding, CancellationToken cancellationToken)
  473. {
  474. var operation = CreateCreateChunksCollectionIndexesOperation();
  475. operation.Execute(binding, cancellationToken);
  476. }
  477. private async Task CreateChunksCollectionIndexesAsync(IReadWriteBindingHandle binding, CancellationToken cancellationToken)
  478. {
  479. var operation = CreateCreateChunksCollectionIndexesOperation();
  480. await operation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false);
  481. }
  482. internal CreateIndexesOperation CreateCreateChunksCollectionIndexesOperation()
  483. {
  484. var collectionNamespace = this.GetChunksCollectionNamespace();
  485. var requests = new[] { new CreateIndexRequest(new BsonDocument { { "files_id", 1 }, { "n", 1 } }) { Unique = true } };
  486. var messageEncoderSettings = this.GetMessageEncoderSettings();
  487. return new CreateIndexesOperation(collectionNamespace, requests, messageEncoderSettings)
  488. {
  489. WriteConcern = _options.WriteConcern ?? _database.Settings.WriteConcern
  490. };
  491. }
  492. internal CreateIndexesOperation CreateCreateFilesCollectionIndexesOperation()
  493. {
  494. var collectionNamespace = this.GetFilesCollectionNamespace();
  495. var requests = new[] { new CreateIndexRequest(new BsonDocument { { "filename", 1 }, { "uploadDate", 1 } }) };
  496. var messageEncoderSettings = this.GetMessageEncoderSettings();
  497. return new CreateIndexesOperation(collectionNamespace, requests, messageEncoderSettings)
  498. {
  499. WriteConcern = _options.WriteConcern ?? _database.Settings.WriteConcern
  500. };
  501. }
  502. private BulkMixedWriteOperation CreateDeleteChunksOperation(TFileId id)
  503. {
  504. var filter = new BsonDocument("files_id", _idSerializationInfo.SerializeValue(id));
  505. return new BulkMixedWriteOperation(
  506. this.GetChunksCollectionNamespace(),
  507. new[] { new DeleteRequest(filter) { Limit = 0 } },
  508. this.GetMessageEncoderSettings());
  509. }
  510. private GridFSDownloadStream<TFileId> CreateDownloadStream(IReadBindingHandle binding, GridFSFileInfo<TFileId> fileInfo, GridFSDownloadOptions options, CancellationToken cancellationToken = default(CancellationToken))
  511. {
  512. var checkMD5 = options.CheckMD5 ?? false;
  513. var seekable = options.Seekable ?? false;
  514. if (checkMD5 && seekable)
  515. {
  516. throw new ArgumentException("CheckMD5 can only be used when Seekable is false.");
  517. }
  518. if (seekable)
  519. {
  520. return new GridFSSeekableDownloadStream<TFileId>(this, binding, fileInfo);
  521. }
  522. else
  523. {
  524. return new GridFSForwardOnlyDownloadStream<TFileId>(this, binding, fileInfo, checkMD5);
  525. }
  526. }
  527. internal DropCollectionOperation CreateDropCollectionOperation(CollectionNamespace collectionNamespace, MessageEncoderSettings messageEncoderSettings)
  528. {
  529. return new DropCollectionOperation(collectionNamespace, messageEncoderSettings)
  530. {
  531. WriteConcern = _options.WriteConcern ?? _database.Settings.WriteConcern
  532. };
  533. }
  534. private BulkMixedWriteOperation CreateDeleteFileOperation(TFileId id)
  535. {
  536. var filter = new BsonDocument("_id", _idSerializationInfo.SerializeValue(id));
  537. return new BulkMixedWriteOperation(
  538. this.GetFilesCollectionNamespace(),
  539. new[] { new DeleteRequest(filter) },
  540. this.GetMessageEncoderSettings());
  541. }
  542. private void CreateFilesCollectionIndexes(IReadWriteBindingHandle binding, CancellationToken cancellationToken)
  543. {
  544. var operation = CreateCreateFilesCollectionIndexesOperation();
  545. operation.Execute(binding, cancellationToken);
  546. }
  547. private async Task CreateFilesCollectionIndexesAsync(IReadWriteBindingHandle binding, CancellationToken cancellationToken)
  548. {
  549. var operation = CreateCreateFilesCollectionIndexesOperation();
  550. await operation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false);
  551. }
  552. private FindOperation<GridFSFileInfo<TFileId>> CreateFindOperation(FilterDefinition<GridFSFileInfo<TFileId>> filter, GridFSFindOptions<TFileId> options)
  553. {
  554. var filesCollectionNamespace = this.GetFilesCollectionNamespace();
  555. var messageEncoderSettings = this.GetMessageEncoderSettings();
  556. var renderedFilter = filter.Render(_fileInfoSerializer, _options.SerializerRegistry);
  557. var renderedSort = options.Sort == null ? null : options.Sort.Render(_fileInfoSerializer, _options.SerializerRegistry);
  558. return new FindOperation<GridFSFileInfo<TFileId>>(
  559. filesCollectionNamespace,
  560. _fileInfoSerializer,
  561. messageEncoderSettings)
  562. {
  563. BatchSize = options.BatchSize,
  564. Filter = renderedFilter,
  565. Limit = options.Limit,
  566. MaxTime = options.MaxTime,
  567. NoCursorTimeout = options.NoCursorTimeout ?? false,
  568. ReadConcern = GetReadConcern(),
  569. Skip = options.Skip,
  570. Sort = renderedSort
  571. };
  572. }
  573. private FindOperation<GridFSFileInfo<TFileId>> CreateGetFileInfoByNameOperation(string filename, int revision)
  574. {
  575. var collectionNamespace = this.GetFilesCollectionNamespace();
  576. var messageEncoderSettings = this.GetMessageEncoderSettings();
  577. var filter = new BsonDocument("filename", filename);
  578. var skip = revision >= 0 ? revision : -revision - 1;
  579. var limit = 1;
  580. var sort = new BsonDocument("uploadDate", revision >= 0 ? 1 : -1);
  581. return new FindOperation<GridFSFileInfo<TFileId>>(
  582. collectionNamespace,
  583. _fileInfoSerializer,
  584. messageEncoderSettings)
  585. {
  586. Filter = filter,
  587. Limit = limit,
  588. ReadConcern = GetReadConcern(),
  589. Skip = skip,
  590. Sort = sort
  591. };
  592. }
  593. private FindOperation<GridFSFileInfo<TFileId>> CreateGetFileInfoOperation(TFileId id)
  594. {
  595. var filesCollectionNamespace = this.GetFilesCollectionNamespace();
  596. var messageEncoderSettings = this.GetMessageEncoderSettings();
  597. var filter = new BsonDocument("_id", _idSerializationInfo.SerializeValue(id));
  598. return new FindOperation<GridFSFileInfo<TFileId>>(
  599. filesCollectionNamespace,
  600. _fileInfoSerializer,
  601. messageEncoderSettings)
  602. {
  603. Filter = filter,
  604. Limit = 1,
  605. ReadConcern = GetReadConcern(),
  606. SingleBatch = true
  607. };
  608. }
  609. private FindOperation<BsonDocument> CreateIsFilesCollectionEmptyOperation()
  610. {
  611. var filesCollectionNamespace = this.GetFilesCollectionNamespace();
  612. var messageEncoderSettings = this.GetMessageEncoderSettings();
  613. return new FindOperation<BsonDocument>(filesCollectionNamespace, BsonDocumentSerializer.Instance, messageEncoderSettings)
  614. {
  615. Limit = 1,
  616. ReadConcern = GetReadConcern(),
  617. SingleBatch = true,
  618. Projection = new BsonDocument("_id", 1)
  619. };
  620. }
  621. private ListIndexesOperation CreateListIndexesOperation(CollectionNamespace collectionNamespace)
  622. {
  623. var messageEncoderSettings = this.GetMessageEncoderSettings();
  624. return new ListIndexesOperation(collectionNamespace, messageEncoderSettings);
  625. }
  626. private BulkMixedWriteOperation CreateRenameOperation(TFileId id, string newFilename)
  627. {
  628. var filesCollectionNamespace = this.GetFilesCollectionNamespace();
  629. var filter = new BsonDocument("_id", _idSerializationInfo.SerializeValue(id));
  630. var update = new BsonDocument("$set", new BsonDocument("filename", newFilename));
  631. var requests = new[] { new UpdateRequest(UpdateType.Update, filter, update) };
  632. var messageEncoderSettings = this.GetMessageEncoderSettings();
  633. return new BulkMixedWriteOperation(filesCollectionNamespace, requests, messageEncoderSettings);
  634. }
  635. private GridFSUploadStream<TFileId> CreateUploadStream(IReadWriteBindingHandle binding, TFileId id, string filename, GridFSUploadOptions options)
  636. {
  637. #pragma warning disable 618
  638. var chunkSizeBytes = options.ChunkSizeBytes ?? _options.ChunkSizeBytes;
  639. var batchSize = options.BatchSize ?? (16 * 1024 * 1024 / chunkSizeBytes);
  640. return new GridFSForwardOnlyUploadStream<TFileId>(
  641. this,
  642. binding.Fork(),
  643. id,
  644. filename,
  645. options.Metadata,
  646. options.Aliases,
  647. options.ContentType,
  648. chunkSizeBytes,
  649. batchSize,
  650. options.DisableMD5);
  651. #pragma warning restore
  652. }
  653. private byte[] DownloadAsBytesHelper(IReadBindingHandle binding, GridFSFileInfo<TFileId> fileInfo, GridFSDownloadOptions options, CancellationToken cancellationToken = default(CancellationToken))
  654. {
  655. if (fileInfo.Length > int.MaxValue)
  656. {
  657. throw new NotSupportedException("GridFS stored file is too large to be returned as a byte array.");
  658. }
  659. var bytes = new byte[(int)fileInfo.Length];
  660. using (var destination = new MemoryStream(bytes))
  661. {
  662. DownloadToStreamHelper(binding, fileInfo, destination, options, cancellationToken);
  663. return bytes;
  664. }
  665. }
  666. private async Task<byte[]> DownloadAsBytesHelperAsync(IReadBindingHandle binding, GridFSFileInfo<TFileId> fileInfo, GridFSDownloadOptions options, CancellationToken cancellationToken = default(CancellationToken))
  667. {
  668. if (fileInfo.Length > int.MaxValue)
  669. {
  670. throw new NotSupportedException("GridFS stored file is too large to be returned as a byte array.");
  671. }
  672. var bytes = new byte[(int)fileInfo.Length];
  673. using (var destination = new MemoryStream(bytes))
  674. {
  675. await DownloadToStreamHelperAsync(binding, fileInfo, destination, options, cancellationToken).ConfigureAwait(false);
  676. return bytes;
  677. }
  678. }
  679. private void DownloadToStreamHelper(IReadBindingHandle binding, GridFSFileInfo<TFileId> fileInfo, Stream destination, GridFSDownloadOptions options, CancellationToken cancellationToken = default(CancellationToken))
  680. {
  681. var checkMD5 = options.CheckMD5 ?? false;
  682. using (var source = new GridFSForwardOnlyDownloadStream<TFileId>(this, binding.Fork(), fileInfo, checkMD5))
  683. {
  684. var count = source.Length;
  685. var buffer = new byte[fileInfo.ChunkSizeBytes];
  686. while (count > 0)
  687. {
  688. var partialCount = (int)Math.Min(buffer.Length, count);
  689. source.ReadBytes(buffer, 0, partialCount, cancellationToken);
  690. //((Stream)source).ReadBytes(buffer, 0, partialCount, cancellationToken);
  691. destination.Write(buffer, 0, partialCount);
  692. count -= partialCount;
  693. }
  694. }
  695. }
  696. private async Task DownloadToStreamHelperAsync(IReadBindingHandle binding, GridFSFileInfo<TFileId> fileInfo, Stream destination, GridFSDownloadOptions options, CancellationToken cancellationToken = default(CancellationToken))
  697. {
  698. var checkMD5 = options.CheckMD5 ?? false;
  699. using (var source = new GridFSForwardOnlyDownloadStream<TFileId>(this, binding.Fork(), fileInfo, checkMD5))
  700. {
  701. var count = source.Length;
  702. var buffer = new byte[fileInfo.ChunkSizeBytes];
  703. while (count > 0)
  704. {
  705. var partialCount = (int)Math.Min(buffer.Length, count);
  706. await source.ReadBytesAsync(buffer, 0, partialCount, cancellationToken).ConfigureAwait(false);
  707. await destination.WriteAsync(buffer, 0, partialCount, cancellationToken).ConfigureAwait(false);
  708. count -= partialCount;
  709. }
  710. await source.CloseAsync(cancellationToken).ConfigureAwait(false);
  711. }
  712. }
  713. private void EnsureIndexes(IReadWriteBindingHandle binding, CancellationToken cancellationToken)
  714. {
  715. _ensureIndexesSemaphore.Wait(cancellationToken);
  716. try
  717. {
  718. if (!_ensureIndexesDone)
  719. {
  720. var isFilesCollectionEmpty = IsFilesCollectionEmpty(binding, cancellationToken);
  721. if (isFilesCollectionEmpty)
  722. {
  723. if (!FilesCollectionIndexesExist(binding, cancellationToken))
  724. {
  725. CreateFilesCollectionIndexes(binding, cancellationToken);
  726. }
  727. if (!ChunksCollectionIndexesExist(binding, cancellationToken))
  728. {
  729. CreateChunksCollectionIndexes(binding, cancellationToken);
  730. }
  731. }
  732. _ensureIndexesDone = true;
  733. }
  734. }
  735. finally
  736. {
  737. _ensureIndexesSemaphore.Release();
  738. }
  739. }
  740. private async Task EnsureIndexesAsync(IReadWriteBindingHandle binding, CancellationToken cancellationToken)
  741. {
  742. await _ensureIndexesSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
  743. try
  744. {
  745. if (!_ensureIndexesDone)
  746. {
  747. var isFilesCollectionEmpty = await IsFilesCollectionEmptyAsync(binding, cancellationToken).ConfigureAwait(false);
  748. if (isFilesCollectionEmpty)
  749. {
  750. if (!(await FilesCollectionIndexesExistAsync(binding, cancellationToken).ConfigureAwait(false)))
  751. {
  752. await CreateFilesCollectionIndexesAsync(binding, cancellationToken).ConfigureAwait(false);
  753. }
  754. if (!(await ChunksCollectionIndexesExistAsync(binding, cancellationToken).ConfigureAwait(false)))
  755. {
  756. await CreateChunksCollectionIndexesAsync(binding, cancellationToken).ConfigureAwait(false);
  757. }
  758. }
  759. _ensureIndexesDone = true;
  760. }
  761. }
  762. finally
  763. {
  764. _ensureIndexesSemaphore.Release();
  765. }
  766. }
  767. private bool FilesCollectionIndexesExist(List<BsonDocument> indexes)
  768. {
  769. var key = new BsonDocument { { "filename", 1 }, { "uploadDate", 1 } };
  770. return IndexExists(indexes, key);
  771. }
  772. private bool FilesCollectionIndexesExist(IReadBindingHandle binding, CancellationToken cancellationToken)
  773. {
  774. var indexes = ListIndexes(binding, this.GetFilesCollectionNamespace(), cancellationToken);
  775. return FilesCollectionIndexesExist(indexes);
  776. }
  777. private async Task<bool> FilesCollectionIndexesExistAsync(IReadBindingHandle binding, CancellationToken cancellationToken)
  778. {
  779. var indexes = await ListIndexesAsync(binding, this.GetFilesCollectionNamespace(), cancellationToken).ConfigureAwait(false);
  780. return FilesCollectionIndexesExist(indexes);
  781. }
  782. private GridFSFileInfo<TFileId> GetFileInfo(IReadBindingHandle binding, TFileId id, CancellationToken cancellationToken)
  783. {
  784. var operation = CreateGetFileInfoOperation(id);
  785. using (var cursor = operation.Execute(binding, cancellationToken))
  786. {
  787. var fileInfo = cursor.FirstOrDefault(cancellationToken);
  788. if (fileInfo == null)
  789. {
  790. throw new GridFSFileNotFoundException(_idSerializationInfo.SerializeValue(id));
  791. }
  792. return fileInfo;
  793. }
  794. }
  795. private async Task<GridFSFileInfo<TFileId>> GetFileInfoAsync(IReadBindingHandle binding, TFileId id, CancellationToken cancellationToken)
  796. {
  797. var operation = CreateGetFileInfoOperation(id);
  798. using (var cursor = await operation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false))
  799. {
  800. var fileInfo = await cursor.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
  801. if (fileInfo == null)
  802. {
  803. throw new GridFSFileNotFoundException(_idSerializationInfo.SerializeValue(id));
  804. }
  805. return fileInfo;
  806. }
  807. }
  808. private GridFSFileInfo<TFileId> GetFileInfoByName(IReadBindingHandle binding, string filename, int revision, CancellationToken cancellationToken)
  809. {
  810. var operation = CreateGetFileInfoByNameOperation(filename, revision);
  811. using (var cursor = operation.Execute(binding, cancellationToken))
  812. {
  813. var fileInfo = cursor.FirstOrDefault(cancellationToken);
  814. if (fileInfo == null)
  815. {
  816. throw new GridFSFileNotFoundException(filename, revision);
  817. }
  818. return fileInfo;
  819. }
  820. }
  821. private async Task<GridFSFileInfo<TFileId>> GetFileInfoByNameAsync(IReadBindingHandle binding, string filename, int revision, CancellationToken cancellationToken)
  822. {
  823. var operation = CreateGetFileInfoByNameOperation(filename, revision);
  824. using (var cursor = await operation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false))
  825. {
  826. var fileInfo = await cursor.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
  827. if (fileInfo == null)
  828. {
  829. throw new GridFSFileNotFoundException(filename, revision);
  830. }
  831. return fileInfo;
  832. }
  833. }
  834. private ReadConcern GetReadConcern()
  835. {
  836. return _options.ReadConcern ?? _database.Settings.ReadConcern;
  837. }
  838. private IReadBindingHandle GetSingleServerReadBinding(CancellationToken cancellationToken)
  839. {
  840. var readPreference = _options.ReadPreference ?? _database.Settings.ReadPreference;
  841. var selector = new ReadPreferenceServerSelector(readPreference);
  842. var server = _cluster.SelectServer(selector, cancellationToken);
  843. var binding = new SingleServerReadBinding(server, readPreference, NoCoreSession.NewHandle());
  844. return new ReadBindingHandle(binding);
  845. }
  846. private async Task<IReadBindingHandle> GetSingleServerReadBindingAsync(CancellationToken cancellationToken)
  847. {
  848. var readPreference = _options.ReadPreference ?? _database.Settings.ReadPreference;
  849. var selector = new ReadPreferenceServerSelector(readPreference);
  850. var server = await _cluster.SelectServerAsync(selector, cancellationToken).ConfigureAwait(false);
  851. var binding = new SingleServerReadBinding(server, readPreference, NoCoreSession.NewHandle());
  852. return new ReadBindingHandle(binding);
  853. }
  854. private IReadWriteBindingHandle GetSingleServerReadWriteBinding(CancellationToken cancellationToken)
  855. {
  856. var selector = WritableServerSelector.Instance;
  857. var server = _cluster.SelectServer(selector, cancellationToken);
  858. var binding = new SingleServerReadWriteBinding(server, NoCoreSession.NewHandle());
  859. return new ReadWriteBindingHandle(binding);
  860. }
  861. private async Task<IReadWriteBindingHandle> GetSingleServerReadWriteBindingAsync(CancellationToken cancellationToken)
  862. {
  863. var selector = WritableServerSelector.Instance;
  864. var server = await _cluster.SelectServerAsync(selector, cancellationToken).ConfigureAwait(false);
  865. var binding = new SingleServerReadWriteBinding(server, NoCoreSession.NewHandle());
  866. return new ReadWriteBindingHandle(binding);
  867. }
  868. private bool IndexExists(List<BsonDocument> indexes, BsonDocument key)
  869. {
  870. foreach (var index in indexes)
  871. {
  872. if (index["key"].Equals(key))
  873. {
  874. return true;
  875. }
  876. }
  877. return false;
  878. }
  879. private bool IsFilesCollectionEmpty(IReadWriteBindingHandle binding, CancellationToken cancellationToken)
  880. {
  881. var operation = CreateIsFilesCollectionEmptyOperation();
  882. using (var cursor = operation.Execute(binding, cancellationToken))
  883. {
  884. var firstOrDefault = cursor.FirstOrDefault(cancellationToken);
  885. return firstOrDefault == null;
  886. }
  887. }
  888. private async Task<bool> IsFilesCollectionEmptyAsync(IReadWriteBindingHandle binding, CancellationToken cancellationToken)
  889. {
  890. var operation = CreateIsFilesCollectionEmptyOperation();
  891. using (var cursor = await operation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false))
  892. {
  893. var firstOrDefault = await cursor.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
  894. return firstOrDefault == null;
  895. }
  896. }
  897. private List<BsonDocument> ListIndexes(IReadBinding binding, CollectionNamespace collectionNamespace, CancellationToken cancellationToken)
  898. {
  899. var operation = CreateListIndexesOperation(collectionNamespace);
  900. return operation.Execute(binding, cancellationToken).ToList();
  901. }
  902. private async Task<List<BsonDocument>> ListIndexesAsync(IReadBinding binding, CollectionNamespace collectionNamespace, CancellationToken cancellationToken)
  903. {
  904. var operation = CreateListIndexesOperation(collectionNamespace);
  905. var cursor = await operation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false);
  906. return await cursor.ToListAsync(cancellationToken).ConfigureAwait(false);
  907. }
  908. }
  909. }