BsonDocument.cs 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293
  1. /* Copyright 2010-2016 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;
  17. using System.Collections.Generic;
  18. using System.IO;
  19. using System.Linq;
  20. using MongoDB.Bson.IO;
  21. using MongoDB.Bson.Serialization;
  22. using MongoDB.Bson.Serialization.Serializers;
  23. using MongoDB.Shared;
  24. namespace MongoDB.Bson
  25. {
  26. /// <summary>
  27. /// Represents a BSON document.
  28. /// </summary>
  29. #if NET45
  30. [Serializable]
  31. #endif
  32. public class BsonDocument : BsonValue, IComparable<BsonDocument>, IConvertibleToBsonDocument, IEnumerable<BsonElement>, IEquatable<BsonDocument>
  33. {
  34. // constants
  35. private const int __indexesThreshold = 8; // the _indexes dictionary will not be created until the document grows to contain 8 elements
  36. // private fields
  37. // use a list and a dictionary because we want to preserve the order in which the elements were added
  38. // if duplicate names are present only the first one will be in the dictionary (the others can only be accessed by index)
  39. private readonly List<BsonElement> _elements = new List<BsonElement>();
  40. private Dictionary<string, int> _indexes = null; // maps names to indexes into elements list (not created until there are enough elements to justify it)
  41. private bool _allowDuplicateNames;
  42. // constructors
  43. /// <summary>
  44. /// Initializes a new instance of the BsonDocument class.
  45. /// </summary>
  46. public BsonDocument()
  47. {
  48. }
  49. /// <summary>
  50. /// Initializes a new instance of the BsonDocument class specifying whether duplicate element names are allowed
  51. /// (allowing duplicate element names is not recommended).
  52. /// </summary>
  53. /// <param name="allowDuplicateNames">Whether duplicate element names are allowed.</param>
  54. public BsonDocument(bool allowDuplicateNames)
  55. {
  56. _allowDuplicateNames = allowDuplicateNames;
  57. }
  58. /// <summary>
  59. /// Initializes a new instance of the BsonDocument class and adds one element.
  60. /// </summary>
  61. /// <param name="element">An element to add to the document.</param>
  62. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
  63. public BsonDocument(BsonElement element)
  64. {
  65. Add(element);
  66. }
  67. /// <summary>
  68. /// Initializes a new instance of the BsonDocument class and adds new elements from a dictionary of key/value pairs.
  69. /// </summary>
  70. /// <param name="dictionary">A dictionary to initialize the document from.</param>
  71. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
  72. public BsonDocument(Dictionary<string, object> dictionary)
  73. {
  74. AddRange(dictionary);
  75. }
  76. /// <summary>
  77. /// Initializes a new instance of the BsonDocument class and adds new elements from a dictionary of key/value pairs.
  78. /// </summary>
  79. /// <param name="dictionary">A dictionary to initialize the document from.</param>
  80. /// <param name="keys">A list of keys to select values from the dictionary.</param>
  81. [Obsolete("Use BsonDocument(IEnumerable<BsonElement> elements) instead.")]
  82. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
  83. public BsonDocument(Dictionary<string, object> dictionary, IEnumerable<string> keys)
  84. {
  85. Add(dictionary, keys);
  86. }
  87. /// <summary>
  88. /// Initializes a new instance of the BsonDocument class and adds new elements from a dictionary of key/value pairs.
  89. /// </summary>
  90. /// <param name="dictionary">A dictionary to initialize the document from.</param>
  91. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
  92. public BsonDocument(IEnumerable<KeyValuePair<string, object>> dictionary)
  93. {
  94. AddRange(dictionary);
  95. }
  96. /// <summary>
  97. /// Initializes a new instance of the BsonDocument class and adds new elements from a dictionary of key/value pairs.
  98. /// </summary>
  99. /// <param name="dictionary">A dictionary to initialize the document from.</param>
  100. /// <param name="keys">A list of keys to select values from the dictionary.</param>
  101. [Obsolete("Use BsonDocument(IEnumerable<BsonElement> elements) instead.")]
  102. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
  103. public BsonDocument(IDictionary<string, object> dictionary, IEnumerable<string> keys)
  104. {
  105. Add(dictionary, keys);
  106. }
  107. /// <summary>
  108. /// Initializes a new instance of the BsonDocument class and adds new elements from a dictionary of key/value pairs.
  109. /// </summary>
  110. /// <param name="dictionary">A dictionary to initialize the document from.</param>
  111. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
  112. public BsonDocument(IDictionary dictionary)
  113. {
  114. AddRange(dictionary);
  115. }
  116. /// <summary>
  117. /// Initializes a new instance of the BsonDocument class and adds new elements from a dictionary of key/value pairs.
  118. /// </summary>
  119. /// <param name="dictionary">A dictionary to initialize the document from.</param>
  120. /// <param name="keys">A list of keys to select values from the dictionary.</param>
  121. [Obsolete("Use BsonDocument(IEnumerable<BsonElement> elements) instead.")]
  122. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
  123. public BsonDocument(IDictionary dictionary, IEnumerable keys)
  124. {
  125. Add(dictionary, keys);
  126. }
  127. /// <summary>
  128. /// Initializes a new instance of the BsonDocument class and adds new elements from a list of elements.
  129. /// </summary>
  130. /// <param name="elements">A list of elements to add to the document.</param>
  131. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
  132. public BsonDocument(IEnumerable<BsonElement> elements)
  133. {
  134. AddRange(elements);
  135. }
  136. /// <summary>
  137. /// Initializes a new instance of the BsonDocument class and adds one or more elements.
  138. /// </summary>
  139. /// <param name="elements">One or more elements to add to the document.</param>
  140. [Obsolete("Use BsonDocument(IEnumerable<BsonElement> elements) instead.")]
  141. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
  142. public BsonDocument(params BsonElement[] elements)
  143. {
  144. Add(elements);
  145. }
  146. /// <summary>
  147. /// Initializes a new instance of the BsonDocument class and creates and adds a new element.
  148. /// </summary>
  149. /// <param name="name">The name of the element to add to the document.</param>
  150. /// <param name="value">The value of the element to add to the document.</param>
  151. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
  152. public BsonDocument(string name, BsonValue value)
  153. {
  154. Add(name, value);
  155. }
  156. // public operators
  157. /// <summary>
  158. /// Compares two BsonDocument values.
  159. /// </summary>
  160. /// <param name="lhs">The first BsonDocument.</param>
  161. /// <param name="rhs">The other BsonDocument.</param>
  162. /// <returns>True if the two BsonDocument values are not equal according to ==.</returns>
  163. public static bool operator !=(BsonDocument lhs, BsonDocument rhs)
  164. {
  165. return !(lhs == rhs);
  166. }
  167. /// <summary>
  168. /// Compares two BsonDocument values.
  169. /// </summary>
  170. /// <param name="lhs">The first BsonDocument.</param>
  171. /// <param name="rhs">The other BsonDocument.</param>
  172. /// <returns>True if the two BsonDocument values are equal according to ==.</returns>
  173. public static bool operator ==(BsonDocument lhs, BsonDocument rhs)
  174. {
  175. return object.Equals(lhs, rhs); // handles lhs == null correctly
  176. }
  177. // public properties
  178. /// <summary>
  179. /// Gets or sets whether to allow duplicate names (allowing duplicate names is not recommended).
  180. /// </summary>
  181. public bool AllowDuplicateNames
  182. {
  183. get { return _allowDuplicateNames; }
  184. set { _allowDuplicateNames = value; }
  185. }
  186. /// <summary>
  187. /// Gets the BsonType of this BsonValue.
  188. /// </summary>
  189. public override BsonType BsonType
  190. {
  191. get { return BsonType.Document; }
  192. }
  193. // ElementCount could be greater than the number of Names if allowDuplicateNames is true
  194. /// <summary>
  195. /// Gets the number of elements.
  196. /// </summary>
  197. public virtual int ElementCount
  198. {
  199. get { return _elements.Count; }
  200. }
  201. /// <summary>
  202. /// Gets the elements.
  203. /// </summary>
  204. public virtual IEnumerable<BsonElement> Elements
  205. {
  206. get { return _elements; }
  207. }
  208. /// <summary>
  209. /// Gets the element names.
  210. /// </summary>
  211. public virtual IEnumerable<string> Names
  212. {
  213. get { return _elements.Select(e => e.Name); }
  214. }
  215. /// <summary>
  216. /// Gets the raw values (see BsonValue.RawValue).
  217. /// </summary>
  218. [Obsolete("Use Values instead.")]
  219. public virtual IEnumerable<object> RawValues
  220. {
  221. get { return _elements.Select(e => e.Value.RawValue); }
  222. }
  223. /// <summary>
  224. /// Gets the values.
  225. /// </summary>
  226. public virtual IEnumerable<BsonValue> Values
  227. {
  228. get { return _elements.Select(e => e.Value); }
  229. }
  230. // public indexers
  231. // note: the return type of the indexers is BsonValue and NOT BsonElement so that we can write code like:
  232. // BsonDocument car;
  233. // car["color"] = "red"; // changes value of existing element or adds new element
  234. // note: we are using implicit conversion from string to BsonValue
  235. // to convert the returned BsonValue to a .NET type you have two approaches (explicit cast or As method):
  236. // string color = (string) car["color"]; // throws exception if value is not a string (returns null if not found)
  237. // string color = car["color"].AsString; // throws exception if value is not a string (results in a NullReferenceException if not found)
  238. // string color = car["color", "none"].AsString; // throws exception if value is not a string (default to "none" if not found)
  239. // the second approach offers a more fluent interface (with fewer parenthesis!)
  240. // string name = car["brand"].AsBsonSymbol.Name;
  241. // string name = ((BsonSymbol) car["brand"]).Name; // the extra parenthesis are required and harder to read
  242. // there are also some conversion methods (and note that ToBoolean uses the JavaScript definition of truthiness)
  243. // bool ok = result["ok"].ToBoolean(); // works whether ok is false, true, 0, 0.0, 1, 1.0, "", "xyz", BsonNull.Value, etc...
  244. // bool ok = result["ok", false].ToBoolean(); // defaults to false if ok element is not found
  245. // int n = result["n"].ToInt32(); // works whether n is Int32, Int64, Double or String (if it can be parsed)
  246. // long n = result["n"].ToInt64(); // works whether n is Int32, Int64, Double or String (if it can be parsed)
  247. // double d = result["n"].ToDouble(); // works whether d is Int32, Int64, Double or String (if it can be parsed)
  248. // to work in terms of BsonElements use Add, GetElement and SetElement
  249. // car.Add(new BsonElement("color", "red")); // might throw exception if allowDuplicateNames is false
  250. // car.SetElement(new BsonElement("color", "red")); // replaces existing element or adds new element
  251. // BsonElement colorElement = car.GetElement("color"); // returns null if element "color" is not found
  252. /// <summary>
  253. /// Gets or sets a value by position.
  254. /// </summary>
  255. /// <param name="index">The position.</param>
  256. /// <returns>The value.</returns>
  257. public override BsonValue this[int index]
  258. {
  259. get { return _elements[index].Value; }
  260. set
  261. {
  262. if (value == null)
  263. {
  264. throw new ArgumentNullException("value");
  265. }
  266. _elements[index] = new BsonElement(_elements[index].Name, value);
  267. }
  268. }
  269. /// <summary>
  270. /// Gets the value of an element or a default value if the element is not found.
  271. /// </summary>
  272. /// <param name="name">The name of the element.</param>
  273. /// <param name="defaultValue">The default value to return if the element is not found.</param>
  274. /// <returns>Teh value of the element or a default value if the element is not found.</returns>
  275. [Obsolete("Use GetValue(string name, BsonValue defaultValue) instead.")]
  276. public virtual BsonValue this[string name, BsonValue defaultValue]
  277. {
  278. get { return GetValue(name, defaultValue); }
  279. }
  280. /// <summary>
  281. /// Gets or sets a value by name.
  282. /// </summary>
  283. /// <param name="name">The name.</param>
  284. /// <returns>The value.</returns>
  285. public override BsonValue this[string name]
  286. {
  287. get
  288. {
  289. if (name == null)
  290. {
  291. throw new ArgumentNullException("name");
  292. }
  293. var index = IndexOfName(name);
  294. if (index != -1)
  295. {
  296. return _elements[index].Value;
  297. }
  298. else
  299. {
  300. string message = string.Format("Element '{0}' not found.", name);
  301. throw new KeyNotFoundException(message);
  302. }
  303. }
  304. set
  305. {
  306. if (name == null)
  307. {
  308. throw new ArgumentNullException("name");
  309. }
  310. if (value == null)
  311. {
  312. throw new ArgumentNullException("value");
  313. }
  314. var index = IndexOfName(name);
  315. if (index != -1)
  316. {
  317. _elements[index] = new BsonElement(name, value);
  318. }
  319. else
  320. {
  321. Add(new BsonElement(name, value));
  322. }
  323. }
  324. }
  325. // public static methods
  326. /// <summary>
  327. /// Creates a new BsonDocument by mapping an object to a BsonDocument.
  328. /// </summary>
  329. /// <param name="value">The object to be mapped to a BsonDocument.</param>
  330. /// <returns>A BsonDocument.</returns>
  331. public new static BsonDocument Create(object value)
  332. {
  333. if (value == null)
  334. {
  335. throw new ArgumentNullException("value");
  336. }
  337. return (BsonDocument)BsonTypeMapper.MapToBsonValue(value, BsonType.Document);
  338. }
  339. /// <summary>
  340. /// Parses a JSON string and returns a BsonDocument.
  341. /// </summary>
  342. /// <param name="json">The JSON string.</param>
  343. /// <returns>A BsonDocument.</returns>
  344. public static BsonDocument Parse(string json)
  345. {
  346. using (var jsonReader = new JsonReader(json))
  347. {
  348. var context = BsonDeserializationContext.CreateRoot(jsonReader);
  349. var document = BsonDocumentSerializer.Instance.Deserialize(context);
  350. if (!jsonReader.IsAtEndOfFile())
  351. {
  352. throw new FormatException("String contains extra non-whitespace characters beyond the end of the document.");
  353. }
  354. return document;
  355. }
  356. }
  357. // public methods
  358. /// <summary>
  359. /// Adds an element to the document.
  360. /// </summary>
  361. /// <param name="element">The element to add.</param>
  362. /// <returns>The document (so method calls can be chained).</returns>
  363. public virtual BsonDocument Add(BsonElement element)
  364. {
  365. var isDuplicate = IndexOfName(element.Name) != -1;
  366. if (isDuplicate && !_allowDuplicateNames)
  367. {
  368. var message = string.Format("Duplicate element name '{0}'.", element.Name);
  369. throw new InvalidOperationException(message);
  370. }
  371. else
  372. {
  373. _elements.Add(element);
  374. if (!isDuplicate)
  375. {
  376. if (_indexes == null)
  377. {
  378. RebuildIndexes();
  379. }
  380. else
  381. {
  382. _indexes.Add(element.Name, _elements.Count - 1); // index of the newly added element
  383. }
  384. }
  385. }
  386. return this;
  387. }
  388. /// <summary>
  389. /// Adds elements to the document from a dictionary of key/value pairs.
  390. /// </summary>
  391. /// <param name="dictionary">The dictionary.</param>
  392. /// <returns>The document (so method calls can be chained).</returns>
  393. [Obsolete("Use AddRange instead.")]
  394. public virtual BsonDocument Add(Dictionary<string, object> dictionary)
  395. {
  396. return AddRange(dictionary);
  397. }
  398. /// <summary>
  399. /// Adds elements to the document from a dictionary of key/value pairs.
  400. /// </summary>
  401. /// <param name="dictionary">The dictionary.</param>
  402. /// <param name="keys">Which keys of the hash table to add.</param>
  403. /// <returns>The document (so method calls can be chained).</returns>
  404. [Obsolete("Use AddRange(IEnumerable<BsonElement> elements) instead.")]
  405. public virtual BsonDocument Add(Dictionary<string, object> dictionary, IEnumerable<string> keys)
  406. {
  407. return Add((IDictionary<string, object>)dictionary, keys);
  408. }
  409. /// <summary>
  410. /// Adds elements to the document from a dictionary of key/value pairs.
  411. /// </summary>
  412. /// <param name="dictionary">The dictionary.</param>
  413. /// <returns>The document (so method calls can be chained).</returns>
  414. [Obsolete("Use AddRange instead.")]
  415. public virtual BsonDocument Add(IDictionary<string, object> dictionary)
  416. {
  417. return AddRange(dictionary);
  418. }
  419. /// <summary>
  420. /// Adds elements to the document from a dictionary of key/value pairs.
  421. /// </summary>
  422. /// <param name="dictionary">The dictionary.</param>
  423. /// <param name="keys">Which keys of the hash table to add.</param>
  424. /// <returns>The document (so method calls can be chained).</returns>
  425. [Obsolete("Use AddRange(IEnumerable<BsonElement> elements) instead.")]
  426. public virtual BsonDocument Add(IDictionary<string, object> dictionary, IEnumerable<string> keys)
  427. {
  428. if (dictionary == null)
  429. {
  430. throw new ArgumentNullException("dictionary");
  431. }
  432. if (keys == null)
  433. {
  434. throw new ArgumentNullException("keys");
  435. }
  436. foreach (var key in keys)
  437. {
  438. Add(key, BsonTypeMapper.MapToBsonValue(dictionary[key]));
  439. }
  440. return this;
  441. }
  442. /// <summary>
  443. /// Adds elements to the document from a dictionary of key/value pairs.
  444. /// </summary>
  445. /// <param name="dictionary">The dictionary.</param>
  446. /// <returns>The document (so method calls can be chained).</returns>
  447. [Obsolete("Use AddRange instead.")]
  448. public virtual BsonDocument Add(IDictionary dictionary)
  449. {
  450. return AddRange(dictionary);
  451. }
  452. /// <summary>
  453. /// Adds elements to the document from a dictionary of key/value pairs.
  454. /// </summary>
  455. /// <param name="dictionary">The dictionary.</param>
  456. /// <param name="keys">Which keys of the hash table to add.</param>
  457. /// <returns>The document (so method calls can be chained).</returns>
  458. [Obsolete("Use AddRange(IEnumerable<BsonElement> elements) instead.")]
  459. public virtual BsonDocument Add(IDictionary dictionary, IEnumerable keys)
  460. {
  461. if (dictionary == null)
  462. {
  463. throw new ArgumentNullException("dictionary");
  464. }
  465. if (keys == null)
  466. {
  467. throw new ArgumentNullException("keys");
  468. }
  469. foreach (var key in keys)
  470. {
  471. if (key == null)
  472. {
  473. throw new ArgumentException("keys", "A key passed to BsonDocument.Add is null.");
  474. }
  475. if (key.GetType() != typeof(string))
  476. {
  477. throw new ArgumentOutOfRangeException("keys", "A key passed to BsonDocument.Add is not a string.");
  478. }
  479. Add((string)key, BsonTypeMapper.MapToBsonValue(dictionary[key]));
  480. }
  481. return this;
  482. }
  483. /// <summary>
  484. /// Adds a list of elements to the document.
  485. /// </summary>
  486. /// <param name="elements">The list of elements.</param>
  487. /// <returns>The document (so method calls can be chained).</returns>
  488. [Obsolete("Use AddRange instead.")]
  489. public virtual BsonDocument Add(IEnumerable<BsonElement> elements)
  490. {
  491. return AddRange(elements);
  492. }
  493. /// <summary>
  494. /// Adds a list of elements to the document.
  495. /// </summary>
  496. /// <param name="elements">The list of elements.</param>
  497. /// <returns>The document (so method calls can be chained).</returns>
  498. [Obsolete("Use AddRange(IEnumerable<BsonElement> elements) instead.")]
  499. public virtual BsonDocument Add(params BsonElement[] elements)
  500. {
  501. return AddRange((IEnumerable<BsonElement>)elements);
  502. }
  503. /// <summary>
  504. /// Creates and adds an element to the document.
  505. /// </summary>
  506. /// <param name="name">The name of the element.</param>
  507. /// <param name="value">The value of the element.</param>
  508. /// <returns>The document (so method calls can be chained).</returns>
  509. public virtual BsonDocument Add(string name, BsonValue value)
  510. {
  511. if (name == null)
  512. {
  513. throw new ArgumentNullException("name");
  514. }
  515. if (value == null)
  516. {
  517. throw new ArgumentNullException("value");
  518. }
  519. Add(new BsonElement(name, value));
  520. return this;
  521. }
  522. /// <summary>
  523. /// Creates and adds an element to the document, but only if the condition is true.
  524. /// </summary>
  525. /// <param name="name">The name of the element.</param>
  526. /// <param name="value">The value of the element.</param>
  527. /// <param name="condition">Whether to add the element to the document.</param>
  528. /// <returns>The document (so method calls can be chained).</returns>
  529. public virtual BsonDocument Add(string name, BsonValue value, bool condition)
  530. {
  531. if (name == null)
  532. {
  533. throw new ArgumentNullException("name");
  534. }
  535. if (condition)
  536. {
  537. // don't check for null value unless condition is true
  538. if (value == null)
  539. {
  540. throw new ArgumentNullException("value");
  541. }
  542. Add(new BsonElement(name, value));
  543. }
  544. return this;
  545. }
  546. /// <summary>
  547. /// Creates and adds an element to the document, but only if the condition is true.
  548. /// If the condition is false the value factory is not called at all.
  549. /// </summary>
  550. /// <param name="name">The name of the element.</param>
  551. /// <param name="valueFactory">A delegate called to compute the value of the element if condition is true.</param>
  552. /// <param name="condition">Whether to add the element to the document.</param>
  553. /// <returns>The document (so method calls can be chained).</returns>
  554. public virtual BsonDocument Add(string name, Func<BsonValue> valueFactory, bool condition)
  555. {
  556. if (name == null)
  557. {
  558. throw new ArgumentNullException("name");
  559. }
  560. if (valueFactory == null)
  561. {
  562. throw new ArgumentNullException("valueFactory");
  563. }
  564. if (condition)
  565. {
  566. Add(new BsonElement(name, valueFactory()));
  567. }
  568. return this;
  569. }
  570. /// <summary>
  571. /// Adds elements to the document from a dictionary of key/value pairs.
  572. /// </summary>
  573. /// <param name="dictionary">The dictionary.</param>
  574. /// <returns>The document (so method calls can be chained).</returns>
  575. public virtual BsonDocument AddRange(Dictionary<string, object> dictionary)
  576. {
  577. return AddRange((IEnumerable<KeyValuePair<string, object>>)dictionary);
  578. }
  579. /// <summary>
  580. /// Adds elements to the document from a dictionary of key/value pairs.
  581. /// </summary>
  582. /// <param name="dictionary">The dictionary.</param>
  583. /// <returns>The document (so method calls can be chained).</returns>
  584. public virtual BsonDocument AddRange(IDictionary dictionary)
  585. {
  586. if (dictionary == null)
  587. {
  588. throw new ArgumentNullException("dictionary");
  589. }
  590. foreach (DictionaryEntry entry in dictionary)
  591. {
  592. if (entry.Key == null)
  593. {
  594. throw new ArgumentException("keys", "A key passed to BsonDocument.AddRange is null.");
  595. }
  596. if (entry.Key.GetType() != typeof(string))
  597. {
  598. throw new ArgumentOutOfRangeException("dictionary", "One or more keys in the dictionary passed to BsonDocument.AddRange is not a string.");
  599. }
  600. Add((string)entry.Key, BsonTypeMapper.MapToBsonValue(entry.Value));
  601. }
  602. return this;
  603. }
  604. /// <summary>
  605. /// Adds a list of elements to the document.
  606. /// </summary>
  607. /// <param name="elements">The list of elements.</param>
  608. /// <returns>The document (so method calls can be chained).</returns>
  609. public virtual BsonDocument AddRange(IEnumerable<BsonElement> elements)
  610. {
  611. if (elements == null)
  612. {
  613. throw new ArgumentNullException("elements");
  614. }
  615. foreach (var element in elements)
  616. {
  617. Add(element);
  618. }
  619. return this;
  620. }
  621. /// <summary>
  622. /// Adds elements to the document from a dictionary of key/value pairs.
  623. /// </summary>
  624. /// <param name="dictionary">The dictionary.</param>
  625. /// <returns>The document (so method calls can be chained).</returns>
  626. public virtual BsonDocument AddRange(IEnumerable<KeyValuePair<string, object>> dictionary)
  627. {
  628. if (dictionary == null)
  629. {
  630. throw new ArgumentNullException("dictionary");
  631. }
  632. foreach (var entry in dictionary)
  633. {
  634. Add(entry.Key, BsonTypeMapper.MapToBsonValue(entry.Value));
  635. }
  636. return this;
  637. }
  638. /// <summary>
  639. /// Clears the document (removes all elements).
  640. /// </summary>
  641. public virtual void Clear()
  642. {
  643. _elements.Clear();
  644. _indexes = null;
  645. }
  646. /// <summary>
  647. /// Creates a shallow clone of the document (see also DeepClone).
  648. /// </summary>
  649. /// <returns>A shallow clone of the document.</returns>
  650. public override BsonValue Clone()
  651. {
  652. BsonDocument clone = new BsonDocument();
  653. foreach (BsonElement element in _elements)
  654. {
  655. clone.Add(element.Clone());
  656. }
  657. return clone;
  658. }
  659. /// <summary>
  660. /// Compares this document to another document.
  661. /// </summary>
  662. /// <param name="rhs">The other document.</param>
  663. /// <returns>A 32-bit signed integer that indicates whether this document is less than, equal to, or greather than the other.</returns>
  664. public virtual int CompareTo(BsonDocument rhs)
  665. {
  666. if (rhs == null) { return 1; }
  667. // lhs and rhs might be subclasses of BsonDocument
  668. using (var lhsEnumerator = Elements.GetEnumerator())
  669. using (var rhsEnumerator = rhs.Elements.GetEnumerator())
  670. {
  671. while (true)
  672. {
  673. var lhsHasNext = lhsEnumerator.MoveNext();
  674. var rhsHasNext = rhsEnumerator.MoveNext();
  675. if (!lhsHasNext && !rhsHasNext) { return 0; }
  676. if (!lhsHasNext) { return -1; }
  677. if (!rhsHasNext) { return 1; }
  678. var lhsElement = lhsEnumerator.Current;
  679. var rhsElement = rhsEnumerator.Current;
  680. var result = lhsElement.Name.CompareTo(rhsElement.Name);
  681. if (result != 0) { return result; }
  682. result = lhsElement.Value.CompareTo(rhsElement.Value);
  683. if (result != 0) { return result; }
  684. }
  685. }
  686. }
  687. /// <summary>
  688. /// Compares the BsonDocument to another BsonValue.
  689. /// </summary>
  690. /// <param name="other">The other BsonValue.</param>
  691. /// <returns>A 32-bit signed integer that indicates whether this BsonDocument is less than, equal to, or greather than the other BsonValue.</returns>
  692. public override int CompareTo(BsonValue other)
  693. {
  694. if (other == null) { return 1; }
  695. var otherDocument = other as BsonDocument;
  696. if (otherDocument != null)
  697. {
  698. return CompareTo(otherDocument);
  699. }
  700. return CompareTypeTo(other);
  701. }
  702. /// <summary>
  703. /// Tests whether the document contains an element with the specified name.
  704. /// </summary>
  705. /// <param name="name">The name of the element to look for.</param>
  706. /// <returns>True if the document contains an element with the specified name.</returns>
  707. public virtual bool Contains(string name)
  708. {
  709. return IndexOfName(name) != -1;
  710. }
  711. /// <summary>
  712. /// Tests whether the document contains an element with the specified value.
  713. /// </summary>
  714. /// <param name="value">The value of the element to look for.</param>
  715. /// <returns>True if the document contains an element with the specified value.</returns>
  716. public virtual bool ContainsValue(BsonValue value)
  717. {
  718. if (value == null)
  719. {
  720. throw new ArgumentNullException("value");
  721. }
  722. return _elements.Any(e => e.Value == value);
  723. }
  724. /// <summary>
  725. /// Creates a deep clone of the document (see also Clone).
  726. /// </summary>
  727. /// <returns>A deep clone of the document.</returns>
  728. public override BsonValue DeepClone()
  729. {
  730. BsonDocument clone = new BsonDocument();
  731. foreach (BsonElement element in _elements)
  732. {
  733. clone.Add(element.DeepClone());
  734. }
  735. return clone;
  736. }
  737. /// <summary>
  738. /// Compares this document to another document.
  739. /// </summary>
  740. /// <param name="obj">The other document.</param>
  741. /// <returns>True if the two documents are equal.</returns>
  742. public bool Equals(BsonDocument obj)
  743. {
  744. return Equals((object)obj); // handles obj == null correctly
  745. }
  746. /// <summary>
  747. /// Compares this BsonDocument to another object.
  748. /// </summary>
  749. /// <param name="obj">The other object.</param>
  750. /// <returns>True if the other object is a BsonDocument and equal to this one.</returns>
  751. public override bool Equals(object obj)
  752. {
  753. if (object.ReferenceEquals(obj, null) || !(obj is BsonDocument)) { return false; }
  754. // lhs and rhs might be subclasses of BsonDocument
  755. var rhs = (BsonDocument)obj;
  756. return Elements.SequenceEqual(rhs.Elements);
  757. }
  758. /// <summary>
  759. /// Gets an element of this document.
  760. /// </summary>
  761. /// <param name="index">The zero based index of the element.</param>
  762. /// <returns>The element.</returns>
  763. public virtual BsonElement GetElement(int index)
  764. {
  765. return _elements[index];
  766. }
  767. /// <summary>
  768. /// Gets an element of this document.
  769. /// </summary>
  770. /// <param name="name">The name of the element.</param>
  771. /// <returns>A BsonElement.</returns>
  772. public virtual BsonElement GetElement(string name)
  773. {
  774. if (name == null)
  775. {
  776. throw new ArgumentNullException("name");
  777. }
  778. var index = IndexOfName(name);
  779. if (index != -1)
  780. {
  781. return _elements[index];
  782. }
  783. else
  784. {
  785. string message = string.Format("Element '{0}' not found.", name);
  786. throw new KeyNotFoundException(message);
  787. }
  788. }
  789. /// <summary>
  790. /// Gets an enumerator that can be used to enumerate the elements of this document.
  791. /// </summary>
  792. /// <returns>An enumerator.</returns>
  793. public virtual IEnumerator<BsonElement> GetEnumerator()
  794. {
  795. return _elements.GetEnumerator();
  796. }
  797. /// <summary>
  798. /// Gets the hash code.
  799. /// </summary>
  800. /// <returns>The hash code.</returns>
  801. public override int GetHashCode()
  802. {
  803. return new Hasher()
  804. .Hash(BsonType)
  805. .HashElements(Elements)
  806. .GetHashCode();
  807. }
  808. /// <summary>
  809. /// Gets the value of an element.
  810. /// </summary>
  811. /// <param name="index">The zero based index of the element.</param>
  812. /// <returns>The value of the element.</returns>
  813. public virtual BsonValue GetValue(int index)
  814. {
  815. return this[index];
  816. }
  817. /// <summary>
  818. /// Gets the value of an element.
  819. /// </summary>
  820. /// <param name="name">The name of the element.</param>
  821. /// <returns>The value of the element.</returns>
  822. public virtual BsonValue GetValue(string name)
  823. {
  824. if (name == null)
  825. {
  826. throw new ArgumentNullException("name");
  827. }
  828. return this[name];
  829. }
  830. /// <summary>
  831. /// Gets the value of an element or a default value if the element is not found.
  832. /// </summary>
  833. /// <param name="name">The name of the element.</param>
  834. /// <param name="defaultValue">The default value returned if the element is not found.</param>
  835. /// <returns>The value of the element or the default value if the element is not found.</returns>
  836. public virtual BsonValue GetValue(string name, BsonValue defaultValue)
  837. {
  838. if (name == null)
  839. {
  840. throw new ArgumentNullException("name");
  841. }
  842. var index = IndexOfName(name);
  843. if (index != -1)
  844. {
  845. return _elements[index].Value;
  846. }
  847. else
  848. {
  849. return defaultValue;
  850. }
  851. }
  852. /// <summary>
  853. /// Gets the index of an element.
  854. /// </summary>
  855. /// <param name="name">The name of the element.</param>
  856. /// <returns>The index of the element, or -1 if the element is not found.</returns>
  857. public virtual int IndexOfName(string name)
  858. {
  859. if (_indexes == null)
  860. {
  861. var count = _elements.Count;
  862. for (var index = 0; index < count; index++)
  863. {
  864. if (_elements[index].Name == name)
  865. {
  866. return index;
  867. }
  868. }
  869. return -1;
  870. }
  871. else
  872. {
  873. int index;
  874. if (_indexes.TryGetValue(name, out index))
  875. {
  876. return index;
  877. }
  878. else
  879. {
  880. return -1;
  881. }
  882. }
  883. }
  884. /// <summary>
  885. /// Inserts a new element at a specified position.
  886. /// </summary>
  887. /// <param name="index">The position of the new element.</param>
  888. /// <param name="element">The element.</param>
  889. public virtual void InsertAt(int index, BsonElement element)
  890. {
  891. var isDuplicate = IndexOfName(element.Name) != -1;
  892. if (isDuplicate && !_allowDuplicateNames)
  893. {
  894. var message = string.Format("Duplicate element name '{0}' not allowed.", element.Name);
  895. throw new InvalidOperationException(message);
  896. }
  897. else
  898. {
  899. _elements.Insert(index, element);
  900. RebuildIndexes();
  901. }
  902. }
  903. /// <summary>
  904. /// Merges another document into this one. Existing elements are not overwritten.
  905. /// </summary>
  906. /// <param name="document">The other document.</param>
  907. /// <returns>The document (so method calls can be chained).</returns>
  908. public virtual BsonDocument Merge(BsonDocument document)
  909. {
  910. Merge(document, false); // don't overwriteExistingElements
  911. return this;
  912. }
  913. /// <summary>
  914. /// Merges another document into this one, specifying whether existing elements are overwritten.
  915. /// </summary>
  916. /// <param name="document">The other document.</param>
  917. /// <param name="overwriteExistingElements">Whether to overwrite existing elements.</param>
  918. /// <returns>The document (so method calls can be chained).</returns>
  919. public virtual BsonDocument Merge(BsonDocument document, bool overwriteExistingElements)
  920. {
  921. if (document == null)
  922. {
  923. throw new ArgumentNullException("document");
  924. }
  925. foreach (BsonElement element in document)
  926. {
  927. if (overwriteExistingElements || !Contains(element.Name))
  928. {
  929. this[element.Name] = element.Value;
  930. }
  931. }
  932. return this;
  933. }
  934. /// <summary>
  935. /// Removes an element from this document (if duplicate element names are allowed
  936. /// then all elements with this name will be removed).
  937. /// </summary>
  938. /// <param name="name">The name of the element to remove.</param>
  939. public virtual void Remove(string name)
  940. {
  941. if (name == null)
  942. {
  943. throw new ArgumentNullException("name");
  944. }
  945. if (_allowDuplicateNames)
  946. {
  947. var count = _elements.Count;
  948. var removedAny = false;
  949. for (var i = count - 1; i >= 0; i--)
  950. {
  951. if (_elements[i].Name == name)
  952. {
  953. _elements.RemoveAt(i);
  954. removedAny = true;
  955. }
  956. }
  957. if (removedAny)
  958. {
  959. RebuildIndexes();
  960. }
  961. }
  962. else
  963. {
  964. var index = IndexOfName(name);
  965. if (index != -1)
  966. {
  967. _elements.RemoveAt(index);
  968. RebuildIndexes();
  969. }
  970. }
  971. }
  972. /// <summary>
  973. /// Removes an element from this document.
  974. /// </summary>
  975. /// <param name="index">The zero based index of the element to remove.</param>
  976. public virtual void RemoveAt(int index)
  977. {
  978. _elements.RemoveAt(index);
  979. RebuildIndexes();
  980. }
  981. /// <summary>
  982. /// Removes an element from this document.
  983. /// </summary>
  984. /// <param name="element">The element to remove.</param>
  985. public virtual void RemoveElement(BsonElement element)
  986. {
  987. if (_elements.Remove(element))
  988. {
  989. RebuildIndexes();
  990. }
  991. }
  992. /// <summary>
  993. /// Sets the value of an element.
  994. /// </summary>
  995. /// <param name="index">The zero based index of the element whose value is to be set.</param>
  996. /// <param name="value">The new value.</param>
  997. /// <returns>The document (so method calls can be chained).</returns>
  998. public virtual BsonDocument Set(int index, BsonValue value)
  999. {
  1000. if (value == null)
  1001. {
  1002. throw new ArgumentNullException("value");
  1003. }
  1004. this[index] = value;
  1005. return this;
  1006. }
  1007. /// <summary>
  1008. /// Sets the value of an element (an element will be added if no element with this name is found).
  1009. /// </summary>
  1010. /// <param name="name">The name of the element whose value is to be set.</param>
  1011. /// <param name="value">The new value.</param>
  1012. /// <returns>The document (so method calls can be chained).</returns>
  1013. public virtual BsonDocument Set(string name, BsonValue value)
  1014. {
  1015. if (name == null)
  1016. {
  1017. throw new ArgumentNullException("name");
  1018. }
  1019. if (value == null)
  1020. {
  1021. throw new ArgumentNullException("value");
  1022. }
  1023. this[name] = value;
  1024. return this;
  1025. }
  1026. /// <summary>
  1027. /// Sets an element of the document (replacing the existing element at that position).
  1028. /// </summary>
  1029. /// <param name="index">The zero based index of the element to replace.</param>
  1030. /// <param name="element">The new element.</param>
  1031. /// <returns>The document.</returns>
  1032. public virtual BsonDocument SetElement(int index, BsonElement element)
  1033. {
  1034. var oldName = _elements[index].Name;
  1035. _elements[index] = element;
  1036. if (element.Name != oldName)
  1037. {
  1038. RebuildIndexes();
  1039. }
  1040. return this;
  1041. }
  1042. /// <summary>
  1043. /// Sets an element of the document (replaces any existing element with the same name or adds a new element if an element with the same name is not found).
  1044. /// </summary>
  1045. /// <param name="element">The new element.</param>
  1046. /// <returns>The document.</returns>
  1047. public virtual BsonDocument SetElement(BsonElement element)
  1048. {
  1049. var index = IndexOfName(element.Name);
  1050. if (index != -1)
  1051. {
  1052. _elements[index] = element;
  1053. }
  1054. else
  1055. {
  1056. Add(element);
  1057. }
  1058. return this;
  1059. }
  1060. /// <summary>
  1061. /// Converts the BsonDocument to a Dictionary&lt;string, object&gt;.
  1062. /// </summary>
  1063. /// <returns>A dictionary.</returns>
  1064. public Dictionary<string, object> ToDictionary()
  1065. {
  1066. var options = new BsonTypeMapperOptions
  1067. {
  1068. DuplicateNameHandling = DuplicateNameHandling.ThrowException,
  1069. MapBsonArrayTo = typeof(object[]), // TODO: should this be List<object>?
  1070. MapBsonDocumentTo = typeof(Dictionary<string, object>),
  1071. MapOldBinaryToByteArray = false
  1072. };
  1073. return (Dictionary<string, object>)BsonTypeMapper.MapToDotNetValue(this, options);
  1074. }
  1075. /// <summary>
  1076. /// Converts the BsonDocument to a Hashtable.
  1077. /// </summary>
  1078. /// <returns>A hashtable.</returns>
  1079. public Hashtable ToHashtable()
  1080. {
  1081. var options = new BsonTypeMapperOptions
  1082. {
  1083. DuplicateNameHandling = DuplicateNameHandling.ThrowException,
  1084. MapBsonArrayTo = typeof(object[]), // TODO: should this be ArrayList?
  1085. MapBsonDocumentTo = typeof(Hashtable),
  1086. MapOldBinaryToByteArray = false
  1087. };
  1088. return (Hashtable)BsonTypeMapper.MapToDotNetValue(this, options);
  1089. }
  1090. /// <summary>
  1091. /// Returns a string representation of the document.
  1092. /// </summary>
  1093. /// <returns>A string representation of the document.</returns>
  1094. public override string ToString()
  1095. {
  1096. return this.ToJson();
  1097. }
  1098. /// <summary>
  1099. /// Tries to get an element of this document.
  1100. /// </summary>
  1101. /// <param name="name">The name of the element.</param>
  1102. /// <param name="value">The element.</param>
  1103. /// <returns>True if an element with that name was found.</returns>
  1104. public virtual bool TryGetElement(string name, out BsonElement value)
  1105. {
  1106. if (name == null)
  1107. {
  1108. throw new ArgumentNullException("name");
  1109. }
  1110. var index = IndexOfName(name);
  1111. if (index != -1)
  1112. {
  1113. value = _elements[index];
  1114. return true;
  1115. }
  1116. else
  1117. {
  1118. value = default(BsonElement);
  1119. return false;
  1120. }
  1121. }
  1122. /// <summary>
  1123. /// Tries to get the value of an element of this document.
  1124. /// </summary>
  1125. /// <param name="name">The name of the element.</param>
  1126. /// <param name="value">The value of the element.</param>
  1127. /// <returns>True if an element with that name was found.</returns>
  1128. public virtual bool TryGetValue(string name, out BsonValue value)
  1129. {
  1130. if (name == null)
  1131. {
  1132. throw new ArgumentNullException("name");
  1133. }
  1134. var index = IndexOfName(name);
  1135. if (index != -1)
  1136. {
  1137. value = _elements[index].Value;
  1138. return true;
  1139. }
  1140. else
  1141. {
  1142. value = null;
  1143. return false;
  1144. }
  1145. }
  1146. // private methods
  1147. private void RebuildIndexes()
  1148. {
  1149. if (_elements.Count < __indexesThreshold)
  1150. {
  1151. _indexes = null;
  1152. return;
  1153. }
  1154. if (_indexes == null)
  1155. {
  1156. _indexes = new Dictionary<string, int>();
  1157. }
  1158. else
  1159. {
  1160. _indexes.Clear();
  1161. }
  1162. // process the elements in reverse order so that in case of duplicates the dictionary ends up pointing at the first one
  1163. var count = _elements.Count;
  1164. for (int index = count - 1; index >= 0; index--)
  1165. {
  1166. BsonElement element = _elements[index];
  1167. _indexes[element.Name] = index;
  1168. }
  1169. }
  1170. // explicit interface implementations
  1171. BsonDocument IConvertibleToBsonDocument.ToBsonDocument()
  1172. {
  1173. return this;
  1174. }
  1175. IEnumerator IEnumerable.GetEnumerator()
  1176. {
  1177. return GetEnumerator();
  1178. }
  1179. }
  1180. }