/// <summary>
		/// Renames or moves a File.
		/// </summary>
		/// <param name="oldFullName">The old full name of the File.</param>
		/// <param name="newFullName">The new full name of the File.</param>
		/// <returns><c>true</c> if the File is renamed, <c>false</c> otherwise.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="oldFullName"/> or <paramref name="newFullName"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="oldFullName"/> or <paramref name="newFullName"/> are empty, or if the old file does not exist, or if the new file already exist.</exception>
		public bool RenameFile(string oldFullName, string newFullName) {
			if(oldFullName == null) throw new ArgumentNullException("oldFullName");
			if(oldFullName.Length == 0) throw new ArgumentException("Old Full Name cannot be empty", "oldFullName");
			if(newFullName == null) throw new ArgumentNullException("newFullName");
			if(newFullName.Length == 0) throw new ArgumentException("New Full Name cannot be empty", "newFullName");

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			if(!FileExists(transaction, oldFullName)) {
				RollbackTransaction(transaction);
				throw new ArgumentException("File does not exist", "oldFullName");
			}
			if(FileExists(transaction, newFullName)) {
				RollbackTransaction(transaction);
				throw new ArgumentException("File already exists", "newFullPath");
			}

			string oldDirectory, newDirectory, oldFilename, newFilename;
			SplitFileFullName(oldFullName, out oldDirectory, out oldFilename);
			SplitFileFullName(newFullName, out newDirectory, out newFilename);

			string query = queryBuilder.Update("File", new string[] { "Name" }, new string[] { "NewName" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "OldName");
			query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "OldDirectory");

			List<Parameter> parameters = new List<Parameter>(3);
			parameters.Add(new Parameter(ParameterType.String, "NewName", newFilename));
			parameters.Add(new Parameter(ParameterType.String, "OldName", oldFilename));
			parameters.Add(new Parameter(ParameterType.String, "OldDirectory", oldDirectory));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);
			if(rows == 1) CommitTransaction(transaction);
			else RollbackTransaction(transaction);

			return rows == 1;
		}
		/// <summary>
		/// Gets all the sub-namespaces.
		/// </summary>
		/// <returns>The sub-namespaces, sorted by name.</returns>
		public NamespaceInfo[] GetNamespaces() {
			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			// select ... from Namespace left join Page on Namespace.DefaultPage = Page.Name where Namespace.Name <> '' and (Namespace.DefaultPage is null or Page.Namespace <> '')
			string query = queryBuilder.SelectFrom("Namespace", "Page", "DefaultPage", "Name", Join.LeftJoin, new string[] { "Name", "DefaultPage" }, new string[] { "CreationDateTime" });
			query = queryBuilder.Where(query, "Namespace", "Name", WhereOperator.NotEquals, "Empty1");
			query = queryBuilder.AndWhere(query, "Namespace", "DefaultPage", WhereOperator.IsNull, null, true, false);
			query = queryBuilder.OrWhere(query, "Page", "Namespace", WhereOperator.NotEquals, "Empty2", false, true);
			query = queryBuilder.OrderBy(query, new[] { "Namespace_Name" }, new[] { Ordering.Asc });

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Empty1", " "));
			parameters.Add(new Parameter(ParameterType.String, "Empty2", " "));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				List<NamespaceInfo> result = new List<NamespaceInfo>(10);

				while(reader.Read()) {
					string realName = reader["Namespace_Name"] as string;
					string page = GetNullableColumn<string>(reader, "Namespace_DefaultPage", null);
					PageInfo defaultPage = string.IsNullOrEmpty(page) ? null :
						new PageInfo(NameTools.GetFullName(realName, page), this, (DateTime)reader["Page_CreationDateTime"]);

					// The query returns duplicate entries if the main page of two or more namespaces have the same name
					if(result.Find(n => { return n.Name.Equals(realName); }) == null) {
						result.Add(new NamespaceInfo(realName, this, defaultPage));
					}
				}

				CloseReader(command, reader);

				return result.ToArray();
			}
			else return null;
		}
		/// <summary>
		/// Gets a meta-data item's content.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <param name="tag">The tag that specifies the context (usually the namespace).</param>
		/// <returns>The content.</returns>
		public string GetMetaDataItem(MetaDataItem item, string tag) {
			if(string.IsNullOrEmpty(tag)) tag = " ";

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectFrom("MetaDataItem", new string[] { "Data" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Tag", WhereOperator.Equals, "Tag");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", item.ToString()));
			parameters.Add(new Parameter(ParameterType.String, "Tag", tag));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			string value = ExecuteScalar<string>(command, " ");

			return value;
		}
		/// <summary>
		/// Removes a Message.
		/// </summary>
		/// <param name="transaction">A database transaction.</param>
		/// <param name="page">The Page.</param>
		/// <param name="id">The ID of the Message to remove.</param>
		/// <param name="removeReplies">A value specifying whether or not to remove the replies.</param>
		/// <returns>True if the Message is removed successfully.</returns>
		private bool RemoveMessage(DbTransaction transaction, PageInfo page, int id, bool removeReplies) {
			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			Message[] messages = GetMessages(transaction, page);
			if(messages == null) return false;
			Message message = FindMessage(messages, id);
			if(message == null) return false;
			Message parent = FindAnchestor(messages, id);
			int parentId = parent != null ? parent.ID : -1;

			UnindexMessage(page, message.ID, message.Subject, message.DateTime, message.Body, transaction);

			if(removeReplies) {
				// Recursively remove all replies BEFORE removing parent (depth-first)
				foreach(Message reply in message.Replies) {
					if(!RemoveMessage(transaction, page, reply.ID, true)) return false;
				}
			}

			// Remove this message
			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("Message");
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.AndWhere(query, "Id", WhereOperator.Equals, "Id");

			List<Parameter> parameters = new List<Parameter>(3);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
			parameters.Add(new Parameter(ParameterType.Int16, "Id", (short)id));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(!removeReplies && rows == 1) {
				// Update replies' parent id

				query = queryBuilder.Update("Message", new string[] { "Parent" }, new string[] { "NewParent" });
				query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
				query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
				query = queryBuilder.AndWhere(query, "Parent", WhereOperator.Equals, "OldParent");

				parameters = new List<Parameter>(4);
				if(parentId != -1) parameters.Add(new Parameter(ParameterType.Int16, "NewParent", parentId));
				else parameters.Add(new Parameter(ParameterType.Int16, "NewParent", DBNull.Value));
				parameters.Add(new Parameter(ParameterType.String, "Page", name));
				parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
				parameters.Add(new Parameter(ParameterType.Int16, "OldParent", (short)id));

				command = builder.GetCommand(transaction, query, parameters);

				rows = ExecuteNonQuery(command, false);
			}

			return rows > 0;
		}
		/// <summary>
		/// Removes a Navigation Path.
		/// </summary>
		/// <param name="connection">A database connection.</param>
		/// <param name="path">The navigation path to remove.</param>
		/// <returns><c>true</c> if the path is removed, <c>false</c> otherwise.</returns>
		private bool RemoveNavigationPath(DbConnection connection, NavigationPath path) {
			string nspace, name;
			NameTools.ExpandFullName(path.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("NavigationPath");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));

			DbCommand command = builder.GetCommand(connection, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			return rows > 0;
		}
		/// <summary>
		/// Gets the Page Messages.
		/// </summary>
		/// <param name="connection">A database connection.</param>
		/// <param name="page">The Page.</param>
		/// <returns>The list of the <b>first-level</b> Messages, containing the replies properly nested, sorted by date/time.</returns>
		private Message[] GetMessages(DbConnection connection, PageInfo page) {
			if(GetPage(connection, page.FullName) == null) return null;

			// 1. Load all messages in memory in a dictionary id->message
			// 2. Build tree using ParentID

			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectFrom("Message", new string[] { "Id", "Parent", "Username", "Subject", "DateTime", "Body" });
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.OrderBy(query, new string[] { "DateTime", "Id" }, new Ordering[] { Ordering.Asc, Ordering.Asc });

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));

			DbCommand command = builder.GetCommand(connection, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				Dictionary<short, Message> allMessages = new Dictionary<short, Message>(50);
				List<short> ids = new List<short>(50);
				List<short?> parents = new List<short?>(50);

				while(reader.Read()) {
					Message msg = new Message((short)Convert.ChangeType( reader["Id"],typeof(short)), reader["Username"] as string, reader["Subject"] as string,
						(DateTime)reader["DateTime"], reader["Body"] as string);

					ids.Add((short)msg.ID);

					// Import from V2: parent = -1, otherwise null
					if(!IsDBNull(reader, "Parent")) {
						short par = (short)Convert.ChangeType(reader["Parent"],typeof(short));
						if(par >= 0) parents.Add(par);
						else parents.Add(null);
					}
					else parents.Add(null);

					allMessages.Add((short)msg.ID, msg);
				}

				CloseReader(reader);

				// Add messages to their parents and build the top-level messages list
				List<Message> result = new List<Message>(20);

				for(int i = 0; i < ids.Count; i++) {
					short? currentParent = parents[i];
					short currentId = ids[i];

					if(currentParent.HasValue) {
						List<Message> replies = new List<Message>(allMessages[currentParent.Value].Replies);
						replies.Add(allMessages[currentId]);
						allMessages[currentParent.Value].Replies = replies.ToArray();
					}
					else result.Add(allMessages[currentId]);
				}

				return result.ToArray();
			}
			else return null;
		}
		/// <summary>
		/// Removes all messages for a page and stores the new messages.
		/// </summary>
		/// <param name="page">The page.</param>
		/// <param name="messages">The new messages to store.</param>
		/// <returns><c>true</c> if the messages are stored, <c>false</c> otherwise.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/> or <paramref name="messages"/> are <c>null</c>.</exception>
		public bool BulkStoreMessages(PageInfo page, Message[] messages) {
			if(page == null) throw new ArgumentNullException("page");
			if(messages == null) throw new ArgumentNullException("messages");

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			if(GetPage(transaction, page.FullName) == null) {
				RollbackTransaction(transaction);
				return false;
			}

			foreach(Message msg in GetMessages(transaction, page)) {
				UnindexMessageTree(page, msg, transaction);
			}

			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("Message");
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			ExecuteNonQuery(command, false);

			List<Message> allMessages;
			List<int> parents;

			UnTreeMessages(messages, out allMessages, out parents, -1);

			string finalQuery = "";
			int count = 1;
			string countString;
			parameters = new List<Parameter>(MaxStatementsInBatch * 8);

			int rowsDone = 0;

			for(int i = 0; i < allMessages.Count; i++) {
				// Execute the batch in smaller chunks

				Message msg = allMessages[i];
				int parent = parents[i];

				countString = count.ToString();

				query = queryBuilder.InsertInto("Message", new string[] { "Page", "Namespace", "Id", "Parent", "Username", "Subject", "DateTime", "Body" },
					new string[] { "Page" + countString, "Namespace" + countString, "Id" + countString, "Parent" + countString, "Username" + countString, "Subject" + countString, "DateTime" + countString, "Body" + countString });

				parameters.Add(new Parameter(ParameterType.String, "Page" + countString, name));
				parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace));
				parameters.Add(new Parameter(ParameterType.Int16, "Id" + countString, (short)msg.ID));
				if(parent != -1) parameters.Add(new Parameter(ParameterType.Int16, "Parent" + countString, parent));
				else parameters.Add(new Parameter(ParameterType.Int16, "Parent" + countString, DBNull.Value));
				parameters.Add(new Parameter(ParameterType.String, "Username" + countString, msg.Username));
				parameters.Add(new Parameter(ParameterType.String, "Subject" + countString, msg.Subject));
				parameters.Add(new Parameter(ParameterType.DateTime, "DateTime" + countString, msg.DateTime));
				parameters.Add(new Parameter(ParameterType.String, "Body" + countString, msg.Body));

				finalQuery = queryBuilder.AppendForBatch(finalQuery, query);

				count++;

				if(count == MaxStatementsInBatch) {
                    finalQuery = queryBuilder.BuildBatchCommand(finalQuery);
					command = builder.GetCommand(transaction, finalQuery, parameters);

                    rowsDone += ExecuteNonQuery(command, false, true, count); 

					finalQuery = "";
					count = 1;
					parameters.Clear();
				}
			}

			if(finalQuery.Length > 0) {
				command = builder.GetCommand(transaction, finalQuery, parameters);

                rowsDone += ExecuteNonQuery(command, false, true, count); 
			}

			if(rowsDone == allMessages.Count) {
				foreach(Message msg in messages) {
					IndexMessageTree(page, msg, transaction);
				}
				CommitTransaction(transaction);
				return true;
			}
			else {
				RollbackTransaction(transaction);
				return false;
			}
		}
		/// <summary>
		/// Removes a Category.
		/// </summary>
		/// <param name="transaction">A database transaction.</param>
		/// <param name="category">The Category to remove.</param>
		/// <returns>True if the Category has been removed successfully.</returns>
		private bool RemoveCategory(DbTransaction transaction, CategoryInfo category) {
			string nspace = null;
			string name = null;
			NameTools.ExpandFullName(category.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("Category");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			return rows > 0;
		}
		/// <summary>
		/// Merges two Categories.
		/// </summary>
		/// <param name="source">The source Category.</param>
		/// <param name="destination">The destination Category.</param>
		/// <returns>The correct <see cref="T:CategoryInfo" /> object.</returns>
		/// <remarks>The destination Category remains, while the source Category is deleted, and all its Pages re-bound 
		/// in the destination Category.</remarks>
		/// <exception cref="ArgumentNullException">If <paramref name="source"/> or <paramref name="destination"/> are <c>null</c>.</exception>
		public CategoryInfo MergeCategories(CategoryInfo source, CategoryInfo destination) {
			if(source == null) throw new ArgumentNullException("source");
			if(destination == null) throw new ArgumentNullException("destination");

			// 1. Check for same namespace
			// 2. Load all pages in source
			// 3. Load all pages in destination
			// 4. Merge lists in memory
			// 5. Delete all destination bindings
			// 6. Delete source cat
			// 7. Insert new bindings stored in memory

			string sourceNs = NameTools.GetNamespace(source.FullName);
			string destinationNs = NameTools.GetNamespace(destination.FullName);
			
			// If one is null and the other not null, fail
			if(sourceNs == null && destinationNs != null || sourceNs != null && destinationNs == null) return null;
			else {
				// Both non-null or both null
				if(sourceNs != null) {
					// Both non-null, check names
					NamespaceInfo tempSource = new NamespaceInfo(sourceNs, this, null);
					NamespaceInfo tempDest = new NamespaceInfo(destinationNs, this, null);
					// Different names, fail
					if(new NamespaceComparer().Compare(tempSource, tempDest) != 0) return null;
				}
				// else both null, OK
			}

			string nspace = sourceNs != null ? sourceNs : " ";

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			CategoryInfo actualSource = GetCategory(transaction, source.FullName);
			CategoryInfo actualDestination = GetCategory(transaction, destination.FullName);

			if(actualSource == null) {
				RollbackTransaction(transaction);
				return null;
			}
			if(actualDestination == null) {
				RollbackTransaction(transaction);
				return null;
			}

			string destinationName = NameTools.GetLocalName(actualDestination.FullName);

			string[] mergedPages = MergeArrays(actualSource.Pages, actualDestination.Pages);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("CategoryBinding");
			query = queryBuilder.Where(query, "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.AndWhere(query, "Category", WhereOperator.Equals, "Category");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
			parameters.Add(new Parameter(ParameterType.String, "Category", destinationName));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == -1) {
				RollbackTransaction(transaction);
				return null;
			}

			if(!RemoveCategory(transaction, source)) {
				RollbackTransaction(transaction);
				return null;
			}

			string finalQuery = "";
			parameters = new List<Parameter>(MaxStatementsInBatch * 3);
			rows = 0;
			int count = 1;
			string countString;

			foreach(string page in mergedPages) {
				// This batch is executed in small chunks (MaxStatementsInBatch) to avoid exceeding DB's max batch length/size

				countString = count.ToString();

				query = queryBuilder.InsertInto("CategoryBinding", new string[] { "Namespace", "Category", "Page" },
					new string[] { "Namespace" + countString, "Category" + countString, "Page" + countString });
				finalQuery = queryBuilder.AppendForBatch(finalQuery, query);

				parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace));
				parameters.Add(new Parameter(ParameterType.String, "Category" + countString, destinationName));
				parameters.Add(new Parameter(ParameterType.String, "Page" + countString, NameTools.GetLocalName(page)));

				count++;

				if(count == MaxStatementsInBatch) {
					// Batch is complete -> execute
                    finalQuery = queryBuilder.BuildBatchCommand(finalQuery);
					command = builder.GetCommand(transaction, finalQuery, parameters);
                    rows += ExecuteNonQuery(command, false, true, count);

					count = 1;
					finalQuery = "";
					parameters.Clear();
				}
			}

			if(finalQuery.Length > 0) {
				// Execute remaining queries, if any
				command = builder.GetCommand(transaction, finalQuery, parameters);
                rows += ExecuteNonQuery(command, false, true, count);
			}

			if(rows == mergedPages.Length) {
				CommitTransaction(transaction);
				CategoryInfo result = new CategoryInfo(actualDestination.FullName, this);
				result.Pages = mergedPages;
				return result;
			}
			else {
				RollbackTransaction(transaction);
				return null;
			}
		}
		/// <summary>
		/// Gets all the categories of a page.
		/// </summary>
		/// <param name="page">The page.</param>
		/// <returns>The categories, sorted by name.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/> is <c>null</c>.</exception>
		public CategoryInfo[] GetCategoriesForPage(PageInfo page) {
			if(page == null) throw new ArgumentNullException("page");

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string nspace, pageName;
			NameTools.ExpandFullName(page.FullName, out nspace, out pageName);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			string query = queryBuilder.SelectFrom("Category", "CategoryBinding", new string[] { "Name", "Namespace" }, new string[] { "Category", "Namespace" }, Join.LeftJoin,
				new string[] { "Name", "Namespace" }, new string[] { "Page" });
			query = queryBuilder.Where(query, "CategoryBinding", "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.AndWhere(query, "CategoryBinding", "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.OrderBy(query, new[] { "Category_Name" }, new[] { Ordering.Asc });

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
			parameters.Add(new Parameter(ParameterType.String, "Page", pageName));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				List<CategoryInfo> result = new List<CategoryInfo>(20);
				List<string> pages = new List<string>(50);

				string prevName = "|||";
				string name = null;

				while(reader.Read()) {
					name = reader["Category_Name"] as string;

					if(name != prevName) {
						if(prevName != "|||") {
							result[result.Count - 1].Pages = pages.ToArray();
							pages.Clear();
						}

						result.Add(new CategoryInfo(NameTools.GetFullName((reader["Category_Namespace"] as string).Trim(), name), this));
					}

					prevName = name;
					if(!IsDBNull(reader, "CategoryBinding_Page")) {
						pages.Add(NameTools.GetFullName((reader["Category_Namespace"] as string).Trim(), reader["CategoryBinding_Page"] as string));
					}
				}

				CloseReader(command, reader);

				if(result.Count > 0) result[result.Count - 1].Pages = pages.ToArray();

				return result.ToArray();
			}
			else return null;
		}
		/// <summary>
		/// Renames a Category.
		/// </summary>
		/// <param name="category">The Category to rename.</param>
		/// <param name="newName">The new Name.</param>
		/// <returns>The correct CategoryInfo object.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="category"/> or <paramref name="newName"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="newName"/> is empty.</exception>
		public CategoryInfo RenameCategory(CategoryInfo category, string newName) {
			if(category == null) throw new ArgumentNullException("category");
			if(newName == null) throw new ArgumentNullException("newName");
			if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName");

			string nspace = null;
			string name = null;
			NameTools.ExpandFullName(category.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("Category", new string[] { "Name" }, new string[] { "NewName" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "OldName");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");

			List<Parameter> parameters = new List<Parameter>(3);
			parameters.Add(new Parameter(ParameterType.String, "NewName", newName));
			parameters.Add(new Parameter(ParameterType.String, "OldName", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows > 0) {
				CategoryInfo result = GetCategory(transaction, NameTools.GetFullName(nspace, newName));
				CommitTransaction(transaction);
				return result;
			}
			else {
				RollbackTransaction(transaction);
				return null;
			}
		}
		/// <summary>
		/// Gets a category.
		/// </summary>
		/// <param name="connection">A database connection.</param>
		/// <param name="fullName">The full name of the category.</param>
		/// <returns>The <see cref="T:CategoryInfo" />, or <c>null</c> if no category is found.</returns>
		private CategoryInfo GetCategory(DbConnection connection, string fullName) {
			string nspace = null;
			string name = null;
			NameTools.ExpandFullName(fullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectFrom("Category", "CategoryBinding", new string[] { "Name", "Namespace" }, new string[] { "Category", "Namespace" }, Join.LeftJoin,
				new string[] { "Name", "Namespace" }, new string[] { "Page" });
			query = queryBuilder.Where(query, "Category", "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.AndWhere(query, "Category", "Name", WhereOperator.Equals, "Name");

			List<Parameter> parameters = new List<Parameter>(3);
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
			parameters.Add(new Parameter(ParameterType.String, "Name", name));

			DbCommand command = builder.GetCommand(connection, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				CategoryInfo result = null;
				List<string> pages = new List<string>(50);

				while(reader.Read()) {
					if(result == null) result = new CategoryInfo(NameTools.GetFullName(((reader["Category_Namespace"] as string).Trim()).Trim(), reader["Category_Name"] as string), this);

					if(!IsDBNull(reader, "CategoryBinding_Page")) {
						pages.Add(NameTools.GetFullName((reader["Category_Namespace"] as string).Trim(), reader["CategoryBinding_Page"] as string));
					}
				}

				CloseReader(reader);

				if(result != null) result.Pages = pages.ToArray();

				return result;
			}
			else return null;
		}
		/// <summary>
		/// Moves a page from its namespace into another.
		/// </summary>
		/// <param name="page">The page to move.</param>
		/// <param name="destination">The destination namespace (<c>null</c> for the root).</param>
		/// <param name="copyCategories">A value indicating whether to copy the page categories in the destination
		/// namespace, if not already available.</param>
		/// <returns>The correct instance of <see cref="T:PageInfo"/>.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/> is <c>null</c>.</exception>
		public PageInfo MovePage(PageInfo page, NamespaceInfo destination, bool copyCategories) {
			if(page == null) throw new ArgumentNullException("page");

			// Check:
			// 1. Same namespace - ROOT, SUB (explicit check)
			// 2. Destination existence (update query affects 0 rows because it would break a FK)
			// 3. Page existence in target (update query affects 0 rows because it would break a FK)
			// 4. Page is default page of its namespace (explicit check)

			string destinationName = destination != null ? destination.Name : " ";
			string sourceName = null;
			string pageName = null;
			NameTools.ExpandFullName(page.FullName, out sourceName, out pageName);
			if(sourceName == null) sourceName = " ";

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			if(destinationName.ToLowerInvariant() == sourceName.ToLowerInvariant()) return null;
			if(IsDefaultPage(transaction, page)) {
				RollbackTransaction(transaction);
				return null;
			}

			PageContent currentContent = GetContent(transaction, page, CurrentRevision);
			if(currentContent != null) {
				UnindexPage(currentContent, transaction);
				foreach(Message msg in GetMessages(transaction, page)) {
					UnindexMessageTree(page, msg, transaction);
				}
			}

			CategoryInfo[] currCategories = GetCategories(transaction, (sourceName == " "||sourceName=="") ? null : GetNamespace(transaction, sourceName));

			// Remove bindings
			RebindPage(transaction, page, new string[0]);

			string[] newCategories = new string[0];

			if(copyCategories) {
				// Retrieve categories for page
				// Copy missing ones in destination

				string lowerPageName = page.FullName.ToLowerInvariant();

				List<string> pageCategories = new List<string>(10);
				foreach(CategoryInfo cat in currCategories) {
					if(Array.Find(cat.Pages, (s) => { return s.ToLowerInvariant() == lowerPageName; }) != null) {
						pageCategories.Add(NameTools.GetLocalName(cat.FullName));
					}
				}

				// Create categories into destination without checking existence (AddCategory will return null)
				string tempName = destinationName == " " ? null : destinationName;
				newCategories = new string[pageCategories.Count];

				for(int i = 0; i < pageCategories.Count; i++) {
					string catName = NameTools.GetFullName(tempName, pageCategories[i]);
					if(GetCategory(transaction, catName) == null) {
						CategoryInfo added = AddCategory(tempName, pageCategories[i]);
					}
					newCategories[i] = catName;
				}
			}

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("Page", new string[] { "Namespace" }, new string[] { "Destination" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Source");

			List<Parameter> parameters = new List<Parameter>(3);
			parameters.Add(new Parameter(ParameterType.String, "Destination", destinationName));
			parameters.Add(new Parameter(ParameterType.String, "Name", pageName));
			parameters.Add(new Parameter(ParameterType.String, "Source", sourceName));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows > 0) {
				PageInfo result = new PageInfo(NameTools.GetFullName(destinationName, pageName), this, page.CreationDateTime);

				// Re-bind categories
				if(copyCategories) {
					bool rebound = RebindPage(transaction, result, newCategories);
					if(!rebound) {
						RollbackTransaction(transaction);
						return null;
					}
				}

				PageContent newContent = GetContent(transaction, result, CurrentRevision);
				IndexPage(newContent, transaction);
				foreach(Message msg in GetMessages(transaction, result)) {
					IndexMessageTree(result, msg, transaction);
				}

				CommitTransaction(transaction);

				return result;
			}
			else {
				RollbackTransaction(transaction);
				return null;
			}
		}
		/// <summary>
		/// Determines whether a page attachment exists.
		/// </summary>
		/// <param name="transaction">A database transaction.</param>
		/// <param name="page">The page.</param>
		/// <param name="name">The attachment.</param>
		/// <returns><c>true</c> if the attachment exists, <c>false</c> otherwise.</returns>
		private bool AttachmentExists(DbTransaction transaction, PageInfo page, string name) {
			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectCountFrom("Attachment");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", name));
			parameters.Add(new Parameter(ParameterType.String, "Page", page.FullName));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int count = ExecuteScalar<int>(command, -1, false);

			return count == 1;
		}
		/// <summary>
		/// Removes a Page.
		/// </summary>
		/// <param name="page">The Page to remove.</param>
		/// <returns>True if the Page is removed successfully.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/> is <c>null</c>.</exception>
		public bool RemovePage(PageInfo page) {
			if(page == null) throw new ArgumentNullException("page");

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			if(IsDefaultPage(transaction, page)) {
				RollbackTransaction(transaction);
				return false;
			}

			PageContent currentContent = GetContent(transaction, page, CurrentRevision);
			if(currentContent != null) {
				UnindexPage(currentContent, transaction);
				foreach(Message msg in GetMessages(transaction, page)) {
					UnindexMessageTree(page, msg, transaction);
				}
			}

			RebindPage(transaction, page, new string[0]);

			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("Page");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows > 0) CommitTransaction(transaction);
			else RollbackTransaction(transaction);

			return rows > 0;
		}
		/// <summary>
		/// Gets a page.
		/// </summary>
		/// <param name="connection">A database connection.</param>
		/// <param name="fullName">The full name of the page.</param>
		/// <returns>The <see cref="T:PageInfo" />, or <c>null</c> if no page is found.</returns>
		private PageInfo GetPage(DbConnection connection, string fullName) {
			string nspace, name;
			NameTools.ExpandFullName(fullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectFrom("Page");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));

			DbCommand command = builder.GetCommand(connection, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				PageInfo result = null;

				if(reader.Read()) {
					result = new PageInfo(NameTools.GetFullName((reader["Namespace"] as string).Trim(), reader["Name"] as string),
						this, (DateTime)reader["CreationDateTime"]);
				}

				CloseReader(reader);

				return result;
			}
			else return null;
		}
		/// <summary>
		/// Binds a Page with one or more Categories.
		/// </summary>
		/// <param name="connection">A database connection.</param>
		/// <param name="page">The Page to bind.</param>
		/// <param name="categories">The Categories to bind the Page with.</param>
		/// <returns>True if the binding succeeded.</returns>
		/// <remarks>After a successful operation, the Page is bound with all and only the categories passed as argument.</remarks>
		private bool RebindPage(DbConnection connection, PageInfo page, string[] categories) {
			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("CategoryBinding");
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));

			DbCommand command = builder.GetCommand(connection, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows < 0) return false;

			if(categories.Length > 0) {
				string finalQuery = "";
				parameters = new List<Parameter>(categories.Length * 3);
				int count = 0;
				string countString;

				foreach(string cat in categories) {
					countString = count.ToString();

					query = queryBuilder.InsertInto("CategoryBinding", new string[] { "Namespace", "Category", "Page" },
						new string[] { "Namespace" + countString, "Category" + countString, "Page" + countString });
					finalQuery = queryBuilder.AppendForBatch(finalQuery, query);

					parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace));
					parameters.Add(new Parameter(ParameterType.String, "Category" + countString, NameTools.GetLocalName(cat)));
					parameters.Add(new Parameter(ParameterType.String, "Page" + countString, name));

					count++;
				}
                finalQuery = queryBuilder.BuildBatchCommand(finalQuery);
				command = builder.GetCommand(connection, finalQuery, parameters);

                rows = ExecuteNonQuery(command, false, true, count);

				return rows == categories.Length;
			}
			else return true;
		}
		/// <summary>
		/// Gets all the pages in a namespace that are bound to zero categories.
		/// </summary>
		/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
		/// <returns>The pages, sorted by name.</returns>
		public PageInfo[] GetUncategorizedPages(NamespaceInfo nspace) {
			string nspaceName = nspace != null&&!string.IsNullOrEmpty(nspace.Name) ? nspace.Name : " ";

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectFrom("Page", "CategoryBinding", "Name", "Page", Join.LeftJoin);
			query = queryBuilder.Where(query, "CategoryBinding", "Category", WhereOperator.IsNull, null);
			query = queryBuilder.AndWhere(query, "Page", "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.OrderBy(query, new[] { "Name" }, new[] { Ordering.Asc });

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspaceName));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				List<PageInfo> result = new List<PageInfo>(100);

				while(reader.Read()) {
					result.Add(new PageInfo(NameTools.GetFullName((reader["Namespace"] as string).Trim(), reader["Name"] as string),
						this, (DateTime)reader["CreationDateTime"]));
				}

				CloseReader(command, reader);

				return result.ToArray();
			}
			else return null;
		}
		/// <summary>
		/// Gets the total number of Messages in a Page Discussion.
		/// </summary>
		/// <param name="page">The Page.</param>
		/// <returns>The number of messages.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/> is <c>null</c>.</exception>
		public int GetMessageCount(PageInfo page) {
			if(page == null) throw new ArgumentNullException("page");

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);

			if(GetPage(connection, page.FullName) == null) {
				CloseConnection(connection);
				return -1;
			}

			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectCountFrom("Message");
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));

			DbCommand command = builder.GetCommand(connection, query, parameters);

			int count = ExecuteScalar<int>(command, 0);

			return count;
		}
		/// <summary>
		/// Gets the content of a specific revision of a page.
		/// </summary>
		/// <param name="transaction">A database transaction.</param>
		/// <param name="page">The page.</param>
		/// <param name="revision">The revision.</param>
		/// <returns>The content.</returns>
		private PageContent GetContent(DbTransaction transaction, PageInfo page, int revision) {
			// Internal version to work with GetContent, GetBackupContent, GetDraft

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string name, nspace;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			string query = queryBuilder.SelectFrom("PageContent", "PageKeyword", new string[] { "Page", "Namespace", "Revision" }, new string[] { "Page", "Namespace", "Revision" }, Join.LeftJoin,
				new string[] { "Title", "User", "LastModified", "Comment", "Content", "Description" }, new string[] { "Keyword" });
			query = queryBuilder.Where(query, "PageContent", "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "PageContent", "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.AndWhere(query, "PageContent", "Revision", WhereOperator.Equals, "Revision");

			List<Parameter> parameters = new List<Parameter>(3);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
			parameters.Add(new Parameter(ParameterType.Int16, "Revision", (short)revision));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				PageContent result = null;

				string title = null, user = null, comment = null, content = null, description = null;
				DateTime dateTime = DateTime.MinValue;
				List<string> keywords = new List<string>(10);

				while(reader.Read()) {
					if(title == null) {
						title = reader["PageContent_Title"] as string;
						user = reader["PageContent_User"] as string;
						dateTime = (DateTime)reader["PageContent_LastModified"];
						comment = GetNullableColumn<string>(reader, "PageContent_Comment", " ");
						content = reader["PageContent_Content"] as string;
						description = GetNullableColumn<string>(reader, "PageContent_Description", null);
					}

					if(!IsDBNull(reader, "PageKeyword_Keyword")) {
						keywords.Add(reader["PageKeyword_Keyword"] as string);
					}
				}

				if(title != null) {
					result = new PageContent(page, title, user, dateTime, comment, content, keywords.ToArray(), description);
				}

				CloseReader(reader);

				return result;
			}
			else return null;
		}
		/// <summary>
		/// Adds a new Message to a Page.
		/// </summary>
		/// <param name="page">The Page.</param>
		/// <param name="username">The Username.</param>
		/// <param name="subject">The Subject.</param>
		/// <param name="dateTime">The Date/Time.</param>
		/// <param name="body">The Body.</param>
		/// <param name="parent">The Parent Message ID, or -1.</param>
		/// <returns>True if the Message is added successfully.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/>, <paramref name="username"/>, <paramref name="subject"/> or <paramref name="body"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="username"/> or <paramref name="subject"/> are empty.</exception>
		/// <exception cref="ArgumentOutOfRangeException">If <paramref name="parent"/> is less than -1.</exception>
		public bool AddMessage(PageInfo page, string username, string subject, DateTime dateTime, string body, int parent) {
			if(page == null) throw new ArgumentNullException("page");
			if(username == null) throw new ArgumentNullException("username");
			if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username");
			if(subject == null) throw new ArgumentNullException("subject");
			if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject");
			if(body == null) throw new ArgumentNullException("body"); // body can be empty
			if(parent < -1) throw new ArgumentOutOfRangeException("parent", "Invalid Parent Message ID");

			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			if(parent != -1 && FindMessage(GetMessages(transaction, page), parent) == null) {
				RollbackTransaction(transaction);
				return false;
			}

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			short freeId = -1;

			string query = queryBuilder.SelectFrom("Message", new string[] { "Id" });
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.OrderBy(query, new string[] { "Id" }, new Ordering[] { Ordering.Desc });

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			freeId = ExecuteScalar<short>(command, -1, false);

			if(freeId == -1) freeId = 0;
			else freeId++;

			query = queryBuilder.InsertInto("Message", new string[] { "Page", "Namespace", "Id", "Parent", "Username", "Subject", "DateTime", "Body" },
				new string[] { "Page", "Namespace", "Id", "Parent", "Username", "Subject", "DateTime", "Body" });

			parameters = new List<Parameter>(8);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
			parameters.Add(new Parameter(ParameterType.Int16, "Id", freeId));
			if(parent != -1) parameters.Add(new Parameter(ParameterType.Int16, "Parent", parent));
			else parameters.Add(new Parameter(ParameterType.Int16, "Parent", DBNull.Value));
			parameters.Add(new Parameter(ParameterType.String, "Username", username));
			parameters.Add(new Parameter(ParameterType.String, "Subject", subject));
			parameters.Add(new Parameter(ParameterType.DateTime, "DateTime", dateTime));
			parameters.Add(new Parameter(ParameterType.String, "Body", body));

			command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == 1) {
				IndexMessage(page, freeId, subject, dateTime, body, transaction);

				CommitTransaction(transaction);
				return true;
			}
			else {
				RollbackTransaction(transaction);
				return false;
			}
		}
		/// <summary>
		/// Gets the Backup/Revision numbers of a Page.
		/// </summary>
		/// <param name="connection">A database connection.</param>
		/// <param name="page">The Page to get the Backups of.</param>
		/// <returns>The Backup/Revision numbers.</returns>
		private int[] GetBackups(DbConnection connection, PageInfo page) {
			if(GetPage(connection, page.FullName) == null) {
				return null;
			}

			string name, nspace;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectFrom("PageContent", new string[] { "Revision" });
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.AndWhere(query, "Revision", WhereOperator.GreaterThanOrEqualTo, "Revision");
			query = queryBuilder.OrderBy(query, new[] { "Revision" }, new[] { Ordering.Asc });

			List<Parameter> parameters = new List<Parameter>(3);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
			parameters.Add(new Parameter(ParameterType.Int16, "Revision", FirstRevision));

			DbCommand command = builder.GetCommand(connection, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				List<int> result = new List<int>(100);

				while(reader.Read()) {
					result.Add((short)Convert.ChangeType(reader["Revision"],typeof(short)));
				}

				CloseReader(reader);

				return result.ToArray();
			}
			else return null;
		}
		/// <summary>
		/// Modifies a Message.
		/// </summary>
		/// <param name="page">The Page.</param>
		/// <param name="id">The ID of the Message to modify.</param>
		/// <param name="username">The Username.</param>
		/// <param name="subject">The Subject.</param>
		/// <param name="dateTime">The Date/Time.</param>
		/// <param name="body">The Body.</param>
		/// <returns>True if the Message is modified successfully.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/>, <paramref name="username"/>, <paramref name="subject"/> or <paramref name="body"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentOutOfRangeException">If <paramref name="id"/> is less than zero.</exception>
		/// <exception cref="ArgumentException">If <paramref name="username"/> or <paramref name="subject"/> are empty.</exception>
		public bool ModifyMessage(PageInfo page, int id, string username, string subject, DateTime dateTime, string body) {
			if(page == null) throw new ArgumentNullException("page");
			if(id < 0) throw new ArgumentOutOfRangeException("id", "Invalid Message ID");
			if(username == null) throw new ArgumentNullException("username");
			if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username");
			if(subject == null) throw new ArgumentNullException("subject");
			if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject");
			if(body == null) throw new ArgumentNullException("body"); // body can be empty

			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			Message[] messages = GetMessages(transaction, page);
			if(messages == null) {
				RollbackTransaction(transaction);
				return false;
			}
			Message oldMessage = FindMessage(messages, id);

			if(oldMessage == null) {
				RollbackTransaction(transaction);
				return false;
			}

			UnindexMessage(page, oldMessage.ID, oldMessage.Subject, oldMessage.DateTime, oldMessage.Body, transaction);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("Message", new string[] { "Username", "Subject", "DateTime", "Body" }, new string[] { "Username", "Subject", "DateTime", "Body" });
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.AndWhere(query, "Id", WhereOperator.Equals, "Id");

			List<Parameter> parameters = new List<Parameter>(7);
			parameters.Add(new Parameter(ParameterType.String, "Username", username));
			parameters.Add(new Parameter(ParameterType.String, "Subject", subject));
			parameters.Add(new Parameter(ParameterType.DateTime, "DateTime", dateTime));
			parameters.Add(new Parameter(ParameterType.String, "Body", body));
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
			parameters.Add(new Parameter(ParameterType.Int16, "Id", id));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == 1) {
				IndexMessage(page, id, subject, dateTime, body, transaction);
				CommitTransaction(transaction);
				return true;
			}
			else {
				RollbackTransaction(transaction);
				return false;
			}
		}
		/// <summary>
		/// Deletes a revision of a page content.
		/// </summary>
		/// <param name="connection">A database connection.</param>
		/// <param name="page">The page.</param>
		/// <param name="revision">The revision.</param>
		/// <returns><c>true</c> if the content ir deleted, <c>false</c> otherwise.</returns>
		private bool DeleteContent(DbConnection connection, PageInfo page, int revision) {
			string name, nspace;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("PageContent");
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.AndWhere(query, "Revision", WhereOperator.Equals, "Revision");

			List<Parameter> parameters = new List<Parameter>(3);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
			parameters.Add(new Parameter(ParameterType.String, "Revision", revision));

			DbCommand command = builder.GetCommand(connection, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			return rows > 0;
		}
		/// <summary>
		/// Gets a namespace.
		/// </summary>
		/// <param name="connection">A database connection.</param>
		/// <param name="name">The name of the namespace (cannot be <c>null</c> or empty).</param>
		/// <returns>The <see cref="T:NamespaceInfo" />, or <c>null</c> if no namespace is found.</returns>
		private NamespaceInfo GetNamespace(DbConnection connection, string name) {
			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			// select ... from Namespace left join Page on Namespace.DefaultPage = Page.Name where Namespace.Name = <name> and (Namespace.DefaultPage is null or Page.Namespace = <name>)
			string query = queryBuilder.SelectFrom("Namespace", "Page", "DefaultPage", "Name", Join.LeftJoin, new string[] { "Name", "DefaultPage" }, new string[] { "CreationDateTime" });
			query = queryBuilder.Where(query, "Namespace", "Name", WhereOperator.Equals, "Name1");
			query = queryBuilder.AndWhere(query, "Namespace", "DefaultPage", WhereOperator.IsNull, null, true, false);
			query = queryBuilder.OrWhere(query, "Page", "Namespace", WhereOperator.Equals, "Name2", false, true);

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name1", name));
			parameters.Add(new Parameter(ParameterType.String, "Name2", name));

			DbCommand command = builder.GetCommand(connection, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				NamespaceInfo result = null;

				if(reader.Read()) {
					string realName = reader["Namespace_Name"] as string;
					string page = GetNullableColumn<string>(reader, "Namespace_DefaultPage", null);
					PageInfo defaultPage = string.IsNullOrEmpty(page) ? null :
						new PageInfo(NameTools.GetFullName(realName, page), this, (DateTime)reader["Page_CreationDateTime"]);

					result = new NamespaceInfo(realName, this, defaultPage);
				}

				CloseReader(reader);

				return result;
			}
			else return null;
		}
		/// <summary>
		/// Renames a Page.
		/// </summary>
		/// <param name="page">The Page to rename.</param>
		/// <param name="newName">The new Name.</param>
		/// <returns>The correct <see cref="T:PageInfo"/> object.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/> or <paramref name="newName"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="newName"/> is empty.</exception>
		public PageInfo RenamePage(PageInfo page, string newName) {
			if(page == null) throw new ArgumentNullException("page");
			if(newName == null) throw new ArgumentNullException("newName");
			if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName");

			// Check
			// 1. Page is default page of its namespace
			// 2. New name already exists

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			if(GetPage(transaction, page.FullName) == null) {
				RollbackTransaction(transaction);
				return null;
			}
			if(IsDefaultPage(transaction, page)) {
				RollbackTransaction(transaction);
				return null;
			}
			if(GetPage(transaction, NameTools.GetFullName(NameTools.GetNamespace(page.FullName), NameTools.GetLocalName(newName))) != null) {
				RollbackTransaction(transaction);
				return null;
			}

			PageContent currentContent = GetContent(transaction, page, CurrentRevision);
			UnindexPage(currentContent, transaction);
			foreach(Message msg in GetMessages(transaction, page)) {
				UnindexMessageTree(page, msg, transaction);
			}

			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			CategoryInfo[] currCategories = GetCategories(transaction, nspace == " " ? null : GetNamespace(transaction, nspace));
			string lowerPageName = page.FullName.ToLowerInvariant();
			List<string> pageCategories = new List<string>(10);
			foreach(CategoryInfo cat in currCategories) {
				if(Array.Find(cat.Pages, (s) => { return s.ToLowerInvariant() == lowerPageName; }) != null) {
					pageCategories.Add(NameTools.GetLocalName(cat.FullName));
				}
			}

			RebindPage(transaction, page, new string[0]);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("Page", new string[] { "Name" }, new string[] { "NewName" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "OldName");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");

			List<Parameter> parameters = new List<Parameter>(3);
			parameters.Add(new Parameter(ParameterType.String, "NewName", newName));
			parameters.Add(new Parameter(ParameterType.String, "OldName", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows > 0) {
				PageInfo result = new PageInfo(NameTools.GetFullName(nspace, newName), this, page.CreationDateTime);

				RebindPage(transaction, result, pageCategories.ToArray());

				PageContent newContent = GetContent(transaction, result, CurrentRevision);

				IndexPage(newContent, transaction);
				foreach(Message msg in GetMessages(transaction, result)) {
					IndexMessageTree(result, msg, transaction);
				}

				CommitTransaction(transaction);

				return result;
			}
			else {
				RollbackTransaction(transaction);
				return null;
			}
		}
		/// <summary>
		/// Deletes some ACL entries.
		/// </summary>
		/// <param name="entries">The entries to delete.</param>
		/// <returns><c>true</c> if one or more entries were deleted, <c>false</c> otherwise.</returns>
		private bool DeleteEntries(AclEntry[] entries) {
			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			foreach(AclEntry entry in entries) {
				string query = queryBuilder.DeleteFrom("AclEntry");
				query = queryBuilder.Where(query, "Resource", WhereOperator.Equals, "Resource");
				query = queryBuilder.AndWhere(query, "Action", WhereOperator.Equals, "Action");
				query = queryBuilder.AndWhere(query, "Subject", WhereOperator.Equals, "Subject");

				List<Parameter> parameters = new List<Parameter>(3);
				parameters.Add(new Parameter(ParameterType.String, "Resource", entry.Resource));
				parameters.Add(new Parameter(ParameterType.String, "Action", entry.Action));
				parameters.Add(new Parameter(ParameterType.String, "Subject", entry.Subject));

				DbCommand command = builder.GetCommand(transaction, query, parameters);

				if(ExecuteNonQuery(command, false) <= 0) {
					RollbackTransaction(transaction);
					return false;
				}
			}

			CommitTransaction(transaction);

			return true;
		}
		/// <summary>
		/// Deletes the Backups of a Page, up to a specified revision.
		/// </summary>
		/// <param name="page">The Page to delete the backups of.</param>
		/// <param name="revision">The newest revision to delete (newer revision are kept) o -1 to delete all the Backups.</param>
		/// <returns><c>true</c> if the deletion succeeded, <c>false</c> otherwise.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/> is <c>null</c>.</exception>
		/// <exception cref="ArgumentOutOfRangeException">If <paramref name="revision"/> is less than -1.</exception>
		public bool DeleteBackups(PageInfo page, int revision) {
			if(page == null) throw new ArgumentNullException("page");
			if(revision < -1) throw new ArgumentOutOfRangeException("revision", "Invalid Revision");

			// 1. Retrieve target content (revision-1 = first kept revision)
			// 2. Replace the current content (delete, store)
			// 3. Delete all older revisions up to the specified on (included) "N-m...N"
			// 4. Re-number remaining revisions starting from FirstRevision (zero) to revision-1 (don't re-number revs -1, -100)

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			if(GetPage(transaction, page.FullName) == null) {
				RollbackTransaction(transaction);
				return false;
			}

			int[] baks = GetBackups(transaction, page);
			if(baks.Length > 0 && revision > baks[baks.Length - 1]) {
				RollbackTransaction(transaction);
				return true;
			}

			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("PageContent");
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
			if(revision != -1) query = queryBuilder.AndWhere(query, "Revision", WhereOperator.LessThanOrEqualTo, "Revision");
			query = queryBuilder.AndWhere(query, "Revision", WhereOperator.GreaterThanOrEqualTo, "FirstRevision");

			List<Parameter> parameters = new List<Parameter>(4);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
			if(revision != -1) parameters.Add(new Parameter(ParameterType.Int16, "Revision", revision));
			parameters.Add(new Parameter(ParameterType.Int16, "FirstRevision", FirstRevision));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == -1) {
				RollbackTransaction(transaction);
				return false;
			}

			if(revision != -1) {
				int revisionDelta = revision + 1;

				query = queryBuilder.UpdateIncrement("PageContent", "Revision", -revisionDelta);
				query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
				query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
				query = queryBuilder.AndWhere(query, "Revision", WhereOperator.GreaterThanOrEqualTo, "FirstRevision");

				parameters = new List<Parameter>(3);
				parameters.Add(new Parameter(ParameterType.String, "Page", name));
				parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
				parameters.Add(new Parameter(ParameterType.Int16, "FirstRevision", FirstRevision));

				command = builder.GetCommand(transaction, query, parameters);

				rows = ExecuteNonQuery(command, false);

				if(rows > 0) CommitTransaction(transaction);
				else RollbackTransaction(transaction);

				return rows >= 0;
			}
			else {
				CommitTransaction(transaction);
				return true;
			}
		}
		/// <summary>
		/// Sets a meta-data items' content.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <param name="tag">The tag that specifies the context (usually the namespace).</param>
		/// <param name="content">The content.</param>
		/// <returns><c>true</c> if the content is set, <c>false</c> otherwise.</returns>
		public bool SetMetaDataItem(MetaDataItem item, string tag, string content) {
			if(string.IsNullOrEmpty(tag)) tag = " ";
			if(string.IsNullOrEmpty(content)) content = " ";

			// 1. Delete old value, if any
			// 2. Store new value

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("MetaDataItem");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Tag", WhereOperator.Equals, "Tag");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", item.ToString()));
			parameters.Add(new Parameter(ParameterType.String, "Tag", tag));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == -1) {
				RollbackTransaction(transaction);
				return false;
			}

			query = queryBuilder.InsertInto("MetaDataItem", new string[] { "Name", "Tag", "Data" }, new string[] { "Name", "Tag", "Content" });

			parameters = new List<Parameter>(3);
			parameters.Add(new Parameter(ParameterType.String, "Name", item.ToString()));
			parameters.Add(new Parameter(ParameterType.String, "Tag", tag));
			parameters.Add(new Parameter(ParameterType.String, "Content", content));

			command = builder.GetCommand(transaction, query, parameters);

			rows = ExecuteNonQuery(command, false);

			if(rows == 1) CommitTransaction(transaction);
			else RollbackTransaction(transaction);

			return rows == 1;
		}
		/// <summary>
		/// Deletes a File.
		/// </summary>
		/// <param name="fullName">The full name of the File.</param>
		/// <returns><c>true</c> if the File is deleted, <c>false</c> otherwise.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="fullName"/> is <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="fullName"/> is empty or it does not exist.</exception>
		public bool DeleteFile(string fullName) {
			if(fullName == null) throw new ArgumentNullException("fullName");
			if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName");

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			if(!FileExists(transaction, fullName)) {
				RollbackTransaction(transaction);
				throw new ArgumentException("File does not exist", "fullName");
			}

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string directory, filename;
			SplitFileFullName(fullName, out directory, out filename);

			string query = queryBuilder.DeleteFrom("File");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", filename));
			parameters.Add(new Parameter(ParameterType.String, "Directory", directory));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);
			if(rows == 1) CommitTransaction(transaction);
			else RollbackTransaction(transaction);

			return rows == 1;
		}