PageRenderTime 67ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/Raven.Client.Lightweight/Shard/ShardedDocumentSession.cs

http://github.com/ayende/ravendb
C# | 1008 lines | 783 code | 148 blank | 77 comment | 75 complexity | c4f8a77e91095d7e7338a751ff80bf5e 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="ShardedDocumentSession.cs" company="Hibernating Rhinos LTD">
  3. // Copyright (c) Hibernating Rhinos LTD. All rights reserved.
  4. // </copyright>
  5. //-----------------------------------------------------------------------
  6. using System.Reflection;
  7. using System.Threading;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Diagnostics;
  11. using System.Linq;
  12. using System.Linq.Expressions;
  13. using System.Text;
  14. using System.Threading.Tasks;
  15. using Raven.Abstractions.Data;
  16. using Raven.Abstractions.Extensions;
  17. using Raven.Client.Connection;
  18. using Raven.Client.Document;
  19. using Raven.Client.Document.SessionOperations;
  20. using Raven.Client.Indexes;
  21. using Raven.Client.Linq;
  22. using Raven.Json.Linq;
  23. using Raven.Client.Connection.Async;
  24. using Raven.Client.Document.Batches;
  25. namespace Raven.Client.Shard
  26. {
  27. /// <summary>
  28. /// Implements Unit of Work for accessing a set of sharded RavenDB servers
  29. /// </summary>
  30. public class ShardedDocumentSession : BaseShardedDocumentSession<IDatabaseCommands>, IDocumentQueryGenerator,
  31. IDocumentSessionImpl, ISyncAdvancedSessionOperation
  32. {
  33. /// <summary>
  34. /// Initializes a new instance of the <see cref="ShardedDocumentSession"/> class.
  35. /// </summary>
  36. /// <param name="shardStrategy">The shard strategy.</param>
  37. /// <param name="shardDbCommands">The shard IDatabaseCommands.</param>
  38. /// <param name="id"></param>
  39. /// <param name="dbName">The db name.</param>
  40. /// <param name="documentStore"></param>
  41. /// <param name="listeners"></param>
  42. public ShardedDocumentSession(string dbName, ShardedDocumentStore documentStore, DocumentSessionListeners listeners, Guid id,
  43. ShardStrategy shardStrategy, IDictionary<string, IDatabaseCommands> shardDbCommands)
  44. : base(dbName, documentStore, listeners, id, shardStrategy, shardDbCommands) { }
  45. protected override JsonDocument GetJsonDocument(string documentKey)
  46. {
  47. var shardRequestData = new ShardRequestData
  48. {
  49. EntityType = typeof(object),
  50. Keys = { documentKey }
  51. };
  52. var dbCommands = GetCommandsToOperateOn(shardRequestData);
  53. var documents = shardStrategy.ShardAccessStrategy.Apply(dbCommands,
  54. shardRequestData,
  55. (commands, i) => commands.Get(documentKey));
  56. var document = documents.FirstOrDefault(x => x != null);
  57. if (document != null)
  58. return document;
  59. throw new InvalidOperationException("Document '" + documentKey + "' no longer exists and was probably deleted");
  60. }
  61. protected override string GenerateKey(object entity)
  62. {
  63. var shardId = shardStrategy.ShardResolutionStrategy.MetadataShardIdFor(entity);
  64. IDatabaseCommands value;
  65. if (shardDbCommands.TryGetValue(shardId, out value) == false)
  66. throw new InvalidOperationException("Could not find shard: " + shardId);
  67. return Conventions.GenerateDocumentKey(dbName, value, entity);
  68. }
  69. protected override Task<string> GenerateKeyAsync(object entity)
  70. {
  71. throw new NotSupportedException("Cannot generate key asynchronously using synchronous session");
  72. }
  73. #region Properties to access different interfacess
  74. ISyncAdvancedSessionOperation IDocumentSession.Advanced
  75. {
  76. get { return this; }
  77. }
  78. /// <summary>
  79. /// Access the lazy operations
  80. /// </summary>
  81. public ILazySessionOperations Lazily
  82. {
  83. get { return this; }
  84. }
  85. /// <summary>
  86. /// Access the eager operations
  87. /// </summary>
  88. IEagerSessionOperations ISyncAdvancedSessionOperation.Eagerly
  89. {
  90. get { return this; }
  91. }
  92. #endregion
  93. #region Load and Include
  94. #region Synchronous
  95. public TResult Load<TTransformer, TResult>(string id) where TTransformer : AbstractTransformerCreationTask, new()
  96. {
  97. var transformer = new TTransformer().TransformerName;
  98. return LoadInternal<TResult>(new[] { id }, null, transformer).FirstOrDefault();
  99. }
  100. public TResult Load<TTransformer, TResult>(string id, Action<ILoadConfiguration> configure) where TTransformer : AbstractTransformerCreationTask, new()
  101. {
  102. var transformer = new TTransformer().TransformerName;
  103. var configuration = new RavenLoadConfiguration();
  104. if (configure != null)
  105. configure(configuration);
  106. return LoadInternal<TResult>(new[] { id }, null, transformer, configuration.TransformerParameters).FirstOrDefault();
  107. }
  108. public T Load<T>(string id)
  109. {
  110. if (IsDeleted(id))
  111. return default(T);
  112. object existingEntity;
  113. if (entitiesByKey.TryGetValue(id, out existingEntity))
  114. {
  115. return (T)existingEntity;
  116. }
  117. JsonDocument value;
  118. if (includedDocumentsByKey.TryGetValue(id, out value))
  119. {
  120. includedDocumentsByKey.Remove(id);
  121. return TrackEntity<T>(value);
  122. }
  123. IncrementRequestCount();
  124. var shardRequestData = new ShardRequestData
  125. {
  126. EntityType = typeof(T),
  127. Keys = { id }
  128. };
  129. var dbCommands = GetCommandsToOperateOn(shardRequestData);
  130. var results = shardStrategy.ShardAccessStrategy.Apply(dbCommands, shardRequestData, (commands, i) =>
  131. {
  132. var loadOperation = new LoadOperation(this, commands.DisableAllCaching, id);
  133. bool retry;
  134. do
  135. {
  136. loadOperation.LogOperation();
  137. using (loadOperation.EnterLoadContext())
  138. {
  139. retry = loadOperation.SetResult(commands.Get(id));
  140. }
  141. } while (retry);
  142. return loadOperation.Complete<T>();
  143. });
  144. var shardsContainThisDocument = results.Where(x => !Equals(x, default(T))).ToArray();
  145. if (shardsContainThisDocument.Count() > 1)
  146. {
  147. throw new InvalidOperationException("Found document with id: " + id +
  148. " on more than a single shard, which is not allowed. Document keys have to be unique cluster-wide.");
  149. }
  150. return shardsContainThisDocument.FirstOrDefault();
  151. }
  152. public T[] Load<T>(IEnumerable<string> ids)
  153. {
  154. return LoadInternal<T>(ids.ToArray());
  155. }
  156. public T Load<T>(ValueType id)
  157. {
  158. var documentKey = Conventions.FindFullDocumentKeyFromNonStringIdentifier(id, typeof(T), false);
  159. return Load<T>(documentKey);
  160. }
  161. public T[] Load<T>(params ValueType[] ids)
  162. {
  163. var documentKeys = ids.Select(id => Conventions.FindFullDocumentKeyFromNonStringIdentifier(id, typeof(T), false));
  164. return Load<T>(documentKeys);
  165. }
  166. public T[] Load<T>(IEnumerable<ValueType> ids)
  167. {
  168. var documentKeys = ids.Select(id => Conventions.FindFullDocumentKeyFromNonStringIdentifier(id, typeof(T), false));
  169. return Load<T>(documentKeys);
  170. }
  171. public TResult[] Load<TTransformer, TResult>(IEnumerable<string> ids, Action<ILoadConfiguration> configure = null) where TTransformer : AbstractTransformerCreationTask, new()
  172. {
  173. var configuration = new RavenLoadConfiguration();
  174. if (configure != null)
  175. configure(configuration);
  176. return LoadInternal<TResult>(ids.ToArray(), null, new TTransformer().TransformerName, configuration.TransformerParameters);
  177. }
  178. public TResult Load<TResult>(string id, string transformer, Action<ILoadConfiguration> configure = null)
  179. {
  180. var configuration = new RavenLoadConfiguration();
  181. if (configure != null)
  182. configure(configuration);
  183. return LoadInternal<TResult>(new[] { id }, null, transformer, configuration.TransformerParameters).FirstOrDefault();
  184. }
  185. public TResult[] Load<TResult>(IEnumerable<string> ids, string transformer, Action<ILoadConfiguration> configure = null)
  186. {
  187. var configuration = new RavenLoadConfiguration();
  188. if (configure != null)
  189. configure(configuration);
  190. return LoadInternal<TResult>(ids.ToArray(), null, transformer, configuration.TransformerParameters);
  191. }
  192. public TResult Load<TResult>(string id, Type transformerType, Action<ILoadConfiguration> configure = null)
  193. {
  194. var configuration = new RavenLoadConfiguration();
  195. if (configure != null)
  196. configure(configuration);
  197. var transformer = ((AbstractTransformerCreationTask)Activator.CreateInstance(transformerType)).TransformerName;
  198. return LoadInternal<TResult>(new[] { id }, null, transformer, configuration.TransformerParameters).FirstOrDefault();
  199. }
  200. public TResult[] Load<TResult>(IEnumerable<string> ids, Type transformerType, Action<ILoadConfiguration> configure = null)
  201. {
  202. var configuration = new RavenLoadConfiguration();
  203. if (configure != null)
  204. configure(configuration);
  205. var transformer = ((AbstractTransformerCreationTask)Activator.CreateInstance(transformerType)).TransformerName;
  206. return LoadInternal<TResult>(ids.ToArray(), null, transformer, configuration.TransformerParameters);
  207. }
  208. public T[] LoadInternal<T>(string[] ids, string transformer, Dictionary<string, RavenJToken> transformerParameters = null)
  209. {
  210. return LoadInternal<T>(ids.ToArray(), null, transformer, transformerParameters);
  211. }
  212. public T[] LoadInternal<T>(string[] ids, KeyValuePair<string, Type>[] includes, string transformer, Dictionary<string, RavenJToken> transformerParameters = null)
  213. {
  214. var results = new T[ids.Length];
  215. var includePaths = includes != null ? includes.Select(x => x.Key).ToArray() : null;
  216. var idsToLoad = GetIdsThatNeedLoading<T>(ids, includePaths, transformer).ToList();
  217. if (idsToLoad.Count == 0)
  218. return results;
  219. IncrementRequestCount();
  220. if (typeof(T).IsArray)
  221. {
  222. foreach (var shard in idsToLoad)
  223. {
  224. var currentShardIds = shard.Select(x => x.Id).ToArray();
  225. var shardResults = shardStrategy.ShardAccessStrategy.Apply(shard.Key,
  226. new ShardRequestData { EntityType = typeof(T), Keys = currentShardIds.ToList() },
  227. (dbCmd, i) =>
  228. {
  229. // Returns array of arrays, public APIs don't surface that yet though as we only support Transform
  230. // With a single Id
  231. var arrayOfArrays = (dbCmd.Get(currentShardIds, includePaths, transformer, transformerParameters))
  232. .Results
  233. .Select(x => x.Value<RavenJArray>("$values").Cast<RavenJObject>())
  234. .Select(values =>
  235. {
  236. var array = values.Select(y =>
  237. {
  238. HandleInternalMetadata(y);
  239. return ConvertToEntity(typeof(T),null, y, new RavenJObject());
  240. }).ToArray();
  241. var newArray = Array.CreateInstance(typeof(T).GetElementType(), array.Length);
  242. Array.Copy(array, newArray, array.Length);
  243. return newArray;
  244. })
  245. .Cast<T>()
  246. .ToArray();
  247. return arrayOfArrays;
  248. });
  249. return shardResults.SelectMany(x => x).ToArray();
  250. }
  251. }
  252. foreach (var shard in idsToLoad)
  253. {
  254. var currentShardIds = shard.Select(x => x.Id).ToArray();
  255. var shardResults = shardStrategy.ShardAccessStrategy.Apply(shard.Key,
  256. new ShardRequestData { EntityType = typeof(T), Keys = currentShardIds.ToList() },
  257. (dbCmd, i) =>
  258. {
  259. var multiLoadResult = dbCmd.Get(currentShardIds, includePaths, transformer, transformerParameters);
  260. var items = new LoadTransformerOperation(this, transformer, ids).Complete<T>(multiLoadResult);
  261. if (items.Length > currentShardIds.Length)
  262. {
  263. throw new InvalidOperationException(
  264. String.Format(
  265. "A load was attempted with transformer {0}, and more than one item was returned per entity - please use {1}[] as the projection type instead of {1}",
  266. transformer,
  267. typeof(T).Name));
  268. }
  269. return items;
  270. });
  271. foreach (var shardResult in shardResults)
  272. {
  273. for (int i = 0; i < shardResult.Length; i++)
  274. {
  275. if (ReferenceEquals(shardResult[i], null))
  276. continue;
  277. var id = currentShardIds[i];
  278. var itemPosition = Array.IndexOf(ids, id);
  279. if (ReferenceEquals(results[itemPosition], default(T)) == false)
  280. throw new InvalidOperationException("Found document with id: " + id + " on more than a single shard, which is not allowed. Document keys have to be unique cluster-wide.");
  281. results[itemPosition] = shardResult[i];
  282. }
  283. }
  284. }
  285. return results;
  286. }
  287. public T[] LoadInternal<T>(string[] ids)
  288. {
  289. return LoadInternal<T>(ids, null);
  290. }
  291. public T[] LoadInternal<T>(string[] ids, KeyValuePair<string, Type>[] includes)
  292. {
  293. var results = new T[ids.Length];
  294. var includePaths = includes != null && includes.Length > 0 ? includes.Select(x => x.Key).ToArray() : null;
  295. var idsToLoad = GetIdsThatNeedLoading<T>(ids, includePaths, transformer: null).ToList();
  296. if (idsToLoad.Count>0)
  297. IncrementRequestCount();
  298. foreach (var shard in idsToLoad)
  299. {
  300. var currentShardIds = shard.Select(x => x.Id).ToArray();
  301. var multiLoadOperations = shardStrategy.ShardAccessStrategy.Apply(shard.Key, new ShardRequestData
  302. {
  303. EntityType = typeof(T),
  304. Keys = currentShardIds.ToList()
  305. }, (dbCmd, i) =>
  306. {
  307. var multiLoadOperation = new MultiLoadOperation(this, dbCmd.DisableAllCaching, currentShardIds, includes);
  308. MultiLoadResult multiLoadResult;
  309. do
  310. {
  311. multiLoadOperation.LogOperation();
  312. using (multiLoadOperation.EnterMultiLoadContext())
  313. {
  314. multiLoadResult = dbCmd.Get(currentShardIds, includePaths);
  315. }
  316. } while (multiLoadOperation.SetResult(multiLoadResult));
  317. return multiLoadOperation;
  318. });
  319. foreach (var multiLoadOperation in multiLoadOperations)
  320. {
  321. var loadResults = multiLoadOperation.Complete<T>();
  322. for (int i = 0; i < loadResults.Length; i++)
  323. {
  324. if (ReferenceEquals(loadResults[i], null))
  325. continue;
  326. var id = currentShardIds[i];
  327. var itemPosition = Array.IndexOf(ids, id);
  328. if (ReferenceEquals(results[itemPosition], default(T)) == false)
  329. {
  330. throw new InvalidOperationException("Found document with id: " + id +
  331. " on more than a single shard, which is not allowed. Document keys have to be unique cluster-wide.");
  332. }
  333. results[itemPosition] = loadResults[i];
  334. }
  335. }
  336. }
  337. return ids.Select(id => // so we get items that were skipped because they are already in the session cache
  338. {
  339. object val;
  340. entitiesByKey.TryGetValue(id, out val);
  341. return (T)val;
  342. }).ToArray();
  343. }
  344. public ILoaderWithInclude<object> Include(string path)
  345. {
  346. return new MultiLoaderWithInclude<object>(this).Include(path);
  347. }
  348. public ILoaderWithInclude<T> Include<T>(Expression<Func<T, object>> path)
  349. {
  350. return new MultiLoaderWithInclude<T>(this).Include(path);
  351. }
  352. public ILoaderWithInclude<T> Include<T, TInclude>(Expression<Func<T, object>> path)
  353. {
  354. return new MultiLoaderWithInclude<T>(this).Include<TInclude>(path);
  355. }
  356. #endregion
  357. #region Lazy loads
  358. /// <summary>
  359. /// Loads the specified ids and a function to call when it is evaluated
  360. /// </summary>
  361. Lazy<T[]> ILazySessionOperations.Load<T>(IEnumerable<string> ids, Action<T[]> onEval)
  362. {
  363. return LazyLoadInternal(ids.ToArray(), new KeyValuePair<string, Type>[0], onEval);
  364. }
  365. /// <summary>
  366. /// Loads the specified ids.
  367. /// </summary>
  368. Lazy<T[]> ILazySessionOperations.Load<T>(IEnumerable<string> ids)
  369. {
  370. return Lazily.Load<T>(ids, null);
  371. }
  372. /// <summary>
  373. /// Loads the specified id.
  374. /// </summary>
  375. Lazy<T> ILazySessionOperations.Load<T>(string id)
  376. {
  377. return Lazily.Load(id, (Action<T>)null);
  378. }
  379. /// <summary>
  380. /// Loads the specified id and a function to call when it is evaluated
  381. /// </summary>
  382. Lazy<T> ILazySessionOperations.Load<T>(string id, Action<T> onEval)
  383. {
  384. var cmds = GetCommandsToOperateOn(new ShardRequestData
  385. {
  386. Keys = { id },
  387. EntityType = typeof(T)
  388. });
  389. var lazyLoadOperation = new LazyLoadOperation<T>(id, new LoadOperation(this, () =>
  390. {
  391. var list = cmds.Select(databaseCommands => databaseCommands.DisableAllCaching()).ToList();
  392. return new DisposableAction(() => list.ForEach(x => x.Dispose()));
  393. }, id), HandleInternalMetadata);
  394. return AddLazyOperation(lazyLoadOperation, onEval, cmds);
  395. }
  396. internal Lazy<T> AddLazyOperation<T>(ILazyOperation operation, Action<T> onEval, IList<IDatabaseCommands> cmds)
  397. {
  398. pendingLazyOperations.Add(Tuple.Create(operation, cmds));
  399. var lazyValue = new Lazy<T>(() =>
  400. {
  401. ExecuteAllPendingLazyOperations();
  402. return (T)operation.Result;
  403. });
  404. if (onEval != null)
  405. onEvaluateLazy[operation] = result => onEval((T)result);
  406. return lazyValue;
  407. }
  408. internal Lazy<int> AddLazyCountOperation(ILazyOperation operation, IList<IDatabaseCommands> cmds)
  409. {
  410. pendingLazyOperations.Add(Tuple.Create(operation, cmds));
  411. var lazyValue = new Lazy<int>(() =>
  412. {
  413. ExecuteAllPendingLazyOperations();
  414. return operation.QueryResult.TotalResults;
  415. });
  416. return lazyValue;
  417. }
  418. /// <summary>
  419. /// Loads the specified entity with the specified id after applying
  420. /// conventions on the provided id to get the real document id.
  421. /// </summary>
  422. /// <remarks>
  423. /// This method allows you to call:
  424. /// Load{Post}(1)
  425. /// And that call will internally be translated to
  426. /// Load{Post}("posts/1");
  427. ///
  428. /// Or whatever your conventions specify.
  429. /// </remarks>
  430. Lazy<T> ILazySessionOperations.Load<T>(ValueType id)
  431. {
  432. return Lazily.Load(id, (Action<T>)null);
  433. }
  434. /// <summary>
  435. /// Loads the specified entity with the specified id after applying
  436. /// conventions on the provided id to get the real document id.
  437. /// </summary>
  438. /// <remarks>
  439. /// This method allows you to call:
  440. /// Load{Post}(1)
  441. /// And that call will internally be translated to
  442. /// Load{Post}("posts/1");
  443. ///
  444. /// Or whatever your conventions specify.
  445. /// </remarks>
  446. Lazy<T> ILazySessionOperations.Load<T>(ValueType id, Action<T> onEval)
  447. {
  448. var documentKey = Conventions.FindFullDocumentKeyFromNonStringIdentifier(id, typeof(T), false);
  449. return Lazily.Load(documentKey, onEval);
  450. }
  451. Lazy<T[]> ILazySessionOperations.Load<T>(IEnumerable<ValueType> ids)
  452. {
  453. var documentKeys = ids.Select(id => Conventions.FindFullDocumentKeyFromNonStringIdentifier(id, typeof(T), false));
  454. return Lazily.Load<T>(documentKeys, null);
  455. }
  456. Lazy<T[]> ILazySessionOperations.Load<T>(IEnumerable<ValueType> ids, Action<T[]> onEval)
  457. {
  458. var documentKeys = ids.Select(id => Conventions.FindFullDocumentKeyFromNonStringIdentifier(id, typeof(T), false));
  459. return Lazily.Load(documentKeys, onEval);
  460. }
  461. Lazy<TResult> ILazySessionOperations.Load<TTransformer, TResult>(string id, Action<ILoadConfiguration> configure, Action<TResult> onEval)
  462. {
  463. var lazy = Lazily.Load<TTransformer, TResult>(new[] { id }, configure);
  464. return new Lazy<TResult>(() => lazy.Value[0]);
  465. }
  466. Lazy<TResult> ILazySessionOperations.Load<TResult>(string id, Type transformerType, Action<ILoadConfiguration> configure, Action<TResult> onEval)
  467. {
  468. var lazy = Lazily.Load(new[] { id }, transformerType, configure, onEval);
  469. return new Lazy<TResult>(() => lazy.Value[0]);
  470. }
  471. Lazy<TResult[]> ILazySessionOperations.Load<TTransformer, TResult>(IEnumerable<string> ids, Action<ILoadConfiguration> configure, Action<TResult> onEval)
  472. {
  473. return Lazily.Load(ids, typeof(TTransformer), configure, onEval);
  474. }
  475. Lazy<TResult[]> ILazySessionOperations.Load<TResult>(IEnumerable<string> ids, Type transformerType, Action<ILoadConfiguration> configure, Action<TResult> onEval)
  476. {
  477. var idsArray = ids.ToArray();
  478. var cmds = GetCommandsToOperateOn(new ShardRequestData
  479. {
  480. Keys = idsArray,
  481. EntityType = transformerType
  482. });
  483. var transformer = ((AbstractTransformerCreationTask)Activator.CreateInstance(transformerType)).TransformerName;
  484. var op = new LoadTransformerOperation(this, transformer, idsArray);
  485. var configuration = new RavenLoadConfiguration();
  486. if (configure != null)
  487. configure(configuration);
  488. var lazyLoadOperation = new LazyTransformerLoadOperation<TResult>(idsArray, transformer, configuration.TransformerParameters, op, false);
  489. return AddLazyOperation<TResult[]>(lazyLoadOperation, null, cmds);
  490. }
  491. Lazy<T[]> ILazySessionOperations.Load<T>(params ValueType[] ids)
  492. {
  493. var documentKeys = ids.Select(id => Conventions.FindFullDocumentKeyFromNonStringIdentifier(id, typeof(T), false));
  494. return Lazily.Load<T>(documentKeys, null);
  495. }
  496. /// <summary>
  497. /// Begin a load while including the specified path
  498. /// </summary>
  499. /// <param name="path">The path.</param>
  500. ILazyLoaderWithInclude<object> ILazySessionOperations.Include(string path)
  501. {
  502. return new LazyMultiLoaderWithInclude<object>(this).Include(path);
  503. }
  504. /// <summary>
  505. /// Begin a load while including the specified path
  506. /// </summary>
  507. /// <param name="path">The path.</param>
  508. ILazyLoaderWithInclude<T> ILazySessionOperations.Include<T>(Expression<Func<T, object>> path)
  509. {
  510. return new LazyMultiLoaderWithInclude<T>(this).Include(path);
  511. }
  512. /// <summary>
  513. /// Register to lazily load documents and include
  514. /// </summary>
  515. public Lazy<T[]> LazyLoadInternal<T>(string[] ids, KeyValuePair<string, Type>[] includes, Action<T[]> onEval)
  516. {
  517. var idsAndShards = ids.Select(id => new
  518. {
  519. id,
  520. shards = GetCommandsToOperateOn(new ShardRequestData
  521. {
  522. Keys = { id },
  523. EntityType = typeof(T),
  524. })
  525. })
  526. .GroupBy(x => x.shards, new DbCmdsListComparer());
  527. var cmds = idsAndShards.SelectMany(idAndShard => idAndShard.Key).Distinct().ToList();
  528. var multiLoadOperation = new MultiLoadOperation(this, () =>
  529. {
  530. var list = cmds.Select(cmd => cmd.DisableAllCaching()).ToList();
  531. return new DisposableAction(() => list.ForEach(disposable => disposable.Dispose()));
  532. }, ids, includes);
  533. var lazyOp = new LazyMultiLoadOperation<T>(multiLoadOperation, ids, includes);
  534. return AddLazyOperation(lazyOp, onEval, cmds);
  535. }
  536. public ResponseTimeInformation ExecuteAllPendingLazyOperations()
  537. {
  538. if (pendingLazyOperations.Count == 0)
  539. return new ResponseTimeInformation();
  540. try
  541. {
  542. var sw = Stopwatch.StartNew();
  543. IncrementRequestCount();
  544. var responseTimeDuration = new ResponseTimeInformation();
  545. while (ExecuteLazyOperationsSingleStep())
  546. {
  547. Thread.Sleep(100);
  548. }
  549. responseTimeDuration.ComputeServerTotal();
  550. foreach (var pendingLazyOperation in pendingLazyOperations)
  551. {
  552. Action<object> value;
  553. if (onEvaluateLazy.TryGetValue(pendingLazyOperation.Item1, out value))
  554. value(pendingLazyOperation.Item1.Result);
  555. }
  556. responseTimeDuration.TotalClientDuration = sw.Elapsed;
  557. return responseTimeDuration;
  558. }
  559. finally
  560. {
  561. pendingLazyOperations.Clear();
  562. }
  563. }
  564. private bool ExecuteLazyOperationsSingleStep()
  565. {
  566. var disposables = pendingLazyOperations.Select(x => x.Item1.EnterContext()).Where(x => x != null).ToList();
  567. try
  568. {
  569. var operationsPerShardGroup = pendingLazyOperations.GroupBy(x => x.Item2, new DbCmdsListComparer());
  570. foreach (var operationPerShard in operationsPerShardGroup)
  571. {
  572. var lazyOperations = operationPerShard.Select(x => x.Item1).ToArray();
  573. var requests = lazyOperations.Select(x => x.CreateRequest()).ToArray();
  574. var multiResponses = shardStrategy.ShardAccessStrategy.Apply(operationPerShard.Key, new ShardRequestData(),
  575. (commands, i) => commands.MultiGet(requests));
  576. var sb = new StringBuilder();
  577. foreach (var response in from shardResponses in multiResponses
  578. from getResponse in shardResponses
  579. where getResponse.RequestHasErrors()
  580. select getResponse)
  581. sb.AppendFormat("Got an error from server, status code: {0}{1}{2}", response.Status, Environment.NewLine,
  582. response.Result)
  583. .AppendLine();
  584. if (sb.Length > 0)
  585. throw new InvalidOperationException(sb.ToString());
  586. for (int i = 0; i < lazyOperations.Length; i++)
  587. {
  588. var copy = i;
  589. lazyOperations[i].HandleResponses(multiResponses.Select(x => x[copy]).ToArray(), shardStrategy);
  590. if (lazyOperations[i].RequiresRetry)
  591. return true;
  592. }
  593. }
  594. return false;
  595. }
  596. finally
  597. {
  598. disposables.ForEach(disposable => disposable.Dispose());
  599. }
  600. }
  601. #endregion
  602. #endregion
  603. #region Queries
  604. public override RavenQueryInspector<T> CreateRavenQueryInspector<T>()
  605. {
  606. return new ShardedRavenQueryInspector<T>(shardStrategy,
  607. shardDbCommands.Values.ToList(),
  608. null);
  609. }
  610. protected override IDocumentQuery<T> DocumentQueryGeneratorQuery<T>(string indexName, bool isMapReduce = false)
  611. {
  612. return DocumentQuery<T>(indexName, isMapReduce);
  613. }
  614. protected override IAsyncDocumentQuery<T> DocumentQueryGeneratorAsyncQuery<T>(string indexName, bool isMapReduce = false)
  615. {
  616. throw new NotSupportedException("The synchronous sharded document store doesn't support async operations");
  617. }
  618. [Obsolete("Use DocumentQuery instead.")]
  619. public IDocumentQuery<T> LuceneQuery<T, TIndexCreator>() where TIndexCreator : AbstractIndexCreationTask, new()
  620. {
  621. return DocumentQuery<T, TIndexCreator>();
  622. }
  623. public IDocumentQuery<T> DocumentQuery<T, TIndexCreator>() where TIndexCreator : AbstractIndexCreationTask, new()
  624. {
  625. var indexName = new TIndexCreator().IndexName;
  626. return DocumentQuery<T>(indexName);
  627. }
  628. [Obsolete("Use DocumentQuery instead")]
  629. public IDocumentQuery<T> LuceneQuery<T>(string indexName, bool isMapReduce = false)
  630. {
  631. return DocumentQuery<T>(indexName, isMapReduce);
  632. }
  633. public IDocumentQuery<T> DocumentQuery<T>(string indexName, bool isMapReduce = false)
  634. {
  635. return new ShardedDocumentQuery<T>(this, GetShardsToOperateOn, shardStrategy, indexName, null, null,
  636. theListeners.QueryListeners, isMapReduce);
  637. }
  638. [Obsolete("Use DocumentQuery instead.")]
  639. public IDocumentQuery<T> LuceneQuery<T>()
  640. {
  641. return DocumentQuery<T>();
  642. }
  643. public IDocumentQuery<T> DocumentQuery<T>()
  644. {
  645. return DocumentQuery<T>(GetDynamicIndexName<T>());
  646. }
  647. #endregion
  648. /// <summary>
  649. /// Saves all the changes to the Raven server.
  650. /// </summary>
  651. void IDocumentSession.SaveChanges()
  652. {
  653. using (EntityToJson.EntitiesToJsonCachingScope())
  654. {
  655. var data = PrepareForSaveChanges();
  656. if (data.Commands.Count == 0 && deferredCommandsByShard.Count == 0)
  657. return; // nothing to do here
  658. IncrementRequestCount();
  659. LogBatch(data);
  660. // split by shards
  661. var saveChangesPerShard = GetChangesToSavePerShard(data);
  662. // execute on all shards
  663. foreach (var shardAndObjects in saveChangesPerShard)
  664. {
  665. var shardId = shardAndObjects.Key;
  666. IDatabaseCommands databaseCommands;
  667. if (shardDbCommands.TryGetValue(shardId, out databaseCommands) == false)
  668. throw new InvalidOperationException(
  669. string.Format("ShardedDocumentStore can't find a DatabaseCommands for shard id '{0}'.", shardId));
  670. var results = databaseCommands.Batch(shardAndObjects.Value.Commands, data.Options);
  671. UpdateBatchResults(results, shardAndObjects.Value);
  672. }
  673. }
  674. }
  675. protected Dictionary<string, SaveChangesData> GetChangesToSavePerShard(SaveChangesData data)
  676. {
  677. var saveChangesPerShard = CreateSaveChangesBatchPerShardFromDeferredCommands();
  678. for (int index = 0; index < data.Entities.Count; index++)
  679. {
  680. var entity = data.Entities[index];
  681. var metadata = GetMetadataFor(entity);
  682. var shardId = metadata.Value<string>(Constants.RavenShardId);
  683. if (shardId == null)
  684. throw new InvalidOperationException("Cannot save a document when the shard id isn't defined. Missing Raven-Shard-Id in the metadata");
  685. var shardSaveChangesData = saveChangesPerShard.GetOrAdd(shardId);
  686. shardSaveChangesData.Entities.Add(entity);
  687. shardSaveChangesData.Commands.Add(data.Commands[index]);
  688. }
  689. return saveChangesPerShard;
  690. }
  691. void ISyncAdvancedSessionOperation.Refresh<T>(T entity)
  692. {
  693. DocumentMetadata value;
  694. if (entitiesAndMetadata.TryGetValue(entity, out value) == false)
  695. throw new InvalidOperationException("Cannot refresh a transient instance");
  696. IncrementRequestCount();
  697. var shardRequestData = new ShardRequestData
  698. {
  699. EntityType = typeof(T),
  700. Keys = { value.Key }
  701. };
  702. var dbCommands = GetCommandsToOperateOn(shardRequestData);
  703. var results = shardStrategy.ShardAccessStrategy.Apply(dbCommands, shardRequestData, (dbCmd, i) =>
  704. {
  705. var jsonDocument = dbCmd.Get(value.Key);
  706. if (jsonDocument == null)
  707. return false;
  708. value.Metadata = jsonDocument.Metadata;
  709. value.OriginalMetadata = (RavenJObject)jsonDocument.Metadata.CloneToken();
  710. value.ETag = jsonDocument.Etag;
  711. value.OriginalValue = jsonDocument.DataAsJson;
  712. var newEntity = ConvertToEntity(typeof(T),value.Key, jsonDocument.DataAsJson, jsonDocument.Metadata);
  713. foreach (
  714. var property in ReflectionUtil.GetPropertiesAndFieldsFor(entity.GetType(), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
  715. .Where(property => property.CanWrite() && property.CanRead() && property.GetIndexParameters().Length == 0))
  716. {
  717. property.SetValue(entity, property.GetValue(newEntity));
  718. }
  719. return true;
  720. });
  721. if (results.All(x => x == false))
  722. {
  723. throw new InvalidOperationException("Document '" + value.Key + "' no longer exists and was probably deleted");
  724. }
  725. }
  726. public T[] LoadStartingWith<T>(string keyPrefix, string matches = null, int start = 0, int pageSize = 25, string exclude = null, RavenPagingInformation pagingInformation = null, string skipAfter = null)
  727. {
  728. IncrementRequestCount();
  729. var shards = GetCommandsToOperateOn(new ShardRequestData
  730. {
  731. EntityType = typeof(T),
  732. Keys = { keyPrefix }
  733. });
  734. var results = shardStrategy.ShardAccessStrategy.Apply(shards, new ShardRequestData
  735. {
  736. EntityType = typeof(T),
  737. Keys = { keyPrefix }
  738. }, (dbCmd, i) => dbCmd.StartsWith(keyPrefix, matches, start, pageSize, exclude: exclude, skipAfter: skipAfter));
  739. return results.SelectMany(x => x).Select(TrackEntity<T>)
  740. .ToArray();
  741. }
  742. public TResult[] LoadStartingWith<TTransformer, TResult>(string keyPrefix, string matches = null, int start = 0,
  743. int pageSize = 25, string exclude = null,
  744. RavenPagingInformation pagingInformation = null,
  745. Action<ILoadConfiguration> configure = null,
  746. string skipAfter = null) where TTransformer : AbstractTransformerCreationTask, new()
  747. {
  748. var transformer = new TTransformer().TransformerName;
  749. var configuration = new RavenLoadConfiguration();
  750. if (configure != null)
  751. {
  752. configure(configuration);
  753. }
  754. IncrementRequestCount();
  755. var shards = GetCommandsToOperateOn(new ShardRequestData
  756. {
  757. EntityType = typeof(TResult),
  758. Keys = { keyPrefix }
  759. });
  760. var results = shardStrategy.ShardAccessStrategy.Apply(shards, new ShardRequestData
  761. {
  762. EntityType = typeof (TResult),
  763. Keys = {keyPrefix}
  764. },
  765. (dbCmd, i) =>
  766. dbCmd.StartsWith(keyPrefix, matches, start, pageSize,
  767. exclude: exclude, transformer: transformer,
  768. transformerParameters: configuration.TransformerParameters,
  769. skipAfter: skipAfter));
  770. var queryOperation = new QueryOperation(this, "Load/StartingWith", null, null, false, TimeSpan.Zero, null, null, false);
  771. return results.SelectMany(x => x).Select(x=>queryOperation.Deserialize<TResult>(x.ToJson()))
  772. .ToArray();
  773. }
  774. public Lazy<TResult[]> MoreLikeThis<TResult>(MoreLikeThisQuery query)
  775. {
  776. throw new NotSupportedException("Not supported for sharded session");
  777. }
  778. Lazy<T[]> ILazySessionOperations.LoadStartingWith<T>(string keyPrefix, string matches, int start, int pageSize, string exclude, RavenPagingInformation pagingInformation, string skipAfter)
  779. {
  780. IncrementRequestCount();
  781. var cmds = GetCommandsToOperateOn(new ShardRequestData
  782. {
  783. EntityType = typeof(T),
  784. Keys = { keyPrefix }
  785. });
  786. var lazyLoadOperation = new LazyStartsWithOperation<T>(keyPrefix, matches, exclude, start, pageSize, this, null, skipAfter);
  787. return AddLazyOperation<T[]>(lazyLoadOperation, null, cmds);
  788. }
  789. public IAsyncDatabaseCommands AsyncDatabaseCommands
  790. {
  791. get { throw new NotSupportedException("Not supported for sharded session"); }
  792. }
  793. string ISyncAdvancedSessionOperation.GetDocumentUrl(object entity)
  794. {
  795. DocumentMetadata value;
  796. if (entitiesAndMetadata.TryGetValue(entity, out value) == false)
  797. throw new ArgumentException("The entity is not part of the session");
  798. var shardId = value.Metadata.Value<string>(Constants.RavenShardId);
  799. IDatabaseCommands commands;
  800. if (shardDbCommands.TryGetValue(shardId, out commands) == false)
  801. throw new InvalidOperationException("Could not find matching shard for shard id: " + shardId);
  802. return commands.UrlFor(value.Key);
  803. }
  804. public IEnumerator<StreamResult<T>> Stream<T>(IQueryable<T> query)
  805. {
  806. QueryHeaderInformation _;
  807. return Stream(query, out _);
  808. }
  809. public IEnumerator<StreamResult<T>> Stream<T>(IQueryable<T> query, out QueryHeaderInformation queryHeaderInformation)
  810. {
  811. throw new NotSupportedException("Streams are currently not supported by sharded document store");
  812. }
  813. public IEnumerator<StreamResult<T>> Stream<T>(IDocumentQuery<T> query)
  814. {
  815. QueryHeaderInformation _;
  816. return Stream(query, out _);
  817. }
  818. public IEnumerator<StreamResult<T>> Stream<T>(IDocumentQuery<T> query, out QueryHeaderInformation queryHeaderInformation)
  819. {
  820. throw new NotSupportedException("Streams are currently not supported by sharded document store");
  821. }
  822. public IEnumerator<StreamResult<T>> Stream<T>(Etag fromEtag, int start = 0, int pageSize = Int32.MaxValue, RavenPagingInformation pagingInformation = null, string transformer = null, Dictionary<string, RavenJToken> transformerParameters = null)
  823. {
  824. throw new NotSupportedException("Streams are currently not supported by sharded document store");
  825. }
  826. public IEnumerator<StreamResult<T>> Stream<T>(string startsWith, string matches = null, int start = 0, int pageSize = Int32.MaxValue, RavenPagingInformation pagingInformation = null, string skipAfter = null, string transformer = null, Dictionary<string, RavenJToken> transformerParameters = null)
  827. {
  828. throw new NotSupportedException("Streams are currently not supported by sharded document store");
  829. }
  830. public Operation DeleteByIndex<T, TIndexCreator>(Expression<Func<T, bool>> expression) where TIndexCreator : AbstractIndexCreationTask, new()
  831. {
  832. var indexCreator = new TIndexCreator();
  833. return DeleteByIndex<T>(indexCreator.IndexName, expression);
  834. }
  835. public Operation DeleteByIndex<T>(string indexName, Expression<Func<T, bool>> expression)
  836. {
  837. var query = Query<T>(indexName).Where(expression);
  838. var indexQuery = new IndexQuery()
  839. {
  840. Query = query.ToString()
  841. };
  842. var shards = GetCommandsToOperateOn(new ShardRequestData
  843. {
  844. EntityType = typeof(T),
  845. Keys = { indexName }
  846. });
  847. var operations = shardStrategy.ShardAccessStrategy.Apply(shards, new ShardRequestData
  848. {
  849. EntityType = typeof(T),
  850. Keys = { indexName }
  851. }, (dbCmd, i) => dbCmd.DeleteByIndex(indexName, indexQuery));
  852. var shardOperation = new ShardsOperation(operations);
  853. return shardOperation;
  854. }
  855. public FacetResults[] MultiFacetedSearch(params FacetQuery[] queries)
  856. {
  857. throw new NotSupportedException("Multi faceted searching is currently not supported by sharded document store");
  858. }
  859. }
  860. }