/Pixie/RecordBase.cs

# · C# · 351 lines · 172 code · 35 blank · 144 comment · 11 complexity · b6d66fb57927255d12b3cfc3c10fac16 MD5 · raw file

  1. //-----------------------------------------------------------------------
  2. // <copyright file="RecordBase.cs" company="Microsoft Corporation">
  3. // Copyright (c) Microsoft Corporation.
  4. // </copyright>
  5. //-----------------------------------------------------------------------
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Diagnostics;
  9. using System.Threading;
  10. using Microsoft.Isam.Esent.Interop;
  11. namespace Microsoft.Isam.Esent
  12. {
  13. /// <summary>
  14. /// One record in a table. A record is identified by a
  15. /// table and a bookmark.
  16. /// </summary>
  17. /// <remarks>
  18. /// A record can be 'lightweight' or 'heavyweight'. A
  19. /// lightweight record uses the JET_TABLEID in the Table object
  20. /// that opened the record. A heavyweight record has its own
  21. /// JET_TABLEID. Heavyweight records are more expensive and have
  22. /// to be closed before the Table can be closed. Heavyweight
  23. /// records are required for updates as updates are per-JET_TABLEID.
  24. /// </remarks>
  25. internal abstract class RecordBase : Record
  26. {
  27. /// <summary>
  28. /// The table containing this Record.
  29. /// </summary>
  30. private readonly TableBase table;
  31. /// <summary>
  32. /// The ID of the record object. This ID has nothing to do with
  33. /// logical record location, it just identifies a specific Record
  34. /// object. Record object that reference the same database object
  35. /// can have different ids.
  36. /// </summary>
  37. private readonly long id;
  38. /// <summary>
  39. /// The last record ID allocated.
  40. /// </summary>
  41. private static long lastId = 1;
  42. /// <summary>
  43. /// Tracing object
  44. /// </summary>
  45. private static Tracer tracer = new Tracer("Record", "Esent Record object", "Record");
  46. /// <summary>
  47. /// Initializes a new instance of the RecordBase class. This constructor
  48. /// is used when a new record is being created and will automatically
  49. /// prepare an insert.
  50. /// </summary>
  51. /// <param name="table">The table containing the record.</param>
  52. protected RecordBase(TableBase table) : this()
  53. {
  54. this.table = table;
  55. this.PrepareInsert();
  56. }
  57. /// <summary>
  58. /// Initializes a new instance of the RecordBase class. This constructor
  59. /// is used for a record which already exists in the table.
  60. /// </summary>
  61. /// <param name="table">The table containing the record.</param>
  62. /// <param name="bookmark">The bookmark of the record.</param>
  63. protected RecordBase(TableBase table, Bookmark bookmark) :
  64. this()
  65. {
  66. this.table = table;
  67. this.Bookmark = bookmark;
  68. }
  69. /// <summary>
  70. /// Initializes a new instance of the RecordBase class from being created.
  71. /// </summary>
  72. protected RecordBase()
  73. {
  74. this.id = Interlocked.Increment(ref RecordBase.lastId);
  75. }
  76. /// <summary>
  77. /// This event is raised when this record closes its cursor.
  78. /// </summary>
  79. public event Action<RecordBase> CursorClosedEvent;
  80. /// <summary>
  81. /// Gets the table that this record is for.
  82. /// </summary>
  83. public virtual TableBase Table
  84. {
  85. get
  86. {
  87. this.CheckNotDisposed();
  88. return this.table;
  89. }
  90. }
  91. /// <summary>
  92. /// Gets or sets a value indicating whether the Record has allocated its own cursor.
  93. /// </summary>
  94. protected bool HasOwnCursor { get; set; }
  95. /// <summary>
  96. /// Gets or sets the cursor on the table.
  97. /// </summary>
  98. protected Cursor MyCursor { get; set; }
  99. /// <summary>
  100. /// Gets or sets a value indicating whether the record is in an update.
  101. /// </summary>
  102. protected bool CurrentlyInUpdate { get; set; }
  103. /// <summary>
  104. /// Gets a value indicating whether the record is disposed.
  105. /// </summary>
  106. protected bool IsDisposed { get; private set; }
  107. /// <summary>
  108. /// Gets or sets the bookmark for the record. This will be null
  109. /// for records that are being inserted (a record doesn't
  110. /// get a bookmark until it is inserted into the table).
  111. /// </summary>
  112. protected Bookmark? Bookmark { get; set; }
  113. /// <summary>
  114. /// Gets the Session for this record.
  115. /// </summary>
  116. protected Session Session
  117. {
  118. get
  119. {
  120. return this.Table.Connection.Session;
  121. }
  122. }
  123. /// <summary>
  124. /// Gets the public ID of this record.
  125. /// </summary>
  126. protected long Id
  127. {
  128. get
  129. {
  130. return this.id;
  131. }
  132. }
  133. /// <summary>
  134. /// Gets the Cursor for this record.
  135. /// </summary>
  136. protected Cursor Cursor
  137. {
  138. get
  139. {
  140. Cursor cursor;
  141. if (this.HasOwnCursor)
  142. {
  143. // Our Cursor is always on the record we want
  144. return this.MyCursor;
  145. }
  146. else if (this.Table.CursorCache.TryGetCursor(this.Id, out cursor))
  147. {
  148. // Our cached cursor will be where we left it.
  149. return cursor;
  150. }
  151. else
  152. {
  153. // A different record moved our cursor. Reposition it.
  154. cursor = this.Table.CursorCache.GetNewCursor(this.Id);
  155. cursor.GotoBookmark(this.Bookmark.Value);
  156. return cursor;
  157. }
  158. }
  159. }
  160. /// <summary>
  161. /// Gets or sets the columns in the record.
  162. /// </summary>
  163. /// <param name="column">Name of the column.</param>
  164. /// <returns>The column as an object.</returns>
  165. public object this[string column]
  166. {
  167. get
  168. {
  169. this.CheckNotDisposed();
  170. return this.RetrieveColumn(column);
  171. }
  172. set
  173. {
  174. this.CheckNotDisposed();
  175. this.SetColumn(column, value);
  176. }
  177. }
  178. /// <summary>
  179. /// Dispose of the object.
  180. /// </summary>
  181. public virtual void Dispose()
  182. {
  183. this.Cancel();
  184. this.CloseCursor();
  185. this.IsDisposed = true;
  186. GC.SuppressFinalize(this);
  187. }
  188. /// <summary>
  189. /// Save changes in the current update. This has no effect if no changes have been
  190. /// made to the record.
  191. /// </summary>
  192. /// <remarks>
  193. /// Implemented by ReadOnlyRecord and ReadWriteRecord.
  194. /// </remarks>
  195. public abstract void Save();
  196. /// <summary>
  197. /// Delete the current record. This disposes the current object.
  198. /// </summary>
  199. /// <remarks>
  200. /// Implemented by ReadOnlyRecord and ReadWriteRecord.
  201. /// </remarks>
  202. public abstract void Delete();
  203. /// <summary>
  204. /// Cancel changes in the current update. This has no effect if no changes have been
  205. /// made to the record.
  206. /// </summary>
  207. /// <remarks>
  208. /// Implemented by ReadOnlyRecord and ReadWriteRecord.
  209. /// </remarks>
  210. public abstract void Cancel();
  211. /// <summary>
  212. /// Set a column in the record.
  213. /// </summary>
  214. /// <remarks>
  215. /// Implemented by ReadOnlyRecord and ReadWriteRecord.
  216. /// </remarks>
  217. /// <param name="column">The column to set.</param>
  218. /// <param name="value">The value to set.</param>
  219. /// <returns>The record the column was set on.</returns>
  220. public abstract Record SetColumn(string column, object value);
  221. /// <summary>
  222. /// Start an insert for the current record. This opens a new JET_TABLEID.
  223. /// </summary>
  224. protected void PrepareInsert()
  225. {
  226. Debug.Assert(!this.HasOwnCursor, "Record should not have its own tableid");
  227. Debug.Assert(!this.CurrentlyInUpdate, "Record is already in an update");
  228. this.CheckConnectionIsInTransaction();
  229. this.OpenOwnCursor();
  230. this.Cursor.PrepareUpdate(JET_prep.Insert);
  231. this.CurrentlyInUpdate = true;
  232. }
  233. /// <summary>
  234. /// Start a replace for the current record. This opens a new JET_TABLEID if
  235. /// the record doesn't already have one.
  236. /// </summary>
  237. protected void PrepareReplace()
  238. {
  239. Debug.Assert(!this.HasOwnCursor, "Record should not have its own tableid");
  240. Debug.Assert(!this.CurrentlyInUpdate, "Record is already in an update");
  241. this.CheckConnectionIsInTransaction();
  242. this.OpenOwnCursor();
  243. this.Cursor.GotoBookmark(this.Bookmark.Value);
  244. this.Cursor.PrepareUpdate(JET_prep.Replace);
  245. this.CurrentlyInUpdate = true;
  246. }
  247. /// <summary>
  248. /// Open a new JET_TABLEID, if this record doesn't already have one.
  249. /// </summary>
  250. protected void OpenOwnCursor()
  251. {
  252. Debug.Assert(!this.HasOwnCursor, "Record already has its own tableid");
  253. Debug.Assert(null == this.MyCursor, "Record has a cursor");
  254. this.MyCursor = this.Table.OpenCursorForRecord(this);
  255. this.HasOwnCursor = true;
  256. Debug.Assert(null != this.MyCursor, "Record does not have a tableid");
  257. }
  258. /// <summary>
  259. /// Close the Cursor allocated by OpenJetTableid.
  260. /// </summary>
  261. protected void CloseCursor()
  262. {
  263. Debug.Assert(!this.CurrentlyInUpdate, "Record is in an update");
  264. if (this.HasOwnCursor)
  265. {
  266. if (null != this.CursorClosedEvent)
  267. {
  268. this.CursorClosedEvent(this);
  269. }
  270. this.table.CloseCursor(this.MyCursor);
  271. this.HasOwnCursor = false;
  272. this.MyCursor = null;
  273. }
  274. Debug.Assert(!this.HasOwnCursor, "Record still has own tableid");
  275. Debug.Assert(null == this.MyCursor, "Record has a tableid");
  276. }
  277. /// <summary>
  278. /// Check that the Connection this record is for is in a transaction.
  279. /// This should be used when preparing an update.
  280. /// </summary>
  281. protected void CheckConnectionIsInTransaction()
  282. {
  283. if (!this.Table.Connection.InTransaction)
  284. {
  285. throw new EsentException("Must be in a transaction to modify a Record");
  286. }
  287. }
  288. /// <summary>
  289. /// Throw an exception if the record has been disposed.
  290. /// </summary>
  291. protected void CheckNotDisposed()
  292. {
  293. if (this.IsDisposed)
  294. {
  295. throw new ObjectDisposedException("Record");
  296. }
  297. }
  298. /// <summary>
  299. /// Retrieve a column from the record.
  300. /// </summary>
  301. /// <param name="column">The column to retrieve.</param>
  302. /// <returns>The value of the column.</returns>
  303. private object RetrieveColumn(string column)
  304. {
  305. RetrieveColumnGrbit grbit = this.CurrentlyInUpdate ? RetrieveColumnGrbit.RetrieveCopy : RetrieveColumnGrbit.None;
  306. try
  307. {
  308. return this.Table.Columns[column].RetrieveColumn(this.Cursor, grbit);
  309. }
  310. catch (KeyNotFoundException ex)
  311. {
  312. throw new EsentColumnNotFoundException(this.Table.TableName, column, ex);
  313. }
  314. }
  315. }
  316. }