PageRenderTime 29ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/VisualStudio/DocumentView/Documents/BaseDocument.cs

https://github.com/rykr/connector-net
C# | 1710 lines | 954 code | 183 blank | 573 comment | 165 complexity | 16d5ab2360ea41a199071b82a8ee95c0 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0, MPL-2.0-no-copyleft-exception
  1. // Copyright (C) 2006-2007 MySQL AB
  2. //
  3. // This file is part of MySQL Tools for Visual Studio.
  4. // MySQL Tools for Visual Studio is free software; you can redistribute it
  5. // and/or modify it under the terms of the GNU Lesser General Public
  6. // License version 2.1 as published by the Free Software Foundation
  7. //
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU Lesser General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU Lesser General Public License
  14. // along with this program; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA using System;
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Text;
  19. using Microsoft.VisualStudio.Shell.Interop;
  20. using System.Diagnostics;
  21. using Microsoft.VisualStudio;
  22. using Microsoft.VisualStudio.Data;
  23. using System.Windows.Forms.Design;
  24. using MySql.Data.VisualStudio.Properties;
  25. using System.Windows.Forms;
  26. using MySql.Data.VisualStudio.Utils;
  27. using Microsoft.VisualStudio.Shell;
  28. using System.Globalization;
  29. using Microsoft.VisualStudio.OLE.Interop;
  30. using System.Data;
  31. using System.ComponentModel;
  32. using System.Data.SqlTypes;
  33. using CommonObject = MySql.Data.VisualStudio.Descriptors.ObjectDescriptor.Attributes;
  34. using MySql.Data.VisualStudio.Descriptors;
  35. using System.ComponentModel.Design;
  36. using System.Drawing.Design;
  37. using MySql.Data.VisualStudio.Dialogs;
  38. using System.Data.Common;
  39. namespace MySql.Data.VisualStudio.DocumentView
  40. {
  41. /// <summary>
  42. /// This is base class for all documents. Implements IVsPersistDocData interface
  43. /// and converts it to set of virtual and abstract methods.
  44. /// </summary>
  45. public abstract class BaseDocument : IDocument
  46. {
  47. #region Constants
  48. /// <summary>Constants for converting enumeration values to MySql-compatible
  49. /// values</summary>
  50. private const char Space = ' ';
  51. private const char Underscore = '_';
  52. #endregion
  53. #region Initialization
  54. /// <summary>
  55. /// This constructor initialize private variables and reads columns list.
  56. /// </summary>
  57. /// <param name="hierarchy">
  58. /// Server explorer facade object to be used for Server Explorer hierarchy interaction.
  59. /// Also used to extract connection.
  60. /// </param>
  61. /// <param name="schemaName">Name of schema to which owns this database object.</param>
  62. /// <param name="objectName">Name of this database object.</param>
  63. /// <param name="isNew">
  64. /// Indicates if this instance represents new database object doesn�t fixed in
  65. /// database yet.
  66. /// </param>
  67. protected BaseDocument(ServerExplorerFacade hierarchy, bool isNew, object[] id)
  68. {
  69. // Check input paramaters
  70. if (hierarchy == null)
  71. throw new ArgumentNullException("hierarchy");
  72. if (id == null)
  73. throw new ArgumentNullException("id");
  74. if (id.Length != ObjectDescriptor.GetIdentifierLength(TypeName))
  75. throw new ArgumentException(
  76. String.Format(
  77. CultureInfo.CurrentCulture,
  78. Resources.Error_InvlaidIdentifier,
  79. id.Length,
  80. TypeName,
  81. ObjectDescriptor.GetIdentifierLength(TypeName)),
  82. "id");
  83. // Storing inputs in private variables
  84. this.hierarchyRef = hierarchy;
  85. this.connectionRef = Hierarchy.Connection;
  86. this.schemaVal = GetSchemaFromID(id);
  87. this.nameVal = GetNameFromID(id);
  88. this.isNewVal = isNew;
  89. }
  90. #endregion
  91. #region Database object properties
  92. /// <summary>
  93. /// Name of schema to which owns this database object. This property is read-only.
  94. /// </summary>
  95. [LocalizableCategory("Category_Identifier")]
  96. [LocalizableDescription("Description_Object_Schema")]
  97. [LocalizableDisplayName("DisplayName_Object_Schema")]
  98. public virtual string Schema
  99. {
  100. get
  101. {
  102. return schemaVal;
  103. }
  104. }
  105. /// <summary>
  106. /// Name of this database object.
  107. /// </summary>
  108. [LocalizableCategory("Category_Identifier")]
  109. [LocalizableDescription("Description_Object_Name")]
  110. [LocalizableDisplayName("DisplayName_Object_Name")]
  111. public virtual string Name
  112. {
  113. get
  114. {
  115. // If object is cloned, return its cloned name
  116. if (!String.IsNullOrEmpty(clonnedName))
  117. return clonnedName;
  118. // It is necessary for all identifier parts to be accessible even
  119. // before attributes are loaded.
  120. if (!IsAttributesLoaded)
  121. return nameVal;
  122. return GetAttributeAsString(Descriptor.NameAttributeName);
  123. }
  124. set
  125. {
  126. if (value == null)
  127. throw new ArgumentNullException("value");
  128. SetAttribute(Descriptor.NameAttributeName, value);
  129. }
  130. }
  131. /// <summary>
  132. /// Unchanged name of the object which is equal to database name.
  133. /// </summary>
  134. [Browsable(false)]
  135. public string OldName
  136. {
  137. get
  138. {
  139. return GetOldAttributeAsString(Descriptor.NameAttributeName);
  140. }
  141. }
  142. /// <summary>
  143. /// Database object comments.
  144. /// </summary>
  145. [LocalizableCategory("Category_Base")]
  146. [LocalizableDescription("Description_Object_Comment")]
  147. [LocalizableDisplayName("DisplayName_Object_Comment")]
  148. [Editor(typeof(MultilineStringEditor),typeof(UITypeEditor))]
  149. public virtual string Comments
  150. {
  151. get
  152. {
  153. return GetAttributeAsString(CommentAttributeName);
  154. }
  155. set
  156. {
  157. if (value == null)
  158. throw new ArgumentNullException("value");
  159. SetAttribute(CommentAttributeName, value);
  160. }
  161. }
  162. /// <summary>
  163. /// Unchanged comments of the object.
  164. /// </summary>
  165. [Browsable(false)]
  166. public string OldComments
  167. {
  168. get
  169. {
  170. return GetOldAttributeAsString(CommentAttributeName);
  171. }
  172. }
  173. #endregion
  174. #region Public properties and methods
  175. /// <summary>
  176. /// Name of server host which owns this database. This property is read-only.
  177. /// </summary>
  178. [LocalizableCategory("Category_Identifier")]
  179. [LocalizableDescription("Description_Object_Server")]
  180. [LocalizableDisplayName("DisplayName_Object_Server")]
  181. public string Server
  182. {
  183. get
  184. {
  185. return Connection.ServerName;
  186. }
  187. }
  188. /// <summary>
  189. /// Indicates if this instance represents new database object
  190. /// doesn�t fixed in database yet.
  191. /// </summary>
  192. [Browsable(false)]
  193. public bool IsNew
  194. {
  195. get
  196. {
  197. return isNewVal;
  198. }
  199. }
  200. /// <summary>
  201. /// Name of the document object type, as it declared in the DataObjectSupport
  202. /// XML.
  203. /// </summary>
  204. [Browsable(false)]
  205. public string TypeName
  206. {
  207. get
  208. {
  209. DocumentObjectAttribute attribute = ReflectionHelper.GetDocumentObjectAttribute(this.GetType());
  210. if (attribute == null)
  211. throw new NotSupportedException(Resources.Error_NotMarkedAsDocument);
  212. return attribute.DocumentTypeName;
  213. }
  214. }
  215. /// <summary>
  216. /// ID of this object as array of strings (null, schema name, object name
  217. /// by default).
  218. /// </summary>
  219. [Browsable(false)]
  220. public virtual object[] ObjectID
  221. {
  222. get
  223. {
  224. return new object[] { null, Schema, Name };
  225. }
  226. }
  227. /// <summary>
  228. /// Pointer to the IDE facade object.
  229. /// </summary>
  230. [Browsable(false)]
  231. public ServerExplorerFacade Hierarchy
  232. {
  233. get
  234. {
  235. return hierarchyRef;
  236. }
  237. }
  238. /// <summary>
  239. /// Data connection object used to interact with MySQL server. This property
  240. /// is read-only.
  241. /// </summary>
  242. [Browsable(false)]
  243. public DataConnectionWrapper Connection
  244. {
  245. get
  246. {
  247. return connectionRef;
  248. }
  249. }
  250. /// <summary>
  251. /// ID of this object as array of strings (null, schema name, object name
  252. /// by default) before changes.
  253. /// </summary>
  254. [Browsable(false)]
  255. public virtual object[] OldObjectID
  256. {
  257. get
  258. {
  259. return new object[] { null, Schema, OldName };
  260. }
  261. }
  262. /// <summary>
  263. /// Changes name of this object and detach it from underlying database object. This object
  264. /// will be considered as new after this method is called. This method should be used before
  265. /// data are loaded.
  266. /// </summary>
  267. /// <param name="newName">New name to use for object.</param>
  268. public void CloneToName(string newName)
  269. {
  270. // Store new name to aplly it after data would be loaded.
  271. clonnedName = newName;
  272. }
  273. #endregion
  274. #region Events
  275. /// <summary>
  276. /// This event fires after complete load of document data.
  277. /// </summary>
  278. [Browsable(false)]
  279. public event EventHandler DataLoaded;
  280. /// <summary>
  281. /// This event fires after any changes in the document data hapens
  282. /// </summary>
  283. [Browsable(false)]
  284. public event EventHandler DataChanged;
  285. /// <summary>
  286. /// This event fires before object data are saved.
  287. /// </summary>
  288. [Browsable(false)]
  289. public event CancelEventHandler Saving;
  290. /// <summary>
  291. /// This event fires after object data are successfully saved.
  292. /// </summary>
  293. [Browsable(false)]
  294. public event EventHandler SuccessfullySaved;
  295. /// <summary>
  296. /// This event fires if attempt to save document fails.
  297. /// </summary>
  298. [Browsable(false)]
  299. public event EventHandler SaveAttemptFailed;
  300. #endregion
  301. #region Virtual document support methods and properties
  302. /// <summary>
  303. /// Dirty flag used to determine whenever data are changed or not.
  304. /// </summary>
  305. [Browsable(false)]
  306. protected virtual bool IsDirty
  307. {
  308. get
  309. {
  310. Debug.Assert(Attributes != null, "Attributes are not read!");
  311. if (Attributes == null)
  312. return false;
  313. // Check, if table contains all necessary columns
  314. foreach (DataColumn column in Attributes.Table.Columns)
  315. if (IsAttributeChanged(column.ColumnName))
  316. return true;
  317. // Attributes are not changed
  318. return false;
  319. }
  320. }
  321. /// <summary>
  322. /// Build SQL query to create new instance of this object in
  323. /// database.
  324. /// </summary>
  325. /// <returns>Create SQL query.</returns>
  326. protected abstract string BuildCreateQuery();
  327. /// <summary>
  328. /// Builds query to modify properties of existing object in database.
  329. /// </summary>
  330. /// <returns>Alter SQL query.</returns>
  331. protected abstract string BuildAlterQuery();
  332. /// <summary>
  333. /// Builds query to pre-drop object before recreating it. Using for stored procedures,
  334. /// indexes, UDF's and other dabase objects which can not be altered and should be
  335. /// recreated instead.
  336. /// </summary>
  337. /// <returns>Returns SQL statement to pre-drop dabase object.</returns>
  338. protected virtual string BuildPreDropQuery()
  339. {
  340. return String.Empty;
  341. }
  342. /// <summary>
  343. /// Validates scalar result of create or alter query. In base implementation always returns true.
  344. /// </summary>
  345. /// <param name="result">Scalar result code returned by query execution.</param>
  346. /// <returns>Returns true if given result indicates succesfull saving.</returns>
  347. protected virtual bool ValidateSaveResult(object result)
  348. {
  349. return true;
  350. }
  351. /// <summary>
  352. /// Validates scalar result of pre-drop query. In base implementation always returns true.
  353. /// </summary>
  354. /// <param name="result">Scalar result code returned by query execution.</param>
  355. /// <returns>Returns true if given result indicates succesfull pre-dropping.</returns>
  356. protected virtual bool ValidatePreDropResult(object result)
  357. {
  358. return true;
  359. }
  360. /// <summary>
  361. /// This method is called when object data are successfully changes. Document
  362. /// should perform all necessary operations to remove IsDirty flag.
  363. /// </summary>
  364. protected virtual void AcceptChanges()
  365. {
  366. // Rename document in the RDT if moniker was changed
  367. if (!DataInterpreter.CompareInvariant(OldMoniker, Moniker))
  368. {
  369. // Get new hierarchy item identifier
  370. int itemID = HierarchyItemID;
  371. // Rename document to the new moniker in the RDT
  372. Hierarchy.Rename(OldMoniker, Moniker, itemID);
  373. // Set new name for hierarchy node
  374. Hierarchy.SetName(HierarchyItemID, Name);
  375. }
  376. // Object is not new any more
  377. isNewVal = false;
  378. // Accept attributes changes
  379. Attributes.AcceptChanges();
  380. }
  381. /// <summary>
  382. /// Saves database object changes to database.
  383. /// </summary>
  384. /// <param name="silent">
  385. /// If this flag set to true, save should be performed without any user interaction.
  386. /// </param>
  387. /// <returns>
  388. /// Returns true if save operation succeed. If user cancels operation or SQL query fails,
  389. /// this method returns false.
  390. /// </returns>
  391. protected virtual bool SaveData(bool silent)
  392. {
  393. // Build SQL query
  394. string query;
  395. string preDropQuery = String.Empty;
  396. if (IsNew)
  397. query = BuildCreateQuery(); // For new object builds create query
  398. else
  399. {
  400. // For existing object builds pre-drop and alter query
  401. preDropQuery = BuildPreDropQuery();
  402. query = BuildAlterQuery();
  403. }
  404. // Check query
  405. if (String.IsNullOrEmpty(query))
  406. return false;
  407. // Confirms query execution if not in silent mode
  408. if (!silent)
  409. {
  410. // Build query to show
  411. string queryToShow = String.IsNullOrEmpty(preDropQuery) ? query : preDropQuery + ";\r\n" + query;
  412. if (SqlPreviewDialog.Show(queryToShow) != DialogResult.OK)
  413. return false;
  414. }
  415. // Executes pre-drop query (if any) and validate results
  416. if (!String.IsNullOrEmpty(preDropQuery)
  417. && !ValidatePreDropResult(Connection.ExecuteScalar(preDropQuery)))
  418. {
  419. Debug.Fail("Failed to pre drop object!");
  420. return false;
  421. }
  422. //Executes query and validates result
  423. if (ValidateSaveResult(Connection.ExecuteScalar(query)))
  424. {
  425. // Need to drop if identifier was changed
  426. bool needDrop = IsAttributeChanged(Descriptor.NameAttributeName);
  427. string oldMoniker = OldMoniker;
  428. // Raises ObjectChangeEvents. We don't know what for, but documentation sais their
  429. // should be raised.
  430. if (IsNew)
  431. {
  432. // If object is new, raise added event
  433. Connection.ObjectChangeEvents.RaiseObjectAdded(TypeName, ObjectID);
  434. }
  435. else
  436. {
  437. // If object was renamed, raise changed with old and new ID, else only with new ID.
  438. if (needDrop)
  439. Connection.ObjectChangeEvents.RaiseObjectChanged(TypeName, OldObjectID, ObjectID);
  440. else
  441. Connection.ObjectChangeEvents.RaiseObjectChanged(TypeName, ObjectID);
  442. }
  443. // Drop current coresponding node, because it can contains wrong object
  444. // identifier and cause Server Explorer to display warning message
  445. if (needDrop)
  446. ResetHierarchyItem();
  447. // Accept all changes to object
  448. AcceptChanges();
  449. // Refreshing server explorer
  450. if (!silent)
  451. Hierarchy.Refresh();
  452. // Reload object data
  453. if (ReloadData())
  454. return false;
  455. // Fire successfully saved for vies to update them
  456. FireSuccessfullySaved();
  457. // Finaly, return true
  458. return true;
  459. }
  460. // ValidateSaveResult fails
  461. return false;
  462. }
  463. /// <summary>
  464. /// Validates document data before saving.
  465. /// </summary>
  466. /// <returns>
  467. /// Returns true if document is consistent and can be saved.
  468. /// Returns false otherwize.
  469. /// </returns>
  470. protected virtual bool ValidateData()
  471. {
  472. // Ask listenters what do they think
  473. if (!FireSaving())
  474. return false;
  475. // Name should not be empty
  476. if (String.IsNullOrEmpty(Name))
  477. {
  478. UIHelper.ShowError(Resources.Error_EmptyName);
  479. return false;
  480. }
  481. // Validate object name
  482. if (!Parser.IsValidIdentifier(Name))
  483. {
  484. UIHelper.ShowError(String.Format(
  485. Resources.Error_InvalidName,
  486. Name));
  487. return false;
  488. }
  489. // If object is new, we need to check for existen objects
  490. if (IsNew)
  491. {
  492. // Check for existent object
  493. DataTable current = ObjectDescriptor.EnumerateObjects(Connection, TypeName, ObjectID);
  494. if (current != null && current.Rows != null && current.Rows.Count > 0)
  495. {
  496. UIHelper.ShowError(String.Format(
  497. Resources.Error_UnableToCreateObjectExists,
  498. Name));
  499. return false;
  500. }
  501. // Check for opened editors if name was changed
  502. if (!DataInterpreter.CompareInvariant(OldName, Name) && Hierarchy.HasDistinctDocument(Moniker, DocumentCookie))
  503. {
  504. UIHelper.ShowError(String.Format(
  505. Resources.Error_UnableToCreateEditorOpened,
  506. Name));
  507. return false;
  508. }
  509. }
  510. // If object is to be renamed, we need to check for existen objects
  511. if (!DataInterpreter.CompareInvariant(OldName, Name))
  512. {
  513. // Check for existent object
  514. DataTable current = ObjectDescriptor.EnumerateObjects(Connection, TypeName, ObjectID);
  515. if (current != null && current.Rows != null && current.Rows.Count > 0)
  516. {
  517. UIHelper.ShowError(String.Format(
  518. Resources.Error_UnableToRenameObjectExists,
  519. OldName,
  520. Name));
  521. return false;
  522. }
  523. // Check for opened editors
  524. if (Hierarchy.HasDistinctDocument(Moniker, DocumentCookie))
  525. {
  526. UIHelper.ShowError(String.Format(
  527. Resources.Error_UnableToRenameEditorOpened,
  528. OldName,
  529. Name));
  530. return false;
  531. }
  532. }
  533. // Finaly, return true
  534. return true;
  535. }
  536. /// <summary>
  537. /// Load database object from database.
  538. /// </summary>
  539. /// <param name="reloading">
  540. /// This flag indicates that object is reloading. Should be ignored in most cases.
  541. /// </param>
  542. /// <returns>Returns true if load succeeds and false otherwise.</returns>
  543. protected virtual bool LoadData(bool reloading)
  544. {
  545. // To read object attributes execute enumeration query for
  546. // object type and with given ID.
  547. DataTable objectTable = ObjectDescriptor.EnumerateObjects(
  548. Connection,
  549. TypeName,
  550. ObjectIDForLoad);
  551. // Check results
  552. if (objectTable == null || (objectTable.Rows.Count == 0 && !IsNew))
  553. {
  554. Debug.Fail("Unable to read data for object type '" + TypeName + "'");
  555. return false;
  556. }
  557. // Fill attributes for new object
  558. if (IsNew)
  559. {
  560. Debug.Assert(objectTable.Rows.Count == 0, "Another row exists in the table for new object!");
  561. // Create new row
  562. DataRow newRow = objectTable.NewRow();
  563. // Fill row with data
  564. FillNewObjectAttributes(newRow);
  565. // Add row to the table
  566. objectTable.Rows.Add(newRow);
  567. }
  568. // Extract attributes row
  569. DataRow attributesRow = objectTable.Rows[0];
  570. Debug.Assert(attributesRow != null, "Failed to extract attributes row!");
  571. // Accept changes
  572. objectTable.AcceptChanges();
  573. // Store attributes row
  574. lock (this)
  575. {
  576. // Reseting attributes
  577. if (reloading)
  578. ResetAttributes();
  579. attributes = attributesRow;
  580. }
  581. // Connect to table notifications
  582. objectTable.RowChanged += new DataRowChangeEventHandler(OnAttributesRowChanged);
  583. return true;
  584. }
  585. /// <summary>
  586. /// Reloads document data and doesn't throws an exception in any case.
  587. /// </summary>
  588. /// <returns>Returns true if reload is succeeded and false otherwize.</returns>
  589. protected virtual bool ReloadData()
  590. {
  591. // Reload document data using interface method to avoid exception
  592. return (this as IVsPersistDocData).ReloadDocData(0) != VSConstants.S_OK;
  593. }
  594. /// <summary>
  595. /// Initialize attributes for the new object.
  596. /// </summary>
  597. /// <param name="newRow">DataRow object to receive new object attributes.</param>
  598. protected virtual void FillNewObjectAttributes(DataRow newRow)
  599. {
  600. if (newRow == null)
  601. throw new ArgumentNullException("newRow");
  602. newRow[Descriptor.SchemaAttributeName] = Schema;
  603. newRow[Descriptor.NameAttributeName] = Name;
  604. }
  605. /// <summary>
  606. /// Releases all specific resources. In most cases do nothing.
  607. /// </summary>
  608. protected virtual void Close()
  609. {
  610. }
  611. /// <summary>
  612. /// Determines if current object can be reloaded. By default,
  613. /// all objects are reloadable.
  614. /// </summary>
  615. [Browsable(false)]
  616. protected virtual bool IsReloadable
  617. {
  618. get
  619. {
  620. return true;
  621. }
  622. }
  623. /// <summary>
  624. /// Returns name of the Comments attribute. Inheritors may override it
  625. /// if it differs from the standard "{TYPE}_COMMENT"
  626. /// </summary>
  627. [Browsable(false)]
  628. protected virtual string CommentAttributeName
  629. {
  630. get
  631. {
  632. return String.Format(
  633. CommonObject.Comments,
  634. TypeName.ToUpperInvariant(),
  635. CultureInfo.InvariantCulture);
  636. }
  637. }
  638. /// <summary>
  639. /// This method is caled then attribtues of this object are changed
  640. /// </summary>
  641. protected virtual void HandleAttributesChanges()
  642. {
  643. }
  644. /// <summary>
  645. /// Extracts Schema part from the object identifier.
  646. /// </summary>
  647. /// <param name="id">Object identifier to process.</param>
  648. /// <returns>Returns Schema part extracted from the object identifier.</returns>
  649. protected virtual string GetSchemaFromID(object[] id)
  650. {
  651. if (id == null)
  652. throw new ArgumentNullException("id");
  653. if (id.Length != ObjectDescriptor.GetIdentifierLength(TypeName)
  654. || String.IsNullOrEmpty(id[1] as string))
  655. throw new ArgumentException(
  656. String.Format(
  657. CultureInfo.CurrentCulture,
  658. Resources.Error_InvlaidIdentifier,
  659. id.Length,
  660. TypeName,
  661. ObjectDescriptor.GetIdentifierLength(TypeName)),
  662. "id");
  663. return id[1] as string;
  664. }
  665. /// <summary>
  666. /// Extracts Name part from the object identifier.
  667. /// </summary>
  668. /// <param name="id">Object identifier to process.</param>
  669. /// <returns>Returns Name part extracted from the object identifier.</returns>
  670. protected virtual string GetNameFromID(object[] id)
  671. {
  672. if (id == null)
  673. throw new ArgumentNullException("id");
  674. if (id.Length != ObjectDescriptor.GetIdentifierLength(TypeName)
  675. || String.IsNullOrEmpty(id[id.Length - 1] as string))
  676. throw new ArgumentException(
  677. String.Format(
  678. CultureInfo.CurrentCulture,
  679. Resources.Error_InvlaidIdentifier,
  680. id.Length,
  681. TypeName,
  682. ObjectDescriptor.GetIdentifierLength(TypeName)),
  683. "id");
  684. // A name of an object is always the last identifier part
  685. return id[id.Length - 1] as string;
  686. }
  687. /// <summary>
  688. /// Resets state of all document data to the New. This method is used for cloning
  689. /// </summary>
  690. protected virtual void ResetToNew()
  691. {
  692. // Mark document as new
  693. isNewVal = true;
  694. // Check attributes
  695. if (Attributes == null || Attributes.Table == null)
  696. {
  697. Debug.Fail("Unable to clone because not loaded yet!");
  698. return;
  699. }
  700. // Dropping the node if it exists in a node tree, and creating the new one
  701. if (Hierarchy.DropObjectNode(HierarchyItemID))
  702. {
  703. hierarchyItemIDVal = Hierarchy.CreateObjectNode();
  704. Hierarchy.Rename(OldMoniker, Moniker, HierarchyItemID);
  705. }
  706. // Extract table
  707. DataTable table = attributes.Table;
  708. // Disconnect from events first
  709. table.RowChanged -= new DataRowChangeEventHandler(OnAttributesRowChanged);
  710. // Make new row for clone
  711. DataRow newRow = table.NewRow();
  712. FillNewObjectAttributes(newRow);
  713. // Set new clonned name
  714. if (clonnedName != null)
  715. {
  716. // Set name in the attributes
  717. newRow[Descriptor.NameAttributeName] = clonnedName;
  718. // Rename in the hierarchy
  719. Hierarchy.SetName(HierarchyItemID, clonnedName);
  720. }
  721. // Store attributes
  722. object[] oldAttributes = Attributes.ItemArray;
  723. // Delete source attributes row
  724. Attributes.Delete();
  725. // Add new row to the table
  726. table.Rows.Add(newRow);
  727. // Accept changes to store original version.
  728. table.AcceptChanges();
  729. // Fill row with data
  730. foreach (DataColumn column in table.Columns)
  731. if (column != null && !DataInterpreter.CompareInvariant(column.ColumnName, Descriptor.NameAttributeName))
  732. DataInterpreter.SetValueIfChanged(newRow, column.ColumnName, oldAttributes[column.Ordinal]);
  733. // Replace attributes with new row
  734. attributes = newRow;
  735. // Connect to events again
  736. table.RowChanged += new DataRowChangeEventHandler(OnAttributesRowChanged);
  737. // Remove cloned state
  738. clonnedName = null;
  739. }
  740. /// <summary>
  741. /// Called if save failed. Base implementation fires propper event.
  742. /// </summary>
  743. protected virtual void SaveFailed()
  744. {
  745. // If object is not new, check for posible pre-drop
  746. if (!IsNew)
  747. {
  748. // Enumerate current object to chek, if it was pre-dropped
  749. DataTable current = ObjectDescriptor.EnumerateObjects(Connection, TypeName, OldObjectID);
  750. if (current == null || current.Rows == null || current.Rows.Count <= 0)
  751. {
  752. // Reset hierarchy item, because it is not functional.
  753. ResetHierarchyItem();
  754. // Warn user
  755. UIHelper.ShowWarning(String.Format(
  756. CultureInfo.CurrentCulture,
  757. Resources.Warning_ObjectPreDropped,
  758. OldName));
  759. // Clone object to new name and reset its state
  760. CloneToName(Name);
  761. ResetToNew();
  762. // Refresh server explorer hierarchy to be sure
  763. Hierarchy.Refresh();
  764. }
  765. }
  766. FireSaveAttemptFailed();
  767. }
  768. #endregion
  769. #region IVsPersistDocData Members
  770. /// <summary>
  771. /// This interface method must check if document data changed or not.
  772. /// IsDirty virtual property is used to detect changes.
  773. /// </summary>
  774. /// <param name="pfDirty">
  775. /// Out parameter must be set to 1 if there are some changes and to 0 otherwise.
  776. /// </param>
  777. /// <returns>
  778. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code.
  779. /// </returns>
  780. int IVsPersistDocData.IsDocDataDirty(out int pfDirty)
  781. {
  782. try
  783. {
  784. // IsDirty virtual property is used to detect changes.
  785. pfDirty = IsNew ||IsDirty ? 1 : 0;
  786. }
  787. catch (Exception e)
  788. {
  789. Trace.TraceError("Error during checking for changes:\n{0}", e.ToString());
  790. // Return unspecified error
  791. pfDirty = 0;
  792. return VSConstants.S_FALSE;
  793. }
  794. return VSConstants.S_OK;
  795. }
  796. /// <summary>
  797. /// Saves document data. At this point proper SQL query should be prepared and executed
  798. /// to fix object changes in database.
  799. /// </summary>
  800. /// <param name="dwSave">Flags whose values are taken from the VSSAVEFLAGS enumeration.</param>
  801. /// <param name="pbstrMkDocumentNew">Pointer to the path to the new document.</param>
  802. /// <param name="pfSaveCanceled">1 if the document was not saved.</param>
  803. /// <returns>
  804. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code.
  805. /// </returns>
  806. int IVsPersistDocData.SaveDocData(
  807. Microsoft.VisualStudio.Shell.Interop.VSSAVEFLAGS dwSave,
  808. out string pbstrMkDocumentNew,
  809. out int pfSaveCanceled)
  810. {
  811. // Initialize output parameters for negative case
  812. pbstrMkDocumentNew = String.Empty;
  813. pfSaveCanceled = 1;
  814. try
  815. {
  816. // Validate data and fire saving event for views to fix all changes
  817. if (!ValidateData())
  818. {
  819. pfSaveCanceled = 1;
  820. // Activates editor, which causes validation failure (note that old identifier should be used)
  821. if (Hierarchy != null)
  822. Hierarchy.FindAndActivateEditor(HierarchyItemID, TypeName, OldObjectID);
  823. return VSConstants.S_OK;
  824. }
  825. if (IsDirty || IsNew)
  826. {
  827. // If SaveData failed (returns false) set pfSaveCanceled flag to 1
  828. pfSaveCanceled = SaveData((dwSave & VSSAVEFLAGS.VSSAVE_SilentSave) != 0) ? 0 : 1;
  829. }
  830. else
  831. {
  832. // Nothing to save, but it's OK
  833. pfSaveCanceled = 0;
  834. }
  835. // Retrives new moniker
  836. pbstrMkDocumentNew = Moniker.ToLowerInvariant();
  837. }
  838. catch (DbException e)
  839. {
  840. Trace.TraceError("Error during saving object:\n{0}", e.ToString());
  841. SqlErrorDialog.ShowError(e, Connection.GetFullStatus());
  842. // Notify all about failure
  843. SaveFailed();
  844. // Return unspecified error
  845. pfSaveCanceled = 1;
  846. return VSConstants.S_FALSE;
  847. }
  848. catch (Exception e)
  849. {
  850. Trace.TraceError("Error during saving object:\n{0}", e.ToString());
  851. UIHelper.ShowError(e);
  852. // Notify all about failure
  853. SaveFailed();
  854. // Return unspecified error
  855. pfSaveCanceled = 1;
  856. return VSConstants.S_FALSE;
  857. }
  858. // Return S_OK
  859. return VSConstants.S_OK;
  860. }
  861. /// <summary>
  862. /// Loads the document data from a given MkDocument.
  863. /// </summary>
  864. /// <param name="pszMkDocument">Document moniker.</param>
  865. /// <returns>
  866. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code.
  867. /// </returns>
  868. int IVsPersistDocData.LoadDocData(string pszMkDocument)
  869. {
  870. // pszMkDocument must be the same as Moniker
  871. if (!DataInterpreter.CompareInvariant(pszMkDocument, Moniker))
  872. {
  873. Debug.Fail("Trying to load from different moniker!");
  874. return VSConstants.E_INVALIDARG;
  875. }
  876. try
  877. {
  878. // Initialy load data
  879. if (!LoadData(false))
  880. {
  881. // Close document and exit
  882. UrgentlyCloseDocument(null);
  883. return VSConstants.S_FALSE;
  884. }
  885. // Check if we should create cloned object. In that case we should reset state of
  886. // all data to new
  887. if (!String.IsNullOrEmpty(clonnedName))
  888. ResetToNew();
  889. // Fire DataLoaded
  890. FireDataLoaded();
  891. }
  892. catch (Exception e)
  893. {
  894. Trace.TraceError("Error during loading object:\n{0}", e.ToString());
  895. // Close document and exit
  896. UrgentlyCloseDocument(e);
  897. // Return unspecified error
  898. return VSConstants.S_FALSE;
  899. }
  900. return VSConstants.S_OK;
  901. }
  902. /// <summary>
  903. /// Returns the unique identifier of the editor factory that created the
  904. /// IVsPersistDocData object. Unfortunately, our data object was created
  905. /// without factory and this method makes no sense.
  906. /// </summary>
  907. /// <param name="pClassID">Pointer to the class identifier of the editor type.</param>
  908. /// <returns>
  909. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code.
  910. /// </returns>
  911. int IVsPersistDocData.GetGuidEditorType(out Guid pClassID)
  912. {
  913. // Predefined GUID
  914. pClassID = GuidList.EditorFactoryCLSID;
  915. return VSConstants.S_OK;
  916. }
  917. /// <summary>
  918. /// This method should release all interfaces and incapacitate itself.
  919. /// </summary>
  920. /// <returns>
  921. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code.
  922. /// </returns>
  923. int IVsPersistDocData.Close()
  924. {
  925. try
  926. {
  927. // Call to virtual method
  928. Close();
  929. }
  930. catch (Exception e)
  931. {
  932. Trace.TraceError("Error during releasing resources:\n{0}", e.ToString());
  933. // Return unspecified error
  934. return VSConstants.S_FALSE;
  935. }
  936. return VSConstants.S_OK;
  937. }
  938. /// <summary>
  939. /// Determines whether the document data can be reloaded.
  940. /// In fileless documents this method doesn�t make much sense.
  941. /// </summary>
  942. /// <param name="pfReloadable">1 if the document data can be reloaded.</param>
  943. /// <returns>
  944. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code.
  945. /// </returns>
  946. int IVsPersistDocData.IsDocDataReloadable(out int pfReloadable)
  947. {
  948. try
  949. {
  950. // Call to virtual property IsReloadable
  951. pfReloadable = IsReloadable ? 1 : 0;
  952. }
  953. catch (Exception e)
  954. {
  955. Trace.TraceError("Error during checking reload ability:\n{0}", e.ToString());
  956. // Return unspecified error
  957. pfReloadable = 0;
  958. return VSConstants.S_FALSE;
  959. }
  960. return VSConstants.S_OK;
  961. }
  962. /// <summary>
  963. /// Called by the Running Document Table (RDT) when it registers the
  964. /// document data in the RDT.
  965. /// </summary>
  966. /// <param name="docCookie">Abstract handle for the document to be registered.</param>
  967. /// <param name="pHierNew">Pointer to the IVsHierarchy interface.</param>
  968. /// <param name="itemidNew">Item identifier of the document to be registered from VSITEM.</param>
  969. /// <returns>
  970. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code.
  971. /// </returns>
  972. int IVsPersistDocData.OnRegisterDocData(
  973. uint docCookie,
  974. IVsHierarchy pHierNew,
  975. uint itemidNew)
  976. {
  977. // Stores data into local variables
  978. documentCookieVal = docCookie;
  979. Debug.Assert(hierarchyRef.Hierarchy == pHierNew, "Registration in wrong hierarchy!");
  980. hierarchyItemIDVal = (int)itemidNew;
  981. return VSConstants.S_OK;
  982. }
  983. /// <summary>
  984. /// Sets the initial name (or path) for unsaved, newly created document data.
  985. /// </summary>
  986. /// <param name="pszDocDataPath">
  987. /// String indicating the path of the document. Most editors can ignore this parameter.
  988. /// It exists for historical reasons.
  989. /// </param>
  990. /// <returns>
  991. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code.
  992. /// </returns>
  993. int IVsPersistDocData.SetUntitledDocPath(string pszDocDataPath)
  994. {
  995. // Do nothing at this point.
  996. return VSConstants.S_OK;
  997. }
  998. /// <summary>
  999. /// Renames the document data. We are not going to allow renames in such way.
  1000. /// </summary>
  1001. /// <param name="grfAttribs">File attribute of the document data to be renamed.</param>
  1002. /// <param name="pHierNew">Pointer to the IVsHierarchy interface of the document being renamed.</param>
  1003. /// <param name="itemidNew">Item identifier of the document being renamed.</param>
  1004. /// <param name="pszMkDocumentNew">Path to the document being renamed.</param>
  1005. /// <returns>
  1006. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code.
  1007. /// </returns>
  1008. int IVsPersistDocData.RenameDocData(
  1009. uint grfAttribs,
  1010. IVsHierarchy pHierNew,
  1011. uint itemidNew,
  1012. string pszMkDocumentNew)
  1013. {
  1014. // This method is called then right hierarchy item fouded
  1015. // (for new saved objects after double-click in the server explorer)
  1016. // pszMkDocument must be the same as OldMoniker or Moniker
  1017. if (!DataInterpreter.CompareInvariant(pszMkDocumentNew, OldMoniker) &&
  1018. !DataInterpreter.CompareInvariant(pszMkDocumentNew, Moniker))
  1019. {
  1020. Debug.Fail("Trying to rename to the different moniker!");
  1021. return VSConstants.E_INVALIDARG;
  1022. }
  1023. Debug.Assert(hierarchyRef.Hierarchy == pHierNew, "Renaming in wrong hierarchy!");
  1024. hierarchyItemIDVal = (int)itemidNew;
  1025. return VSConstants.S_OK;
  1026. }
  1027. /// <summary>
  1028. /// Reloads the document data and in the process determines whether to
  1029. /// ignore a subsequent data change.
  1030. /// </summary>
  1031. /// <param name="grfFlags">
  1032. /// Flag indicating whether to ignore the next data change when reloading the document data.
  1033. /// At this time flag is ignored.
  1034. /// </param>
  1035. /// <returns>
  1036. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code.
  1037. /// </returns>
  1038. int IVsPersistDocData.ReloadDocData(uint grfFlags)
  1039. {
  1040. try
  1041. {
  1042. // Initialy load data
  1043. if (!LoadData(true))
  1044. {
  1045. // Close document and exit
  1046. UrgentlyCloseDocument(null);
  1047. return VSConstants.S_FALSE;
  1048. }
  1049. // Fire DataLoaded
  1050. FireDataLoaded();
  1051. }
  1052. catch (Exception e)
  1053. {
  1054. Trace.TraceError("Error during loading object:\n{0}", e.ToString());
  1055. // Close document and exit
  1056. UrgentlyCloseDocument(e);
  1057. // Return unspecified error
  1058. return VSConstants.S_FALSE;
  1059. }
  1060. return VSConstants.S_OK;
  1061. }
  1062. #endregion
  1063. #region Document attribute row and accessors
  1064. /// <summary>
  1065. /// Attributes row for this object. Describes all object properties.
  1066. /// </summary>
  1067. [Browsable(false)]
  1068. protected DataRow Attributes
  1069. {
  1070. get
  1071. {
  1072. lock (this)
  1073. {
  1074. return attributes;
  1075. }
  1076. }
  1077. }
  1078. /// <summary>
  1079. /// Determines if attributes for the object are loaded.
  1080. /// </summary>
  1081. [Browsable(false)]
  1082. protected bool IsAttributesLoaded
  1083. {
  1084. get
  1085. {
  1086. return Attributes != null;
  1087. }
  1088. }
  1089. /// <summary>
  1090. /// Checks, if value of given attribute is changed.
  1091. /// </summary>
  1092. /// <param name="attributeName">Attribute name.</param>
  1093. /// <returns>Returns true if value of given attribute was changed and false otherwise.</returns>
  1094. protected bool IsAttributeChanged(string attributeName)
  1095. {
  1096. // Check input
  1097. if (attributeName == null)
  1098. throw new ArgumentNullException("attributeName");
  1099. // Check attribute collection
  1100. Debug.Assert(Attributes != null, "Atributes are not read!");
  1101. return DataInterpreter.HasChanged(Attributes, attributeName);
  1102. }
  1103. /// <summary>
  1104. /// Returns string value for given attribute.
  1105. /// </summary>
  1106. /// <param name="attributeName">Attribute name.</param>
  1107. /// <returns>Returns string value for given attribute.</returns>
  1108. protected string GetAttributeAsString(string attributeName)
  1109. {
  1110. // Check input
  1111. if (attributeName == null)
  1112. throw new ArgumentNullException("attributeName");
  1113. // Check attribute collection
  1114. Debug.Assert(Attributes != null, "Atributes are not read!");
  1115. return DataInterpreter.GetString(Attributes, attributeName);
  1116. }
  1117. /// <summary>
  1118. /// Returns string value for given attribute. Changes null's to empty strings.
  1119. /// </summary>
  1120. /// <param name="attributeName">Attribute name.</param>
  1121. /// <returns>Returns string value for given attribute.</returns>
  1122. protected string GetAttributeAsStringNotNull(string attributeName)
  1123. {
  1124. // Check input
  1125. if (attributeName == null)
  1126. throw new ArgumentNullException("attributeName");
  1127. // Check attribute collection
  1128. Debug.Assert(Attributes != null, "Atributes are not read!");
  1129. return DataInterpreter.GetStringNotNull(Attributes, attributeName);
  1130. }
  1131. /// <summary>
  1132. /// Returns integer value for given attribute.
  1133. /// </summary>
  1134. /// <param name="attributeName">Attribute name.</param>
  1135. /// <returns>Returns integer value for given attribute.</returns>
  1136. protected Nullable<Int64> GetAttributeAsInt(string attributeName)
  1137. {
  1138. // Check input
  1139. if (attributeName == null)
  1140. throw new ArgumentNullException("attributeName");
  1141. // Check attribute collection
  1142. Debug.Assert(Attributes != null, "Atributes are not read!");
  1143. return DataInterpreter.GetInt(Attributes, attributeName);
  1144. }
  1145. /// <summary>
  1146. /// Returns SqlBoolean value for given attribute.
  1147. /// </summary>
  1148. /// <param name="attributeName">Attribute name.</param>
  1149. /// <returns>Returns SqlBoolean value for given attribute.</returns>
  1150. protected SqlBoolean GetAttributeAsSqlBool(string attributeName)
  1151. {
  1152. // Check input
  1153. if (attributeName == null)
  1154. throw new ArgumentNullException("attributeName");
  1155. // Check attribute collection
  1156. Debug.Assert(Attributes != null, "Atributes are not read!");
  1157. return DataInterpreter.GetSqlBool(Attributes, attributeName);
  1158. }
  1159. /// <summary>
  1160. /// Returns value of a given attribute as value of a given enumeration
  1161. /// </summary>
  1162. /// <param name="attrName">Attribute name</param>
  1163. /// <param name="defaultValue">Default value for the attribute</param>
  1164. /// <returns>Value of a given attribute as enumeration value</returns>
  1165. protected object GetAttributeAsEnum(string attrName, object defaultValue)
  1166. {
  1167. string strAttrValue = GetAttributeAsString(attrName);
  1168. return ConvertToEnum(strAttrValue, defaultValue, false);
  1169. }
  1170. /// <summary>
  1171. /// Returns value of a given attribute as value of a given enumeration, when
  1172. /// string representation of the value can contain spaces
  1173. /// </summary>
  1174. /// <param name="attrName">Attribute name</param>
  1175. /// <param name="defaultValue">Default value for the attribute</param>
  1176. /// <returns>Value of a given attribute as enumeration value</returns>
  1177. protected object GetAttributeAsSpacedEnum(string attrName, object defaultValue)
  1178. {
  1179. string strAttrValue = GetAttributeAsString(attrName);
  1180. return ConvertToEnum(strAttrValue, defaultValue, true);
  1181. }
  1182. /// <summary>
  1183. /// Returns string value for given attribute, using original data row version.
  1184. /// </summary>
  1185. /// <param name="attributeName">Attribute name.</param>
  1186. /// <returns>Returns string value for given attribute, using original data row version.</returns>
  1187. protected string GetOldAttributeAsString(string attributeName)
  1188. {
  1189. // Check input
  1190. if (attributeName == null)
  1191. throw new ArgumentNullException("attributeName");
  1192. // Check attribute collection
  1193. Debug.Assert(Attributes != null, "Atributes are not read!");
  1194. return DataInterpreter.GetString(Attributes, attributeName, DataRowVersion.Original);
  1195. }
  1196. /// <summary>
  1197. /// Returns integer value for given attribute, using original data row version.
  1198. /// </summary>
  1199. /// <param name="attributeName">Attribute name.</param>
  1200. /// <returns>Returns integer value for given attribute, using original data row version.</returns>
  1201. protected Nullable<Int64> GetOldAttributeAsInt(string attributeName)
  1202. {
  1203. // Check input
  1204. if (attributeName == null)
  1205. throw new ArgumentNullException("attributeName");
  1206. // Check attribute collection
  1207. Debug.Assert(Attributes != null, "Atributes are not read!");
  1208. return DataInterpreter.GetInt(Attributes, attributeName, DataRowVersion.Original);
  1209. }
  1210. /// <summary>
  1211. /// Returns SqlBoolean value for given attribute, using original data row version.
  1212. /// </summary>
  1213. /// <param name="attributeName">Attribute name.</param>
  1214. /// <returns>Returns SqlBoolean value for given attribute, using original data row version.</returns>
  1215. protected SqlBoolean GetOldAttributeAsSqlBool(string attributeName)
  1216. {
  1217. // Check input
  1218. if (attributeName == null)
  1219. throw new ArgumentNullException("attributeName");
  1220. // Check attribute collection
  1221. Debug.Assert(Attributes != null, "Atributes are not read!");
  1222. return DataInterpreter.GetSqlBool(Attributes, attributeName, DataRowVersion.Original);
  1223. }
  1224. /// <summary>
  1225. /// Set new value for given attribute.
  1226. /// </summary>
  1227. /// <param name="attributeName">Attribute name.</param>
  1228. /// <param name="value">New value.</param>
  1229. protected void SetAttribute(string attributeName, object value)
  1230. {
  1231. // Check input
  1232. if (attributeName == null)
  1233. throw new ArgumentNullException("attributeName");
  1234. // Check attribute collection
  1235. Debug.Assert(Attributes != null, "Atributes are not read!");
  1236. object dbValue = value != null ? value : DBNull.Value;
  1237. // Change value if not equals to current
  1238. if (!DataInterpreter.CompareObjects(dbValue, Attributes[attributeName]))
  1239. Attributes[attributeName] = dbValue;
  1240. }
  1241. /// <summary>
  1242. /// Set a new value for a given enumeration attribute, if the latter can have
  1243. /// spaces
  1244. /// </summary>
  1245. /// <param name="attributeName">The attribute's name</param>
  1246. /// <param name="value">The new value</param>
  1247. protected void SetAttributeAsSpacedEnum(string attributeName, object value)
  1248. {
  1249. // Casting an enumeration value to a usual string with spaces
  1250. string strValue =
  1251. (value != null) ? value.ToString().Replace(Underscore, Space) : null;
  1252. SetAttribute(attributeName, strValue);
  1253. }
  1254. /// <summary>
  1255. /// Allows inheritors to change attributes row.
  1256. /// </summary>
  1257. /// <param name="attributesRow">New data row with attributes.</param>
  1258. protected void ChangeAttributesRow(DataRow attributesRow)
  1259. {
  1260. if (attributesRow == null)
  1261. throw new ArgumentNullException("attributesRow");
  1262. // Validate datatable first
  1263. if (attributesRow.Table == null)
  1264. {
  1265. Debug.Fail("Attributes table is is missing!");
  1266. return;
  1267. }
  1268. // Store attributes row
  1269. lock (this)
  1270. {
  1271. // Reseting attributes
  1272. ResetAttributes();
  1273. attributes = attributesRow;
  1274. }
  1275. // Connect to table notifications
  1276. attributesRow.Table.RowChanged += new DataRowChangeEventHandler(OnAttributesRowChanged);
  1277. }
  1278. #endregion
  1279. #region Protected properties
  1280. /// <summary>
  1281. /// Abstract handle for the document to be registered in RDT.
  1282. /// </summary>
  1283. [Browsable(false)]
  1284. protected uint DocumentCookie
  1285. {
  1286. get
  1287. {
  1288. return documentCookieVal;
  1289. }
  1290. }
  1291. protected int HierarchyItemIDVal
  1292. {
  1293. get { return hierarchyItemIDVal; }
  1294. }
  1295. /// <summary>
  1296. /// Item identifier of the document in the hierarchy.
  1297. /// </summary>
  1298. [Browsable(false)]
  1299. protected int HierarchyItemID
  1300. {
  1301. get
  1302. {
  1303. // Check current hierarchy item id
  1304. string name = String.Empty;
  1305. try
  1306. {
  1307. // If name can be retrived, it is ok
  1308. if (hierarchyItemIDVal != int.MinValue)
  1309. name = Hierarchy.GetName(hierarchyItemIDVal);
  1310. }
  1311. catch
  1312. {
  1313. name = string.Empty;
  1314. }
  1315. // Compare extracted name with our old name. Type is not checked, because node can be contextless
  1316. // and has no type.
  1317. if (DataInterpreter.CompareInvariant(name, OldName))
  1318. return hierarchyItemIDVal;
  1319. // If item not found and names are different, create new item and set its name
  1320. hierarchyItemIDVal = Hierarchy.CreateObjectNode();
  1321. Hierarchy.SetName(hierarchyItemIDVal, OldName);
  1322. // Rename document to the new hierarchy item in the RDT
  1323. Hierarchy.Rename(OldMoniker, OldMoniker, hierarchyItemIDVal);
  1324. // If search failed, returns stored item
  1325. return hierarchyItemIDVal;
  1326. }
  1327. }
  1328. /// <summary>
  1329. /// Returns moniker string, used to register this object in RDT.
  1330. /// </summary>
  1331. [Browsable(false)]
  1332. protected String Moniker
  1333. {
  1334. get
  1335. {
  1336. return Hierarchy.BuildMoniker(TypeName, ObjectID);
  1337. }
  1338. }
  1339. /// <summary>
  1340. /// Returns old moniker string (moniker befre changes are applyed),
  1341. /// used to register this object in RDT.
  1342. /// </summary>
  1343. [Browsable(false)]
  1344. protected String OldMoniker
  1345. {
  1346. get
  1347. {
  1348. return Hierarchy.BuildMoniker(TypeName, OldObjectID);
  1349. }
  1350. }
  1351. /// <summary>
  1352. /// Returns descriptor for this object type
  1353. /// </summary>
  1354. [Browsable(false)]
  1355. protected IObjectDescriptor Descriptor
  1356. {
  1357. get
  1358. {
  1359. if (descriptorRef == null)
  1360. {
  1361. descriptorRef = ObjectDescriptorFactory.Instance.CreateDescriptor(TypeName);
  1362. if (descriptorRef == null)
  1363. throw new NotSupportedException(String.Format(
  1364. CultureInfo.CurrentCulture,
  1365. Resources.Error_UnableToGetDescriptor,
  1366. TypeName));
  1367. }
  1368. return descriptorRef;
  1369. }
  1370. }
  1371. /// <summary>
  1372. /// Returns object identifier which should be used for data loading. This identifier
  1373. /// is current object identifier for all objects except cloned. For cloned object it
  1374. /// is the identifier of the source object.
  1375. /// </summary>
  1376. [Browsable(false)]
  1377. public virtual object[] ObjectIDForLoad
  1378. {
  1379. get
  1380. {
  1381. // Extract current id
  1382. object[] id = ObjectID;
  1383. // If object is clonned, replace name in the identifier by the original name
  1384. if (clonnedName != null)
  1385. id[id.Length - 1] = nameVal;
  1386. // Return result
  1387. return id;
  1388. }
  1389. }
  1390. #endregion
  1391. #region Protected methods
  1392. /// <summary>
  1393. /// Fires DataLoaded event
  1394. /// </summary>
  1395. protected void FireDataLoaded()
  1396. {
  1397. if (DataLoaded != null)
  1398. DataLoaded(this, EventArgs.Empty);
  1399. }
  1400. /// <summary>
  1401. /// Fires DataChanged event
  1402. /// </summary>
  1403. protected void FireDataChanged()
  1404. {
  1405. if (DataChanged != null)
  1406. DataChanged(this, EventArgs.Empty);
  1407. }
  1408. /// <summary>
  1409. /// Fires Saving event.
  1410. /// </summary>
  1411. /// <returns>Returns false if any listener cancels saving.</returns>
  1412. protected bool FireSaving()
  1413. {
  1414. if (Saving != null)
  1415. {
  1416. CancelEventArgs e = new CancelEventArgs(false);
  1417. Saving(this, e);
  1418. return !e.Cancel;
  1419. }
  1420. return true;
  1421. }
  1422. /// <summary>
  1423. /// Fires SuccessfullySaved event
  1424. /// </summary>
  1425. protected void FireSuccessfullySaved()
  1426. {
  1427. if (SuccessfullySaved != null)
  1428. SuccessfullySaved(this, EventArgs.Empty);
  1429. }
  1430. /// <summary>
  1431. /// Fires SaveAttemptFailed event
  1432. /// </summary>
  1433. protected void FireSaveAttemptFailed()
  1434. {
  1435. if (SaveAttemptFailed != null)
  1436. SaveAttemptFailed(this, EventArgs.Empty);
  1437. }
  1438. /// <summary>
  1439. /// Urgently close document is something wrong.
  1440. /// </summary>
  1441. protected void UrgentlyCloseDocument(Exception e)
  1442. {
  1443. // Close document and exit
  1444. Hierarchy.CloseDocument(TypeName, ObjectID);
  1445. // If exception is given, alert user
  1446. if (e != null)
  1447. UIHelper.ShowError(e);
  1448. // Alert user that document windows is closed
  1449. UIHelper.ShowError(Resources.Error_ReloadFailed);
  1450. }
  1451. /// <summary>
  1452. /// Resets hierarchy item identifier to be recreated later
  1453. /// </summary>
  1454. protected void ResetHierarchyItem()
  1455. {
  1456. // Drop current hierarchy node
  1457. Hierarchy.DropObjectNode(HierarchyItemID);
  1458. // Reset hierarchy item identifier to be recreated later
  1459. hierarchyItemIDVal = int.MinValue;
  1460. }
  1461. #endregion
  1462. #region Private methods
  1463. /// <summary>
  1464. /// Resets attributes row before reload and disconnects events.
  1465. /// </summary>
  1466. private void ResetAttributes()
  1467. {
  1468. Debug.Assert(attributes != null, "Attributes are not loaded!");
  1469. attributes.Table.RowChanged -= new DataRowChangeEventHandler(OnAttributesRowChanged);
  1470. attributes.Table.Dispose();
  1471. attributes = null;
  1472. }
  1473. /// <summary>
  1474. /// Handles notification about attributes row changes.
  1475. /// </summary>
  1476. /// <param name="sender">Sender of the event.</param>
  1477. /// <param name="e">Detailed information on the event.</param>
  1478. private void OnAttributesRowChanged(object sender, DataRowChangeEventArgs e)
  1479. {
  1480. Debug.Assert(e != null && e.Row == Attributes, "Empty event argumets provided or wrong DataRow included!");
  1481. HandleAttributesChanges();
  1482. FireDataChanged();
  1483. }
  1484. /// <summary>
  1485. /// Converts a string to an enumeration
  1486. /// </summary>
  1487. /// <param name="strValue">String representation of a value to convert</param>
  1488. /// <param name="defaultValue">Default value for the enumeration</param>
  1489. /// <param name="hasSpaces">Indicates if the string representation can have
  1490. /// spaces</param>
  1491. /// <returns>The value converted to enumeration if possible</returns>
  1492. private object ConvertToEnum(string strValue, object defaultValue, bool hasSpaces)
  1493. {
  1494. if (string.IsNullOrEmpty(strValue))
  1495. return defaultValue;
  1496. if (defaultValue == null)
  1497. // The value is not typed; returning it as a string
  1498. return strValue;
  1499. if (hasSpaces)
  1500. // Enumeration value can't have spaces
  1501. strValue = strValue.Replace(Space, Underscore);
  1502. // Casting a string representation of the value to the enumeration type
  1503. Type enumType = defaultValue.GetType();
  1504. try
  1505. {
  1506. return Enum.Parse(enumType, strValue, true);
  1507. }
  1508. catch (ArgumentException)
  1509. {
  1510. return defaultValue;
  1511. }
  1512. }
  1513. #endregion
  1514. #region Private variables to store properties
  1515. private uint documentCookieVal;
  1516. private int hierarchyItemIDVal;
  1517. private ServerExplorerFacade hierarchyRef;
  1518. private DataConnectionWrapper connectionRef;
  1519. private string schemaVal;
  1520. private string nameVal;
  1521. private bool isNewVal;
  1522. private DataRow attributes;
  1523. private IObjectDescriptor descriptorRef;
  1524. private string clonnedName = null;
  1525. #endregion
  1526. }
  1527. }