PageRenderTime 58ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/Raven.Client.Lightweight/Document/Async/AsyncDocumentSession.cs

http://github.com/ayende/ravendb
C# | 1100 lines | 738 code | 153 blank | 209 comment | 74 complexity | a6f0ba987e0e76e2493199279da98123 MD5 | raw file
Possible License(s): GPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-2.1, Apache-2.0, BSD-3-Clause, CC-BY-SA-3.0
  1. //-----------------------------------------------------------------------
  2. // <copyright file="AsyncDocumentSession.cs" company="Hibernating Rhinos LTD">
  3. // Copyright (c) Hibernating Rhinos LTD. All rights reserved.
  4. // </copyright>
  5. //-----------------------------------------------------------------------
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Linq.Expressions;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. using Raven.Abstractions.Data;
  13. using Raven.Abstractions.Extensions;
  14. using Raven.Abstractions.Util;
  15. using Raven.Client.Connection;
  16. using Raven.Client.Connection.Async;
  17. using Raven.Client.Document.SessionOperations;
  18. using Raven.Client.Linq;
  19. using Raven.Client.Indexes;
  20. using Raven.Json.Linq;
  21. using Raven.Client.Document.Batches;
  22. using System.Diagnostics;
  23. using System.Dynamic;
  24. namespace Raven.Client.Document.Async
  25. {
  26. /// <summary>
  27. /// Implementation for async document session
  28. /// </summary>
  29. public class AsyncDocumentSession : InMemoryDocumentSessionOperations, IAsyncDocumentSessionImpl, IAsyncAdvancedSessionOperations, IDocumentQueryGenerator, ITransactionalDocumentSession
  30. {
  31. private readonly AsyncDocumentKeyGeneration asyncDocumentKeyGeneration;
  32. /// <summary>
  33. /// Initializes a new instance of the <see cref="AsyncDocumentSession"/> class.
  34. /// </summary>
  35. public AsyncDocumentSession(string dbName, DocumentStore documentStore,
  36. IAsyncDatabaseCommands asyncDatabaseCommands,
  37. DocumentSessionListeners listeners,
  38. Guid id)
  39. : base(dbName, documentStore, listeners, id)
  40. {
  41. AsyncDatabaseCommands = asyncDatabaseCommands;
  42. GenerateDocumentKeysOnStore = false;
  43. asyncDocumentKeyGeneration = new AsyncDocumentKeyGeneration(this, entitiesAndMetadata.TryGetValue, (key, entity, metadata) => key);
  44. }
  45. /// <summary>
  46. /// Gets the async database commands.
  47. /// </summary>
  48. /// <value>The async database commands.</value>
  49. public IAsyncDatabaseCommands AsyncDatabaseCommands { get; private set; }
  50. /// <summary>
  51. /// Access the lazy operations
  52. /// </summary>
  53. public IAsyncLazySessionOperations Lazily
  54. {
  55. get { return this; }
  56. }
  57. /// <summary>
  58. /// Access the eager operations
  59. /// </summary>
  60. public IAsyncEagerSessionOperations Eagerly
  61. {
  62. get { return this; }
  63. }
  64. /// <summary>
  65. /// Begin a load while including the specified path
  66. /// </summary>
  67. /// <param name="path">The path.</param>
  68. IAsyncLazyLoaderWithInclude<object> IAsyncLazySessionOperations.Include(string path)
  69. {
  70. return new AsyncLazyMultiLoaderWithInclude<object>(this).Include(path);
  71. }
  72. /// <summary>
  73. /// Begin a load while including the specified path
  74. /// </summary>
  75. /// <param name="path">The path.</param>
  76. IAsyncLazyLoaderWithInclude<T> IAsyncLazySessionOperations.Include<T>(Expression<Func<T, object>> path)
  77. {
  78. return new AsyncLazyMultiLoaderWithInclude<T>(this).Include(path);
  79. }
  80. /// <summary>
  81. /// Loads the specified ids.
  82. /// </summary>
  83. /// <param name="token">The cancellation token.</param>
  84. /// <param name="ids">The ids of the documents to load.</param>
  85. Lazy<Task<T[]>> IAsyncLazySessionOperations.LoadAsync<T>(IEnumerable<string> ids, CancellationToken token)
  86. {
  87. return Lazily.LoadAsync<T>(ids, null, token);
  88. }
  89. /// <summary>
  90. /// Loads the specified id.
  91. /// </summary>
  92. /// <typeparam name="T"></typeparam>
  93. /// <param name="id">The id.</param>
  94. /// <param name="token">The cancellation token.</param>
  95. /// <returns></returns>
  96. Lazy<Task<T>> IAsyncLazySessionOperations.LoadAsync<T>(string id, CancellationToken token)
  97. {
  98. return Lazily.LoadAsync(id, (Action<T>)null, token);
  99. }
  100. /// <summary>
  101. /// Loads the specified ids and a function to call when it is evaluated
  102. /// </summary>
  103. public Lazy<Task<T[]>> LoadAsync<T>(IEnumerable<string> ids, Action<T[]> onEval, CancellationToken token = default (CancellationToken))
  104. {
  105. return LazyLoadInternal(ids.ToArray(), new KeyValuePair<string, Type>[0], onEval, token);
  106. }
  107. /// <summary>
  108. /// Loads the specified id and a function to call when it is evaluated
  109. /// </summary>
  110. public Lazy<Task<T>> LoadAsync<T>(string id, Action<T> onEval, CancellationToken token = default (CancellationToken))
  111. {
  112. if (IsLoaded(id))
  113. return new Lazy<Task<T>>(() => LoadAsync<T>(id, token));
  114. var lazyLoadOperation = new LazyLoadOperation<T>(id, new LoadOperation(this, AsyncDatabaseCommands.DisableAllCaching, id), handleInternalMetadata: HandleInternalMetadata);
  115. return AddLazyOperation(lazyLoadOperation, onEval, token);
  116. }
  117. /// <summary>
  118. /// Loads the specified entities with the specified id after applying
  119. /// conventions on the provided id to get the real document id.
  120. /// </summary>
  121. /// <remarks>
  122. /// This method allows you to call:
  123. /// Load{Post}(1)
  124. /// And that call will internally be translated to
  125. /// Load{Post}("posts/1");
  126. ///
  127. /// Or whatever your conventions specify.
  128. /// </remarks>
  129. Lazy<Task<T>> IAsyncLazySessionOperations.LoadAsync<T>(ValueType id, Action<T> onEval, CancellationToken token)
  130. {
  131. var documentKey = Conventions.FindFullDocumentKeyFromNonStringIdentifier(id, typeof(T), false);
  132. return Lazily.LoadAsync(documentKey, onEval, token);
  133. }
  134. Lazy<Task<T[]>> IAsyncLazySessionOperations.LoadAsync<T>(CancellationToken token,params ValueType[] ids)
  135. {
  136. var documentKeys = ids.Select(id => Conventions.FindFullDocumentKeyFromNonStringIdentifier(id, typeof(T), false));
  137. return Lazily.LoadAsync<T>(documentKeys, null, token);
  138. }
  139. Lazy<Task<T[]>> IAsyncLazySessionOperations.LoadAsync<T>(IEnumerable<ValueType> ids, CancellationToken token)
  140. {
  141. var documentKeys = ids.Select(id => Conventions.FindFullDocumentKeyFromNonStringIdentifier(id, typeof(T), false));
  142. return Lazily.LoadAsync<T>(documentKeys, null, token);
  143. }
  144. Lazy<Task<T[]>> IAsyncLazySessionOperations.LoadAsync<T>(IEnumerable<ValueType> ids, Action<T[]> onEval, CancellationToken token)
  145. {
  146. var documentKeys = ids.Select(id => Conventions.FindFullDocumentKeyFromNonStringIdentifier(id, typeof(T), false));
  147. return LazyLoadInternal(documentKeys.ToArray(), new KeyValuePair<string, Type>[0], onEval, token);
  148. }
  149. Lazy<Task<TResult>> IAsyncLazySessionOperations.LoadAsync<TTransformer, TResult>(string id, Action<ILoadConfiguration> configure, Action<TResult> onEval, CancellationToken token)
  150. {
  151. return Lazily.LoadAsync(id, typeof(TTransformer), configure, onEval, token);
  152. }
  153. Lazy<Task<TResult>> IAsyncLazySessionOperations.LoadAsync<TResult>(string id, Type transformerType, Action<ILoadConfiguration> configure, Action<TResult> onEval, CancellationToken token)
  154. {
  155. var transformer = ((AbstractTransformerCreationTask)Activator.CreateInstance(transformerType)).TransformerName;
  156. var ids = new[] { id };
  157. var configuration = new RavenLoadConfiguration();
  158. if (configure != null)
  159. configure(configuration);
  160. var lazyLoadOperation = new LazyTransformerLoadOperation<TResult>(
  161. ids,
  162. transformer,
  163. configuration.TransformerParameters,
  164. new LoadTransformerOperation(this, transformer, ids),
  165. singleResult: true);
  166. return AddLazyOperation(lazyLoadOperation, onEval, token);
  167. }
  168. public Lazy<Task<TResult[]>> MoreLikeThisAsync<TResult>(MoreLikeThisQuery query, CancellationToken token = default (CancellationToken))
  169. {
  170. var multiLoadOperation = new MultiLoadOperation(this, AsyncDatabaseCommands.DisableAllCaching, null, null);
  171. var lazyOp = new LazyMoreLikeThisOperation<TResult>(multiLoadOperation, query);
  172. return AddLazyOperation<TResult[]>(lazyOp, null, token);
  173. }
  174. public Task<FacetResults[]> MultiFacetedSearchAsync(params FacetQuery[] queries)
  175. {
  176. IncrementRequestCount();
  177. return AsyncDatabaseCommands.GetMultiFacetsAsync(queries);
  178. }
  179. public string GetDocumentUrl(object entity)
  180. {
  181. DocumentMetadata value;
  182. if (entitiesAndMetadata.TryGetValue(entity, out value) == false)
  183. throw new InvalidOperationException("Could not figure out identifier for transient instance");
  184. return AsyncDatabaseCommands.UrlFor(value.Key);
  185. }
  186. Lazy<Task<T[]>> IAsyncLazySessionOperations.LoadStartingWithAsync<T>(string keyPrefix, string matches, int start, int pageSize, string exclude, RavenPagingInformation pagingInformation, string skipAfter, CancellationToken token)
  187. {
  188. var operation = new LazyStartsWithOperation<T>(keyPrefix, matches, exclude, start, pageSize, this, pagingInformation, skipAfter);
  189. return AddLazyOperation<T[]>(operation, null, token);
  190. }
  191. /// <summary>
  192. /// Loads the specified entities with the specified id after applying
  193. /// conventions on the provided id to get the real document id.
  194. /// </summary>
  195. /// <remarks>
  196. /// This method allows you to call:
  197. /// Load{Post}(1)
  198. /// And that call will internally be translated to
  199. /// Load{Post}("posts/1");
  200. ///
  201. /// Or whatever your conventions specify.
  202. /// </remarks>
  203. Lazy<Task<T>> IAsyncLazySessionOperations.LoadAsync<T>(ValueType id, CancellationToken token)
  204. {
  205. return Lazily.LoadAsync(id, (Action<T>)null, token);
  206. }
  207. internal Lazy<Task<T>> AddLazyOperation<T>(ILazyOperation operation, Action<T> onEval, CancellationToken token = default (CancellationToken))
  208. {
  209. pendingLazyOperations.Add(operation);
  210. var lazyValue = new Lazy<Task<T>>(() =>
  211. ExecuteAllPendingLazyOperationsAsync(token)
  212. .ContinueWith(t =>
  213. {
  214. if(t.Exception != null)
  215. throw new InvalidOperationException("Could not perform add lazy operation", t.Exception);
  216. return (T)operation.Result;
  217. }, token));
  218. if (onEval != null)
  219. onEvaluateLazy[operation] = theResult => onEval((T)theResult);
  220. return lazyValue;
  221. }
  222. internal Lazy<Task<int>> AddLazyCountOperation(ILazyOperation operation, CancellationToken token = default (CancellationToken))
  223. {
  224. pendingLazyOperations.Add(operation);
  225. var lazyValue = new Lazy<Task<int>>(() => ExecuteAllPendingLazyOperationsAsync(token)
  226. .ContinueWith(t =>
  227. {
  228. if(t.Exception != null)
  229. throw new InvalidOperationException("Could not perform lazy count", t.Exception);
  230. return operation.QueryResult.TotalResults;
  231. }));
  232. return lazyValue;
  233. }
  234. public async Task<ResponseTimeInformation> ExecuteAllPendingLazyOperationsAsync(CancellationToken token = default (CancellationToken))
  235. {
  236. if (pendingLazyOperations.Count == 0)
  237. return new ResponseTimeInformation();
  238. try
  239. {
  240. var sw = Stopwatch.StartNew();
  241. IncrementRequestCount();
  242. var responseTimeDuration = new ResponseTimeInformation();
  243. while (await ExecuteLazyOperationsSingleStep(responseTimeDuration).WithCancellation(token).ConfigureAwait(false))
  244. {
  245. await Task.Delay(100).WithCancellation(token).ConfigureAwait(false);
  246. }
  247. responseTimeDuration.ComputeServerTotal();
  248. foreach (var pendingLazyOperation in pendingLazyOperations)
  249. {
  250. Action<object> value;
  251. if (onEvaluateLazy.TryGetValue(pendingLazyOperation, out value))
  252. value(pendingLazyOperation.Result);
  253. }
  254. responseTimeDuration.TotalClientDuration = sw.Elapsed;
  255. return responseTimeDuration;
  256. }
  257. finally
  258. {
  259. pendingLazyOperations.Clear();
  260. }
  261. }
  262. private async Task<bool> ExecuteLazyOperationsSingleStep(ResponseTimeInformation responseTimeInformation)
  263. {
  264. var disposables = pendingLazyOperations.Select(x => x.EnterContext()).Where(x => x != null).ToList();
  265. try
  266. {
  267. var requests = pendingLazyOperations.Select(x => x.CreateRequest()).ToArray();
  268. var responses = await AsyncDatabaseCommands.MultiGetAsync(requests).ConfigureAwait(false);
  269. for (int i = 0; i < pendingLazyOperations.Count; i++)
  270. {
  271. long totalTime;
  272. long.TryParse(responses[i].Headers["Temp-Request-Time"], out totalTime);
  273. responseTimeInformation.DurationBreakdown.Add(new ResponseTimeItem
  274. {
  275. Url = requests[i].UrlAndQuery,
  276. Duration = TimeSpan.FromMilliseconds(totalTime)
  277. });
  278. if (responses[i].RequestHasErrors())
  279. {
  280. throw new InvalidOperationException("Got an error from server, status code: " + responses[i].Status +
  281. Environment.NewLine + responses[i].Result);
  282. }
  283. pendingLazyOperations[i].HandleResponse(responses[i]);
  284. if (pendingLazyOperations[i].RequiresRetry)
  285. {
  286. return true;
  287. }
  288. }
  289. return false;
  290. }
  291. finally
  292. {
  293. foreach (var disposable in disposables)
  294. {
  295. disposable.Dispose();
  296. }
  297. }
  298. }
  299. /// <summary>
  300. /// Register to lazily load documents and include
  301. /// </summary>
  302. public Lazy<Task<T[]>> LazyLoadInternal<T>(string[] ids, KeyValuePair<string, Type>[] includes, Action<T[]> onEval, CancellationToken token = default (CancellationToken))
  303. {
  304. var multiLoadOperation = new MultiLoadOperation(this, AsyncDatabaseCommands.DisableAllCaching, ids, includes);
  305. var lazyOp = new LazyMultiLoadOperation<T>(multiLoadOperation, ids, includes);
  306. return AddLazyOperation(lazyOp, onEval,token);
  307. }
  308. /// <summary>
  309. /// Load documents with the specified key prefix
  310. /// </summary>
  311. public Task<IEnumerable<T>> LoadStartingWithAsync<T>(string keyPrefix, string matches = null, int start = 0, int pageSize = 25, string exclude = null, RavenPagingInformation pagingInformation = null, string skipAfter = null, CancellationToken token = default (CancellationToken))
  312. {
  313. return AsyncDatabaseCommands.StartsWithAsync(keyPrefix, matches, start, pageSize, exclude: exclude, pagingInformation: pagingInformation, skipAfter: skipAfter, token: token)
  314. .ContinueWith(task => (IEnumerable<T>)task.Result.Select(TrackEntity<T>).ToList(), token);
  315. }
  316. public Task<IEnumerable<TResult>> LoadStartingWithAsync<TTransformer, TResult>(string keyPrefix, string matches = null, int start = 0, int pageSize = 25,
  317. string exclude = null, RavenPagingInformation pagingInformation = null,
  318. Action<ILoadConfiguration> configure = null,
  319. string skipAfter = null, CancellationToken token = default (CancellationToken)) where TTransformer : AbstractTransformerCreationTask, new()
  320. {
  321. var transformer = new TTransformer().TransformerName;
  322. var configuration = new RavenLoadConfiguration();
  323. if (configure != null)
  324. {
  325. configure(configuration);
  326. }
  327. var queryOperation = new QueryOperation(this, "Load/StartingWith", null, null, false, TimeSpan.Zero, null, null, false);
  328. return AsyncDatabaseCommands.StartsWithAsync(keyPrefix, matches, start, pageSize, exclude: exclude,
  329. pagingInformation: pagingInformation, transformer: transformer,
  330. transformerParameters: configuration.TransformerParameters,
  331. skipAfter: skipAfter, token: token)
  332. .ContinueWith(
  333. task => (IEnumerable<TResult>) task.Result.Select(x=> queryOperation.Deserialize<TResult>(x.ToJson())).ToList(), token);
  334. }
  335. public Task<IAsyncEnumerator<StreamResult<T>>> StreamAsync<T>(IAsyncDocumentQuery<T> query, CancellationToken token = default (CancellationToken))
  336. {
  337. return StreamAsync(query, new Reference<QueryHeaderInformation>(), token);
  338. }
  339. public Task<IAsyncEnumerator<StreamResult<T>>> StreamAsync<T>(IQueryable<T> query, CancellationToken token = default (CancellationToken))
  340. {
  341. return StreamAsync(query, new Reference<QueryHeaderInformation>(), token);
  342. }
  343. public async Task<IAsyncEnumerator<StreamResult<T>>> StreamAsync<T>(IQueryable<T> query, Reference<QueryHeaderInformation> queryHeaderInformation, CancellationToken token = default (CancellationToken))
  344. {
  345. var queryInspector = (IRavenQueryProvider)query.Provider;
  346. var indexQuery = queryInspector.ToAsyncDocumentQuery<T>(query.Expression);
  347. return await StreamAsync(indexQuery, queryHeaderInformation, token).ConfigureAwait(false);
  348. }
  349. public async Task<IAsyncEnumerator<StreamResult<T>>> StreamAsync<T>(IAsyncDocumentQuery<T> query, Reference<QueryHeaderInformation> queryHeaderInformation, CancellationToken token = default (CancellationToken))
  350. {
  351. var indexQuery = query.GetIndexQuery(true);
  352. bool waitForNonStaleResultsWasSetGloably = this.Advanced.DocumentStore.Conventions.DefaultQueryingConsistency == ConsistencyOptions.AlwaysWaitForNonStaleResultsAsOfLastWrite;
  353. if (!waitForNonStaleResultsWasSetGloably && (indexQuery.WaitForNonStaleResults || indexQuery.WaitForNonStaleResultsAsOfNow))
  354. throw new NotSupportedException(
  355. "Since Stream() does not wait for indexing (by design), streaming query with WaitForNonStaleResults is not supported.");
  356. var enumerator = await AsyncDatabaseCommands.StreamQueryAsync(query.AsyncIndexQueried, indexQuery, queryHeaderInformation, token).ConfigureAwait(false);
  357. var queryOperation = ((AsyncDocumentQuery<T>)query).InitializeQueryOperation();
  358. queryOperation.DisableEntitiesTracking = true;
  359. return new QueryYieldStream<T>(this, enumerator, queryOperation,query, token);
  360. }
  361. public Task<IAsyncEnumerator<StreamResult<T>>> StreamAsync<T>(Etag fromEtag, int start = 0,
  362. int pageSize = Int32.MaxValue, RavenPagingInformation pagingInformation = null, string transformer = null, Dictionary<string, RavenJToken> transformerParameters = null, CancellationToken token = default (CancellationToken))
  363. {
  364. return StreamAsync<T>(fromEtag: fromEtag, startsWith: null, matches: null, start: start, pageSize: pageSize, pagingInformation: pagingInformation, transformer: transformer, transformerParameters: transformerParameters, token: token);
  365. }
  366. public Task<IAsyncEnumerator<StreamResult<T>>> StreamAsync<T>(string startsWith, string matches = null, int start = 0,
  367. int pageSize = Int32.MaxValue, RavenPagingInformation pagingInformation = null, string skipAfter = null, string transformer = null, Dictionary<string, RavenJToken> transformerParameters = null, CancellationToken token = default (CancellationToken))
  368. {
  369. return StreamAsync<T>(fromEtag: null, startsWith: startsWith, matches: matches, start: start, pageSize: pageSize, pagingInformation: pagingInformation, skipAfter: skipAfter, transformer: transformer, transformerParameters: transformerParameters, token: token);
  370. }
  371. private async Task<IAsyncEnumerator<StreamResult<T>>> StreamAsync<T>(Etag fromEtag, string startsWith, string matches, int start, int pageSize, RavenPagingInformation pagingInformation = null, string skipAfter = null, string transformer = null, Dictionary<string, RavenJToken> transformerParameters = null, CancellationToken token = default (CancellationToken))
  372. {
  373. var enumerator = await AsyncDatabaseCommands.StreamDocsAsync(fromEtag, startsWith, matches, start, pageSize, pagingInformation: pagingInformation, skipAfter: skipAfter, transformer: transformer, transformerParameters: transformerParameters, token: token).ConfigureAwait(false);
  374. return new DocsYieldStream<T>(this, enumerator, token);
  375. }
  376. public abstract class YieldStream<T> : IAsyncEnumerator<StreamResult<T>>
  377. {
  378. protected readonly AsyncDocumentSession parent;
  379. protected readonly IAsyncEnumerator<RavenJObject> enumerator;
  380. protected CancellationToken token;
  381. protected YieldStream(AsyncDocumentSession parent, IAsyncEnumerator<RavenJObject> enumerator, CancellationToken token)
  382. {
  383. this.parent = parent;
  384. this.enumerator = enumerator;
  385. }
  386. public void Dispose()
  387. {
  388. enumerator.Dispose();
  389. }
  390. public async Task<bool> MoveNextAsync()
  391. {
  392. if (await enumerator.MoveNextAsync().WithCancellation(token).ConfigureAwait(false) == false)
  393. {
  394. this.Dispose();
  395. return false;
  396. }
  397. SetCurrent();
  398. return true;
  399. }
  400. protected abstract void SetCurrent();
  401. public StreamResult<T> Current { get; protected set; }
  402. }
  403. public class QueryYieldStream<T> : YieldStream<T>
  404. {
  405. private readonly QueryOperation queryOperation;
  406. private readonly IAsyncDocumentQuery<T> query;
  407. public QueryYieldStream(AsyncDocumentSession parent, IAsyncEnumerator<RavenJObject> enumerator, QueryOperation queryOperation, IAsyncDocumentQuery<T> query, CancellationToken token = default (CancellationToken))
  408. : base(parent, enumerator, token)
  409. {
  410. this.queryOperation = queryOperation;
  411. this.query = query;
  412. }
  413. protected override void SetCurrent()
  414. {
  415. var ravenJObject = enumerator.Current;
  416. query.InvokeAfterStreamExecuted(ref ravenJObject);
  417. var meta = ravenJObject.Value<RavenJObject>(Constants.Metadata);
  418. string key = null;
  419. Etag etag = null;
  420. if (meta != null)
  421. {
  422. key = meta.Value<string>("@id") ??
  423. meta.Value<string>(Constants.DocumentIdFieldName) ??
  424. ravenJObject.Value<string>(Constants.DocumentIdFieldName);
  425. var value = meta.Value<string>("@etag");
  426. if (value != null)
  427. etag = Etag.Parse(value);
  428. }
  429. Current = new StreamResult<T>
  430. {
  431. Document = queryOperation.Deserialize<T>(ravenJObject),
  432. Etag = etag,
  433. Key = key,
  434. Metadata = meta
  435. };
  436. }
  437. }
  438. public class DocsYieldStream<T> : YieldStream<T>
  439. {
  440. public DocsYieldStream(AsyncDocumentSession parent, IAsyncEnumerator<RavenJObject> enumerator, CancellationToken token)
  441. : base(parent, enumerator, token)
  442. {
  443. }
  444. protected override void SetCurrent()
  445. {
  446. var document = SerializationHelper.RavenJObjectToJsonDocument(enumerator.Current);
  447. Current = new StreamResult<T>
  448. {
  449. Document = (T)parent.ConvertToEntity(typeof(T),
  450. document.Key,
  451. document.DataAsJson,
  452. document.Metadata, true),
  453. Etag = document.Etag,
  454. Key = document.Key,
  455. Metadata = document.Metadata
  456. };
  457. }
  458. }
  459. /// <summary>
  460. /// Queries the index specified by <typeparamref name="TIndexCreator"/> using lucene syntax.
  461. /// </summary>
  462. /// <typeparam name="T">The result of the query</typeparam>
  463. /// <typeparam name="TIndexCreator">The type of the index creator.</typeparam>
  464. /// <returns></returns>
  465. [Obsolete("Use AsyncDocumentQuery instead")]
  466. public IAsyncDocumentQuery<T> AsyncLuceneQuery<T, TIndexCreator>() where TIndexCreator : AbstractIndexCreationTask, new()
  467. {
  468. return AsyncDocumentQuery<T, TIndexCreator>();
  469. }
  470. /// <summary>
  471. /// Queries the index specified by <typeparamref name="TIndexCreator"/> using lucene syntax.
  472. /// </summary>
  473. /// <typeparam name="T">The result of the query</typeparam>
  474. /// <typeparam name="TIndexCreator">The type of the index creator.</typeparam>
  475. /// <returns></returns>
  476. public IAsyncDocumentQuery<T> AsyncDocumentQuery<T, TIndexCreator>() where TIndexCreator : AbstractIndexCreationTask, new()
  477. {
  478. var index = new TIndexCreator();
  479. return AsyncDocumentQuery<T>(index.IndexName, index.IsMapReduce);
  480. }
  481. /// <summary>
  482. /// Query the specified index using Lucene syntax
  483. /// </summary>
  484. [Obsolete("Use AsyncDocumentQuery instead.")]
  485. public IAsyncDocumentQuery<T> AsyncLuceneQuery<T>(string index, bool isMapReduce)
  486. {
  487. return AsyncDocumentQuery<T>(index, isMapReduce);
  488. }
  489. /// <summary>
  490. /// Query the specified index using Lucene syntax
  491. /// </summary>
  492. public IAsyncDocumentQuery<T> AsyncDocumentQuery<T>(string index, bool isMapReduce)
  493. {
  494. return new AsyncDocumentQuery<T>(this,null,AsyncDatabaseCommands, index, new string[0], new string[0], theListeners.QueryListeners, isMapReduce);
  495. }
  496. /// <summary>
  497. /// Dynamically query RavenDB using Lucene syntax
  498. /// </summary>
  499. [Obsolete("Use AsyncDocumentQuery instead.")]
  500. public IAsyncDocumentQuery<T> AsyncLuceneQuery<T>()
  501. {
  502. return AsyncDocumentQuery<T>();
  503. }
  504. /// <summary>
  505. /// Dynamically query RavenDB using Lucene syntax
  506. /// </summary>
  507. public IAsyncDocumentQuery<T> AsyncDocumentQuery<T>()
  508. {
  509. var indexName = CreateDynamicIndexName<T>();
  510. return new AsyncDocumentQuery<T>(this, null, AsyncDatabaseCommands, indexName, new string[0], new string[0], theListeners.QueryListeners, false);
  511. }
  512. /// <summary>
  513. /// Get the accessor for advanced operations
  514. /// </summary>
  515. /// <remarks>
  516. /// Those operations are rarely needed, and have been moved to a separate
  517. /// property to avoid cluttering the API
  518. /// </remarks>
  519. public IAsyncAdvancedSessionOperations Advanced
  520. {
  521. get { return this; }
  522. }
  523. /// <summary>
  524. /// Begin a load while including the specified path
  525. /// </summary>
  526. /// <param name="path">The path.</param>
  527. public IAsyncLoaderWithInclude<object> Include(string path)
  528. {
  529. return new AsyncMultiLoaderWithInclude<object>(this).Include(path);
  530. }
  531. /// <summary>
  532. /// Begin a load while including the specified path
  533. /// </summary>
  534. /// <param name="path">The path.</param>
  535. public IAsyncLoaderWithInclude<T> Include<T>(Expression<Func<T, object>> path)
  536. {
  537. return new AsyncMultiLoaderWithInclude<T>(this).Include(path);
  538. }
  539. /// <summary>
  540. /// Begin a load while including the specified path
  541. /// </summary>
  542. /// <param name="path">The path.</param>
  543. public IAsyncLoaderWithInclude<T> Include<T, TInclude>(Expression<Func<T, object>> path)
  544. {
  545. return new AsyncMultiLoaderWithInclude<T>(this).Include<TInclude>(path);
  546. }
  547. /// <summary>
  548. /// Begins the async load operation, with the specified id after applying
  549. /// conventions on the provided id to get the real document id.
  550. /// </summary>
  551. /// <remarks>
  552. /// This method allows you to call:
  553. /// LoadAsync{Post}(1)
  554. /// And that call will internally be translated to
  555. /// LoadAsync{Post}("posts/1");
  556. ///
  557. /// Or whatever your conventions specify.
  558. /// </remarks>
  559. public Task<T> LoadAsync<T>(ValueType id, CancellationToken token = default (CancellationToken))
  560. {
  561. var documentKey = Conventions.FindFullDocumentKeyFromNonStringIdentifier(id, typeof(T), false);
  562. return LoadAsync<T>(documentKey, token);
  563. }
  564. /// <summary>
  565. /// Begins the async multi-load operation, with the specified ids after applying
  566. /// conventions on the provided ids to get the real document ids.
  567. /// </summary>
  568. /// <remarks>
  569. /// This method allows you to call:
  570. /// LoadAsync{Post}(1,2,3)
  571. /// And that call will internally be translated to
  572. /// LoadAsync{Post}("posts/1","posts/2","posts/3");
  573. ///
  574. /// Or whatever your conventions specify.
  575. /// </remarks>
  576. public Task<T[]> LoadAsync<T>(CancellationToken token = default (CancellationToken),params ValueType[] ids)
  577. {
  578. var documentKeys = ids.Select(id => Conventions.FindFullDocumentKeyFromNonStringIdentifier(id, typeof(T), false));
  579. return LoadAsync<T>(documentKeys, token);
  580. }
  581. /// <summary>
  582. /// Begins the async multi-load operation, with the specified ids after applying
  583. /// conventions on the provided ids to get the real document ids.
  584. /// </summary>
  585. /// <remarks>
  586. /// This method allows you to call:
  587. /// LoadAsync{Post}(new List&lt;int&gt;(){1,2,3})
  588. /// And that call will internally be translated to
  589. /// LoadAsync{Post}("posts/1","posts/2","posts/3");
  590. ///
  591. /// Or whatever your conventions specify.
  592. /// </remarks>
  593. public Task<T[]> LoadAsync<T>(IEnumerable<ValueType> ids)
  594. {
  595. return LoadAsync<T>(ids, new CancellationToken());
  596. }
  597. /// <summary>
  598. /// Begins the async multi-load operation, with the specified ids after applying
  599. /// conventions on the provided ids to get the real document ids.
  600. /// </summary>
  601. /// <remarks>
  602. /// This method allows you to call:
  603. /// LoadAsync{Post}(new List&lt;int&gt;(){1,2,3})
  604. /// And that call will internally be translated to
  605. /// LoadAsync{Post}("posts/1","posts/2","posts/3");
  606. ///
  607. /// Or whatever your conventions specify.
  608. /// </remarks>
  609. public Task<T[]> LoadAsync<T>(IEnumerable<ValueType> ids, CancellationToken token = default (CancellationToken))
  610. {
  611. var documentKeys = ids.Select(id => Conventions.FindFullDocumentKeyFromNonStringIdentifier(id, typeof(T), false));
  612. return LoadAsync<T>(documentKeys, token);
  613. }
  614. /// <summary>
  615. /// Begins the async load operation
  616. /// </summary>
  617. /// <param name="id">The id.</param>
  618. /// <param name="token">The canecllation token.</param>
  619. /// <returns></returns>
  620. public async Task<T> LoadAsync<T>(string id, CancellationToken token = default (CancellationToken))
  621. {
  622. if (id == null)
  623. throw new ArgumentNullException("id", "The document id cannot be null");
  624. object entity;
  625. if (entitiesByKey.TryGetValue(id, out entity))
  626. {
  627. return (T) entity;
  628. }
  629. JsonDocument value;
  630. if (includedDocumentsByKey.TryGetValue(id, out value))
  631. {
  632. includedDocumentsByKey.Remove(id);
  633. return TrackEntity<T>(value);
  634. }
  635. if (IsDeleted(id))
  636. return default(T);
  637. IncrementRequestCount();
  638. var loadOperation = new LoadOperation(this, AsyncDatabaseCommands.DisableAllCaching, id);
  639. return await CompleteLoadAsync<T>(id, loadOperation, token).ConfigureAwait(false);
  640. }
  641. private async Task<T> CompleteLoadAsync<T>(string id, LoadOperation loadOperation, CancellationToken token = default (CancellationToken))
  642. {
  643. loadOperation.LogOperation();
  644. using (loadOperation.EnterLoadContext())
  645. {
  646. var result = await AsyncDatabaseCommands.GetAsync(id, token).ConfigureAwait(false);
  647. if (loadOperation.SetResult(result) == false)
  648. return loadOperation.Complete<T>();
  649. return await CompleteLoadAsync<T>(id, loadOperation, token).WithCancellation(token).ConfigureAwait(false);
  650. }
  651. }
  652. public Task<T[]> LoadAsync<T>(IEnumerable<string> ids, CancellationToken token = default (CancellationToken))
  653. {
  654. return LoadAsyncInternal<T>(ids.ToArray(), token);
  655. }
  656. public async Task<T> LoadAsync<TTransformer, T>(string id, Action<ILoadConfiguration> configure = null, CancellationToken token = default (CancellationToken)) where TTransformer : AbstractTransformerCreationTask, new()
  657. {
  658. var result = await LoadAsync<TTransformer, T>(new[] { id }.AsEnumerable(), configure, token).ConfigureAwait(false);
  659. return result.FirstOrDefault();
  660. }
  661. public async Task<TResult[]> LoadAsync<TTransformer, TResult>(IEnumerable<string> ids, Action<ILoadConfiguration> configure = null, CancellationToken token = default (CancellationToken)) where TTransformer : AbstractTransformerCreationTask, new()
  662. {
  663. var transformer = new TTransformer();
  664. var configuration = new RavenLoadConfiguration();
  665. if (configure != null)
  666. configure(configuration);
  667. var result = await LoadUsingTransformerInternalAsync<TResult>(ids.ToArray(), null, transformer.TransformerName, configuration.TransformerParameters, token).ConfigureAwait(false);
  668. return result;
  669. }
  670. public async Task<TResult> LoadAsync<TResult>(string id, string transformer, Action<ILoadConfiguration> configure = null, CancellationToken token = default (CancellationToken))
  671. {
  672. var configuration = new RavenLoadConfiguration();
  673. if (configure != null)
  674. configure(configuration);
  675. var result = await LoadUsingTransformerInternalAsync<TResult>(new[] { id }, null, transformer, configuration.TransformerParameters, token).ConfigureAwait(false);
  676. return result.FirstOrDefault();
  677. }
  678. public async Task<TResult[]> LoadAsync<TResult>(IEnumerable<string> ids, string transformer, Action<ILoadConfiguration> configure = null, CancellationToken token = default (CancellationToken))
  679. {
  680. var configuration = new RavenLoadConfiguration();
  681. if (configure != null)
  682. configure(configuration);
  683. return await LoadUsingTransformerInternalAsync<TResult>(ids.ToArray(), null, transformer, configuration.TransformerParameters, token).ConfigureAwait(false);
  684. }
  685. public async Task<TResult> LoadAsync<TResult>(string id, Type transformerType, Action<ILoadConfiguration> configure = null, CancellationToken token = default (CancellationToken))
  686. {
  687. var configuration = new RavenLoadConfiguration();
  688. if (configure != null)
  689. configure(configuration);
  690. var transformer = ((AbstractTransformerCreationTask)Activator.CreateInstance(transformerType)).TransformerName;
  691. var result = await LoadUsingTransformerInternalAsync<TResult>(new[] { id }, null, transformer, configuration.TransformerParameters, token).ConfigureAwait(false);
  692. return result.FirstOrDefault();
  693. }
  694. public async Task<TResult[]> LoadAsync<TResult>(IEnumerable<string> ids, Type transformerType, Action<ILoadConfiguration> configure = null, CancellationToken token = default (CancellationToken))
  695. {
  696. var configuration = new RavenLoadConfiguration();
  697. if (configure != null)
  698. configure(configuration);
  699. var transformer = ((AbstractTransformerCreationTask)Activator.CreateInstance(transformerType)).TransformerName;
  700. return await LoadUsingTransformerInternalAsync<TResult>(ids.ToArray(), null, transformer, configuration.TransformerParameters, token).ConfigureAwait(false);
  701. }
  702. public async Task<T[]> LoadUsingTransformerInternalAsync<T>(string[] ids, KeyValuePair<string, Type>[] includes, string transformer, Dictionary<string, RavenJToken> transformerParameters = null, CancellationToken token = default (CancellationToken))
  703. {
  704. if (transformer == null)
  705. throw new ArgumentNullException("transformer");
  706. if (ids.Length == 0)
  707. return new T[0];
  708. IncrementRequestCount();
  709. var includeNames = includes != null ? includes.Select(x=>x.Key).ToArray() : new string[0];
  710. var multiLoadResult = await AsyncDatabaseCommands.GetAsync(ids, includeNames, transformer, transformerParameters, token: token).ConfigureAwait(false);
  711. return new LoadTransformerOperation(this, transformer, ids).Complete<T>(multiLoadResult);
  712. }
  713. public Lazy<Task<T[]>> LazyAsyncLoadInternal<T>(string[] ids, KeyValuePair<string, Type>[] includes, Action<T[]> onEval, CancellationToken token = default (CancellationToken))
  714. {
  715. if (CheckIfIdAlreadyIncluded(ids, includes))
  716. {
  717. return new Lazy<Task<T[]>>(async () => await Task.WhenAll(ids.Select(id => LoadAsync<T>(id,token)).ToArray()).WithCancellation(token).ConfigureAwait(false));
  718. }
  719. var multiLoadOperation = new MultiLoadOperation(this, AsyncDatabaseCommands.DisableAllCaching, ids, includes);
  720. var lazyOp = new LazyMultiLoadOperation<T>(multiLoadOperation, ids, includes);
  721. return AddLazyOperation(lazyOp, onEval);
  722. }
  723. /// <summary>
  724. /// Begins the async multi load operation
  725. /// </summary>
  726. public async Task<T[]> LoadAsyncInternal<T>(string[] ids, KeyValuePair<string, Type>[] includes,CancellationToken token = default (CancellationToken))
  727. {
  728. if (CheckIfIdAlreadyIncluded(ids, includes))
  729. {
  730. var loadTasks = ids.Select(id => LoadAsync<T>(id,token)).ToArray();
  731. var loadedData = await Task.WhenAll(loadTasks).WithCancellation(token).ConfigureAwait(false);
  732. return loadedData;
  733. }
  734. IncrementRequestCount();
  735. var multiLoadOperation = new MultiLoadOperation(this, AsyncDatabaseCommands.DisableAllCaching, ids, includes);
  736. multiLoadOperation.LogOperation();
  737. var includePaths = includes != null ? includes.Select(x => x.Key).ToArray() : null;
  738. MultiLoadResult result;
  739. do
  740. {
  741. multiLoadOperation.LogOperation();
  742. using (multiLoadOperation.EnterMultiLoadContext())
  743. {
  744. result = await AsyncDatabaseCommands.GetAsync(ids, includePaths, token: token).ConfigureAwait(false);
  745. }
  746. } while (multiLoadOperation.SetResult(result));
  747. return multiLoadOperation.Complete<T>();
  748. }
  749. /// <summary>
  750. /// Begins the async multi load operation
  751. /// </summary>
  752. public async Task<T[]> LoadAsyncInternal<T>(string[] ids, CancellationToken token = default(CancellationToken))
  753. {
  754. if (ids.Length == 0)
  755. return new T[0];
  756. // only load documents that aren't already cached
  757. var idsOfNotExistingObjects = ids.Where(id => IsLoaded(id) == false && IsDeleted(id) == false)
  758. .Distinct(StringComparer.OrdinalIgnoreCase)
  759. .ToArray();
  760. if (idsOfNotExistingObjects.Length > 0)
  761. {
  762. IncrementRequestCount();
  763. var multiLoadOperation = new MultiLoadOperation(this, AsyncDatabaseCommands.DisableAllCaching, idsOfNotExistingObjects, null);
  764. MultiLoadResult multiLoadResult;
  765. do
  766. {
  767. multiLoadOperation.LogOperation();
  768. using (multiLoadOperation.EnterMultiLoadContext())
  769. {
  770. multiLoadResult = await AsyncDatabaseCommands.GetAsync(idsOfNotExistingObjects, null).ConfigureAwait(false);
  771. }
  772. } while (multiLoadOperation.SetResult(multiLoadResult));
  773. multiLoadOperation.Complete<T>();
  774. }
  775. var loadTasks = ids.Select(async id => await LoadAsync<T>(id, token).ConfigureAwait(false)).ToArray();
  776. var loadedData = await Task.WhenAll(loadTasks).WithCancellation(token).ConfigureAwait(false);
  777. return loadedData;
  778. }
  779. /// <summary>
  780. /// Begins the async save changes operation
  781. /// </summary>
  782. /// <returns></returns>
  783. public async Task SaveChangesAsync(CancellationToken token = default (CancellationToken))
  784. {
  785. await asyncDocumentKeyGeneration.GenerateDocumentKeysForSaveChanges().WithCancellation(token).ConfigureAwait(false);
  786. using (EntityToJson.EntitiesToJsonCachingScope())
  787. {
  788. var data = PrepareForSaveChanges();
  789. if (data.Commands.Count == 0)
  790. return;
  791. IncrementRequestCount();
  792. var result = await AsyncDatabaseCommands.BatchAsync(data.Commands.ToArray(), data.Options, token).ConfigureAwait(false);
  793. UpdateBatchResults(result, data);
  794. }
  795. }
  796. /// <summary>
  797. /// Get the json document by key from the store
  798. /// </summary>
  799. protected override JsonDocument GetJsonDocument(string documentKey)
  800. {
  801. throw new NotSupportedException("Cannot get a document in a synchronous manner using async document session");
  802. }
  803. #if !DNXCORE50
  804. /// <summary>
  805. /// Commits the specified tx id.
  806. /// </summary>
  807. /// <param name="txId">The tx id.</param>
  808. public override async Task Commit(string txId)
  809. {
  810. await AsyncDatabaseCommands.CommitAsync(txId).ConfigureAwait(false);
  811. ClearEnlistment();
  812. }
  813. /// <summary>
  814. /// Rollbacks the specified tx id.
  815. /// </summary>
  816. /// <param name="txId">The tx id.</param>
  817. public override async Task Rollback(string txId)
  818. {
  819. await AsyncDatabaseCommands.RollbackAsync(txId).ConfigureAwait(false);
  820. ClearEnlistment();
  821. }
  822. public async Task PrepareTransaction(string txId, Guid? resourceManagerId = null, byte[] recoveryInformation = null)
  823. {
  824. await AsyncDatabaseCommands.PrepareTransactionAsync(txId, resourceManagerId, recoveryInformation).ConfigureAwait(false);
  825. ClearEnlistment();
  826. }
  827. #endif
  828. /// <summary>
  829. /// Dynamically queries RavenDB using LINQ
  830. /// </summary>
  831. /// <typeparam name="T">The result of the query</typeparam>
  832. public IRavenQueryable<T> Query<T>()
  833. {
  834. string indexName = CreateDynamicIndexName<T>();
  835. return Query<T>(indexName);
  836. }
  837. public IRavenQueryable<T> Query<T, TIndexCreator>() where TIndexCreator : AbstractIndexCreationTask, new()
  838. {
  839. var indexCreator = new TIndexCreator();
  840. return Query<T>(indexCreator.IndexName, indexCreator.IsMapReduce);
  841. }
  842. public IRavenQueryable<T> Query<T>(string indexName, bool isMapReduce = false)
  843. {
  844. var ravenQueryStatistics = new RavenQueryStatistics();
  845. var highlightings = new RavenQueryHighlightings();
  846. var ravenQueryInspector = new RavenQueryInspector<T>();
  847. var ravenQueryProvider = new RavenQueryProvider<T>(this, indexName, ravenQueryStatistics, highlightings, null, AsyncDatabaseCommands, isMapReduce);
  848. ravenQueryInspector.Init(ravenQueryProvider,
  849. ravenQueryStatistics,
  850. highlightings,
  851. indexName,
  852. null,
  853. this, null, AsyncDatabaseCommands, isMapReduce);
  854. return ravenQueryInspector;
  855. }
  856. /// <summary>
  857. /// Create a new query for <typeparam name="T"/>
  858. /// </summary>
  859. IDocumentQuery<T> IDocumentQueryGenerator.Query<T>(string indexName, bool isMapReduce)
  860. {
  861. throw new NotSupportedException("You can't get a sync query from async session");
  862. }
  863. /// <summary>
  864. /// Create a new query for <typeparam name="T"/>
  865. /// </summary>
  866. public IAsyncDocumentQuery<T> AsyncQuery<T>(string indexName, bool isMapReduce = false)
  867. {
  868. return AsyncDocumentQuery<T>(indexName, isMapReduce);
  869. }
  870. public RavenQueryInspector<S> CreateRavenQueryInspector<S>()
  871. {
  872. return new RavenQueryInspector<S>();
  873. }
  874. protected override string GenerateKey(object entity)
  875. {
  876. throw new NotSupportedException("Async session cannot generate keys synchronously");
  877. }
  878. protected override void RememberEntityForDocumentKeyGeneration(object entity)
  879. {
  880. asyncDocumentKeyGeneration.Add(entity);
  881. }
  882. protected override Task<string> GenerateKeyAsync(object entity)
  883. {
  884. return Conventions.GenerateDocumentKeyAsync(dbName, AsyncDatabaseCommands, entity);
  885. }
  886. public async Task RefreshAsync<T>(T entity, CancellationToken token = default (CancellationToken))
  887. {
  888. DocumentMetadata value;
  889. if (entitiesAndMetadata.TryGetValue(entity, out value) == false)
  890. throw new InvalidOperationException("Cannot refresh a transient instance");
  891. IncrementRequestCount();
  892. var jsonDocument = await AsyncDatabaseCommands.GetAsync(value.Key, token).ConfigureAwait(false);
  893. RefreshInternal(entity, jsonDocument, value);
  894. }
  895. public async Task<RavenJObject> GetMetadataForAsync<T>(T instance)
  896. {
  897. var metadata = await GetDocumentMetadataAsync(instance).ConfigureAwait(false);
  898. return metadata.Metadata;
  899. }
  900. private async Task<DocumentMetadata> GetDocumentMetadataAsync<T>(T instance)
  901. {
  902. DocumentMetadata value;
  903. if (entitiesAndMetadata.TryGetValue(instance, out value) == false)
  904. {
  905. string id;
  906. if (GenerateEntityIdOnTheClient.TryGetIdFromInstance(instance, out id)
  907. || (instance is IDynamicMetaObjectProvider &&
  908. GenerateEntityIdOnTheClient.TryGetIdFromDynamic(instance, out id)))
  909. {
  910. AssertNoNonUniqueInstance(instance, id);
  911. var jsonDocument = await GetJsonDocumentAsync(id).ConfigureAwait(false);
  912. value = GetDocumentMetadataValue(instance, id, jsonDocument);
  913. }
  914. else
  915. {
  916. throw new InvalidOperationException("Could not find the document key for " + instance);
  917. }
  918. }
  919. return value;
  920. }
  921. /// <summary>
  922. /// Get the json document by key from the store
  923. /// </summary>
  924. private async Task<JsonDocument> GetJsonDocumentAsync(string documentKey)
  925. {
  926. var jsonDocument = await AsyncDatabaseCommands.GetAsync(documentKey).ConfigureAwait(false);
  927. if (jsonDocument == null)
  928. throw new InvalidOperationException("Document '" + documentKey + "' no longer exists and was probably deleted");
  929. return jsonDocument;
  930. }
  931. public async Task<Operation> DeleteByIndexAsync<T, TIndexCreator>(Expression<Func<T, bool>> expression) where TIndexCreator : AbstractIndexCreationTask, new()
  932. {
  933. var indexCreator = new TIndexCreator();
  934. var operation = await DeleteByIndexAsync<T>(indexCreator.IndexName, expression).ConfigureAwait(false);
  935. return operation;
  936. }
  937. public async Task<Operation> DeleteByIndexAsync<T>(string indexName, Expression<Func<T, bool>> expression)
  938. {
  939. var query = Query<T>(indexName).Where(expression);
  940. var indexQuery = new IndexQuery()
  941. {
  942. Query = query.ToString()
  943. };
  944. return await AsyncDatabaseCommands.DeleteByIndexAsync(indexName, indexQuery).ConfigureAwait(false);
  945. }
  946. }
  947. }