Пошаговое руководство. Распространение рефакторинга с переименованием для баз данных на текстовые файлы
В этом разделе с пошаговыми инструкциями описаны процессы создания, установки, регистрации и тестирования нового участника рефакторинга с переименованием. Этот целевой объект рефакторинга расширяет возможности Visual Studio Premium или Visual Studio Ultimate, позволяя использовать рефакторинг базы данных для переименования ссылок на объекты базы данных, которые содержатся в текстовых файлах проекта базы данных.
Если в существующий тип рефакторинга добавляется новый участник рефакторинга, он должен использовать входной класс существующего участника.
В данном пошаговом руководстве рассмотрены следующие задачи:
Создание новой сборки, содержащей классы для пользовательского целевого объекта рефакторинга.
Установка и регистрация сборки, призванная сделать целевой объект рефакторинга доступным в Visual Studio Premium или Visual Studio Ultimate.
Создание простого проекта базы данных для проверки правильности работы целевого объекта рефакторинга.
Обязательные компоненты
Ниже приведены компоненты, необходимые для выполнения данного пошагового руководства.
Необходимо установить Visual Studio 2010 Premium или Visual Studio 2010 Ultimate.
На компьютере также должен быть установлен пакет SDK для Visual Studio 2010. Его можно загрузить со страницы веб-сайта корпорации Майкрософт Visual Studio 2010 SDK.
Создание сборки с пользовательским целевым объектом рефакторинга
Чтобы создать пользовательский целевой объект рефакторинга, позволяющий применять операции рефакторинга с переименованием к текстовым файлам, необходимо реализовать класс, определяющий новый RefactoringContributor:
- RenameReferenceTextContributorContributor — этот класс строит список предложений по изменению для переименованного символа. Предложения по изменению предоставляются для каждой ссылки, содержащейся в текстовом файле проекта базы данных.
Перед созданием этого класса необходимо сначала создать библиотеку классов, затем добавить необходимые ссылки, а также вспомогательный код, упрощающий разработку кода, который будет написан далее в этом пошаговом руководстве.
Создание библиотеки классов и вспомогательного кода
Создайте новый проект библиотеки классов C# с именем RenameTextContributor.csproj.
Добавьте ссылки на следующие сборки .NET.
Microsoft.Data.Schema;
Microsoft.Data.Schema.ScriptDom;
Microsoft.Data.Schema.ScriptDom.sql;
Microsoft.Data.Schema.Sql;
Добавьте ссылки на следующие сборки в папке %Program Files%\Microsoft Visual Studio 10.0\VSTSDB:
Microsoft.Data.Schema.Tools.Sql.dll;
Microsoft.VisualStudio.Data.Schema.Package.dll;
Microsoft.VisualStudio.Data.Schema.Package.Sql.dll.
Добавьте ссылки на следующие сборки из пакета средства разработки программного обеспечения (SDK) Visual Studio 2010:
Microsoft.VisualStudio.OLE.Interop.dll;
Microsoft.VisualStudio.Shell.10.0.dll;
Microsoft.VisualStudio.TextManager.Interop.dll.
Microsoft.VisualStudio.Shell.Interop.dll;
Microsoft.VisualStudio.Shell.Interop.8.0.dll;
Microsoft.VisualStudio.Shell.Interop.9.0.dll;
Microsoft.VisualStudio.Shell.Interop.10.0.dll;
В обозревателе решений переименуйте файл Class1.cs в SampleHelper.cs.
Дважды щелкните файл SampleHelper.cs, чтобы открыть его в редакторе кода.
Примечание
Этот вспомогательный класс идентичен тому вспомогательному тексту, который использовался в пошаговом руководстве для пользовательских типов рефакторинга.В целях экономии времени можно скопировать исходный код из того проекта в новый проект.
Замените код в редакторе кода следующим кодом:
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using Microsoft.Data.Schema.SchemaModel; using Microsoft.Data.Schema.ScriptDom.Sql; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Data.Schema.Package.Refactoring; using Microsoft.VisualStudio.Data.Schema.Package.UI; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TextManager.Interop; namespace MySamples.Refactoring { internal static class SampleHelper { public static String GetModelElementName(IModelElement modelElement) { SampleHelper.CheckNullArgument(modelElement, "modelElement"); return modelElement.ToString(); } /// <summary> /// Given a model element, returns its simple name. /// </summary> public static String GetModelElementSimpleName(IModelElement modelElement) { String separator = "."; String simpleName = String.Empty; String fullName = modelElement.ToString(); String[] nameParts = fullName.Split(separator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); if (nameParts.Length > 0) { simpleName = nameParts[nameParts.Length - 1]; // last part } if (simpleName.StartsWith("[") && simpleName.EndsWith("]")) { simpleName = simpleName.Substring(1, simpleName.Length - 2); } return simpleName; } /// <summary> /// Find all files in the project with the specified file extension /// </summary> public static List<string> GetAllFilesInProject(IVsHierarchy solutionNode, string fileExtension, bool visibleNodesOnly) { List<string> files = new List<string>(); if (null != solutionNode) { EnumProjectItems(solutionNode, fileExtension, files, VSConstants.VSITEMID_ROOT, // item id of solution root node 0, // recursion from solution node true, // hierarchy is Solution node visibleNodesOnly); // visibleNodesOnly } return files; } /// <summary> /// Enumerates recursively over the hierarchy items. /// </summary> /// <param name="hierarchy">hierarchy to enmerate over.</param> /// <param name="fileExtension">type of files we need to collect from the project</param> /// <param name="files">list of file paths</param> /// <param name="itemid">item id of the hierarchy</param> /// <param name="recursionLevel">Depth of recursion. For example, if recursion started with the Solution /// node, then : Level 0 -- Solution node, Level 1 -- children of Solution, etc.</param> /// <param name="hierIsSolution">true if hierarchy is Solution Node. </param> /// <param name="visibleNodesOnly">true if only nodes visible in the Solution Explorer should /// be traversed. false if all project items should be traversed.</param> private static void EnumProjectItems(IVsHierarchy hierarchy, string fileExtension, List<string> files, uint itemid, int recursionLevel, bool hierIsSolution, bool visibleNodesOnly) { int hr; IntPtr nestedHierarchyObj; uint nestedItemId; Guid hierGuid = typeof(IVsHierarchy).GUID; // Check first if this node has a nested hierarchy. hr = hierarchy.GetNestedHierarchy(itemid, ref hierGuid, out nestedHierarchyObj, out nestedItemId); if (VSConstants.S_OK == hr && IntPtr.Zero != nestedHierarchyObj) { IVsHierarchy nestedHierarchy = Marshal.GetObjectForIUnknown(nestedHierarchyObj) as IVsHierarchy; Marshal.Release(nestedHierarchyObj); if (nestedHierarchy != null) { EnumProjectItems(nestedHierarchy, fileExtension, files, nestedItemId, recursionLevel, false, visibleNodesOnly); } } else { // Check if the file extension of this node matches string fileFullPath; hierarchy.GetCanonicalName(itemid, out fileFullPath); if (CompareExtension(fileFullPath, fileExtension)) { // add matched file paths into the list files.Add(fileFullPath); } recursionLevel++; //Get the first child node of the current hierarchy being walked object pVar; hr = hierarchy.GetProperty(itemid, ((visibleNodesOnly || (hierIsSolution && recursionLevel == 1) ? (int)__VSHPROPID.VSHPROPID_FirstVisibleChild : (int)__VSHPROPID.VSHPROPID_FirstChild)), out pVar); ErrorHandler.ThrowOnFailure(hr); if (VSConstants.S_OK == hr) { // Use Depth first search so at each level we recurse to check if the node has any children // and then look for siblings. uint childId = GetItemId(pVar); while (childId != VSConstants.VSITEMID_NIL) { EnumProjectItems(hierarchy, fileExtension, files, childId, recursionLevel, false, visibleNodesOnly); hr = hierarchy.GetProperty(childId, ((visibleNodesOnly || (hierIsSolution && recursionLevel == 1)) ? (int)__VSHPROPID.VSHPROPID_NextVisibleSibling : (int)__VSHPROPID.VSHPROPID_NextSibling), out pVar); if (VSConstants.S_OK == hr) { childId = GetItemId(pVar); } else { ErrorHandler.ThrowOnFailure(hr); break; } } } } } /// <summary> /// Gets the item id. /// </summary> /// <param name="pvar">VARIANT holding an itemid.</param> /// <returns>Item Id of the concerned node</returns> private static uint GetItemId(object pvar) { if (pvar == null) return VSConstants.VSITEMID_NIL; if (pvar is int) return (uint)(int)pvar; if (pvar is uint) return (uint)pvar; if (pvar is short) return (uint)(short)pvar; if (pvar is ushort) return (uint)(ushort)pvar; if (pvar is long) return (uint)(long)pvar; return VSConstants.VSITEMID_NIL; } /// <summary> /// Check if the file has the expected extension. /// </summary> /// <param name="filePath"></param> /// <param name="extension"></param> /// <returns></returns> public static bool CompareExtension(string filePath, string extension) { bool equals = false; if (!string.IsNullOrEmpty(filePath)) { equals = (string.Compare(System.IO.Path.GetExtension(filePath), extension, StringComparison.OrdinalIgnoreCase) == 0); } return equals; } /// <summary> /// Read file content from a file /// </summary> /// <param name="filePath"> file path </param> /// <returns> file content in a string </returns> internal static string ReadFileContent(string filePath) { // Ensure that the file exists first. if (!File.Exists(filePath)) { Debug.WriteLine(string.Format("Cannot find the file: '{0}'", filePath)); return string.Empty; } string content; using (StreamReader reader = new StreamReader(filePath)) { content = reader.ReadToEnd(); reader.Close(); } return content; } /// <summary> /// Check null references and throw /// </summary> /// <param name="obj"></param> /// <param name="?"></param> public static void CheckNullArgument(object obj, string objectName) { if (obj == null) { throw new System.ArgumentNullException(objectName); } } /// <summary> /// Get offset of the fragment from an Identifier if the identifier.value matches the /// name we are looking for. /// </summary> /// <param name="identifier"></param> /// <param name="expectedName"></param> public static RawChangeInfo AddOffsestFromIdentifier( Identifier identifier, String expectedName, String newName, Boolean keepOldQuote) { RawChangeInfo change = null; if (identifier != null && String.Compare(expectedName, identifier.Value, true) == 0) { if (keepOldQuote) { QuoteType newQuote = QuoteType.NotQuoted; newName = Identifier.DecodeIdentifier(newName, out newQuote); newName = Identifier.EncodeIdentifier(newName, identifier.QuoteType); } change = new RawChangeInfo(identifier.StartOffset, identifier.FragmentLength, expectedName, newName); } return change; } public static IList<ChangeProposal> ConvertOffsets( string projectFullName, string fileFullPath, List<RawChangeInfo> changes, bool defaultIncluded) { // Get the file content into IVsTextLines IVsTextLines textLines = GetTextLines(fileFullPath); int changesCount = changes.Count; List<ChangeProposal> changeProposals = new List<ChangeProposal>(changesCount); for (int changeIndex = 0; changeIndex < changesCount; changeIndex++) { int startLine = 0; int startColumn = 0; int endLine = 0; int endColumn = 0; RawChangeInfo currentChange = changes[changeIndex]; int startPosition = currentChange.StartOffset; int endPosition = currentChange.StartOffset + currentChange.Length; int result = textLines.GetLineIndexOfPosition(startPosition, out startLine, out startColumn); if (result == VSConstants.S_OK) { result = textLines.GetLineIndexOfPosition(endPosition, out endLine, out endColumn); if (result == VSConstants.S_OK) { TextChangeProposal changeProposal = new TextChangeProposal(projectFullName, fileFullPath, currentChange.NewText); changeProposal.StartLine = startLine; changeProposal.StartColumn = startColumn; changeProposal.EndLine = endLine; changeProposal.EndColumn = endColumn; changeProposal.Included = defaultIncluded; changeProposals.Add(changeProposal); } } if (result != VSConstants.S_OK) { throw new InvalidOperationException("Failed to convert offset"); } } return changeProposals; } /// <summary> /// Get IVsTextLines from a file. If that file is in RDT, get text buffer from it. /// If the file is not in RDT, open that file in invisible editor and get text buffer /// from it. /// If failed to get text buffer, it will return null. /// </summary> /// <param name="fullPathFileName">File name with full path.</param> /// <returns>Text buffer for that file.</returns> private static IVsTextLines GetTextLines(string fullPathFileName) { System.IServiceProvider serviceProvider = DataPackage.Instance; IVsTextLines textLines = null; IVsRunningDocumentTable rdt = (IVsRunningDocumentTable)serviceProvider.GetService(typeof(SVsRunningDocumentTable)); if (rdt != null) { IVsHierarchy ppHier = null; uint pitemid, pdwCookie; IntPtr ppunkDocData = IntPtr.Zero; try { rdt.FindAndLockDocument((uint)(_VSRDTFLAGS.RDT_NoLock), fullPathFileName, out ppHier, out pitemid, out ppunkDocData, out pdwCookie); if (pdwCookie != 0) { if (ppunkDocData != IntPtr.Zero) { try { // Get text lines from the doc data IVsPersistDocData docData = (IVsPersistDocData)Marshal.GetObjectForIUnknown(ppunkDocData); if (docData is IVsTextLines) { textLines = (IVsTextLines)docData; } else { textLines = null; } } catch (ArgumentException) { // Do nothing here, it will return null stream at the end. } } } else { // The file is not in RDT, open it in invisible editor and get the text lines from it. IVsInvisibleEditor invisibleEditor = null; TryGetTextLinesAndInvisibleEditor(fullPathFileName, out invisibleEditor, out textLines); } } finally { if (ppunkDocData != IntPtr.Zero) Marshal.Release(ppunkDocData); } } return textLines; } /// <summary> /// Open the file in invisible editor in the running /// documents table (RDT), and get text buffer from that editor. /// </summary> /// <param name="fullPathFileName">File name with full path.</param> /// <param name="spEditor">The result invisible editor.</param> /// <param name="textLines">The result text buffer.</param> /// <returns>True, if the file is opened correctly in invisible editor.</returns> private static bool TryGetTextLinesAndInvisibleEditor(string fullPathFileName, out IVsInvisibleEditor spEditor, out IVsTextLines textLines) { System.IServiceProvider serviceProvider = DataPackage.Instance; spEditor = null; textLines = null; // Need to open this file. Use the invisible editor manager to do so. IVsInvisibleEditorManager spIEM; IntPtr ppDocData = IntPtr.Zero; bool result; Guid IID_IVsTextLines = typeof(IVsTextLines).GUID; try { spIEM = (IVsInvisibleEditorManager)serviceProvider.GetService(typeof(IVsInvisibleEditorManager)); spIEM.RegisterInvisibleEditor(fullPathFileName, null, (uint)_EDITORREGFLAGS.RIEF_ENABLECACHING, null, out spEditor); if (spEditor != null) { int hr = spEditor.GetDocData(0, ref IID_IVsTextLines, out ppDocData); if (hr == VSConstants.S_OK && ppDocData != IntPtr.Zero) { textLines = Marshal.GetTypedObjectForIUnknown(ppDocData, typeof(IVsTextLines)) as IVsTextLines; result = true; } else { result = false; } } else { result = false; } } finally { if (ppDocData != IntPtr.Zero) Marshal.Release(ppDocData); } return result; } } }
В меню Файл выберите команду Сохранить SampleHelper.cs.
Добавьте в проект класс с именем RawChangeInfo.
В редакторе кода измените код следующим образом:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MySamples.Refactoring { /// <summary> /// Helper class to encapsulate StartOffset, FragmentLength and change string from /// parser and SchemaAnalzyer. /// </summary> internal sealed class RawChangeInfo { private int _startOffset; private int _length; private string _oldText; private string _newText; public RawChangeInfo(int startOffset, int length, string oldText, string newText) { _startOffset = startOffset; _length = length; _oldText = oldText; _newText = newText; } public int StartOffset { get { return _startOffset; } set { _startOffset = value; } } public int Length { get { return _length; } } public string OldText { get { return _oldText; } } public string NewText { get { return _newText; } set { _newText = value; } } } }
В меню Файл выберите команду Сохранить RawChangeInfo.cs.
Далее будет определен класс RenameReferenceTextContributor.
Определение класса RenameReferenceTextContributor
Добавьте в проект класс с именем RenameReferenceTextContributor.
В редакторе кода обновите следующим образом операторы using:
using System; using System.Collections.Generic; using Microsoft.Data.Schema.Extensibility; using Microsoft.Data.Schema.Sql; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Data.Schema.Package.Refactoring; using Microsoft.VisualStudio.Data.Schema.Package.Sql.Refactoring;
Замените пространство имен на MySamples.Refactoring:
namespace MySamples.Refactoring
Обновите определение класса следующим образом:
[DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))] internal class RenameReferenceTextContributor : RefactoringContributor<RenameReferenceContributorInput> { }
Задайте атрибут, чтобы объявить, что этот участник совместим со всеми поставщиками схем базы данных, производными от SqlDatabaseSchemaProvider. Класс должен наследовать от RefactoringContributor, соответствующего RenameReferenceContributorInput.
Определите дополнительные константы и закрытые переменные-члены:
#region const private const string TxtExtension = @".txt"; private const string PreviewFriendlyName = @"Text Files"; private const string PreviewDescription = @"Update text symbols in text files in the database project."; private const string PreviewWarningMessage = @"Updating text symbols in text files in the database project can cause inconsistency."; #endregion #region members private RefactoringPreviewGroup _textPreviewGroup; private List<String> _changingFiles; #endregion
Константы содержат сведения, которые отображаются в окне предварительного просмотра. Дополнительные участники используются для отслеживания группы просмотра и для последующего изменения списка текстовых файлов.
Добавьте конструктор класса:
#region ctor public RenameReferenceTextContributor() { _textPreviewGroup = new RefactoringPreviewGroup(PreviewFriendlyName); _textPreviewGroup.Description = PreviewDescription; _textPreviewGroup.WarningMessage = PreviewWarningMessage; _textPreviewGroup.EnableChangeGroupUncheck = true; _textPreviewGroup.EnableChangeUncheck = true; _textPreviewGroup.DefaultChecked = false; _textPreviewGroup.IncludeInCurrentProject = true; // This sample uses the default icon for the file, // but you could provide your own icon here. //RefactoringPreviewGroup.RegisterIcon(TxtExtension, "textfile.ico"); } #endregion
Конструктор инициализирует элемент модели, создавая новую группу просмотра и инициализируя ее свойства. В нем также можно регистрировать значки, которые должны отображаться в окне просмотра для определенных расширений имен файлов. Поскольку синтаксическое выделение в текстовых файлах не используется, регистрация службы языка для текстовых файлов не требуется.
Переопределите свойство PreviewGroup, чтобы оно возвращало группу, сформированную при создании этого участника.
/// <summary> /// Preview group for text files /// </summary> public override RefactoringPreviewGroup PreviewGroup { get { return _textPreviewGroup; } set { _textPreviewGroup = value; } }
Переопределите метод ContributeChanges(Boolean), чтобы он возвращал список предложений по изменению:
/// <summary> /// Contribute to the change proposals /// </summary> /// <param name="input">contributor input</param> /// <returns>List of change proposals with corresponding contributor inputs</returns> protected override Tuple<IList<ChangeProposal>, IList<ContributorInput>> ContributeChanges(RenameReferenceContributorInput input) { RenameReferenceContributorInput referenceInput = input as RenameReferenceContributorInput; if (referenceInput == null) { throw new ArgumentNullException("input"); } string projectFullName; referenceInput.RefactoringOperation.CurrentProjectHierarchy.GetCanonicalName(VSConstants.VSITEMID_ROOT, out projectFullName); return GetChangesForAllTextFiles(referenceInput, projectFullName, _textPreviewGroup.DefaultChecked, out _changingFiles); }
Этот метод вызывает метод GetAllChangesForAllTextFiles, который выполняет большинство необходимых действий.
Добавьте метод GetChangesForAllTextFiles, который проведет итерацию списков текстовых файлов проекта, получит изменения для каждого файла и объединит эти изменения в список предложений по изменению:
/// <summary> /// Get all changes from all text files. /// </summary> private static Tuple<IList<ChangeProposal>, IList<ContributorInput>> GetChangesForAllTextFiles( RenameReferenceContributorInput input, string projectFullName, bool defaultChecked, out List<String> changingFiles) { if (input == null) { throw new ArgumentNullException("input"); } changingFiles = new List<String>(); List<ChangeProposal> allChanges = new List<ChangeProposal>(); List<string> files = new List<string>(); files = SampleHelper.GetAllFilesInProject(input.RefactoringOperation.CurrentProjectHierarchy, TxtExtension, false); // process the text files one by one if (files != null && files.Count > 0) { int fileCount = files.Count; // Get all the changes for all txt files. for (int fileIndex = 0; fileIndex < fileCount; fileIndex++) { IList<ChangeProposal> changes = GetChangesForOneTextFile( input, projectFullName, files[fileIndex], defaultChecked); if (changes != null && changes.Count > 0) { allChanges.AddRange(changes); changingFiles.Add(files[fileIndex]); } } } return new Tuple<IList<ChangeProposal>, IList<ContributorInput>>(allChanges, null); }
Реализуйте метод GetChangesForOneTextFileMethod для возврата списка предложений по изменению, содержащихся в одиночном текстовом файле:
/// <summary> /// Get all the change proposals from one text file. /// </summary> private static IList<ChangeProposal> GetChangesForOneTextFile( RenameReferenceContributorInput input, string projectFullName, string fileFullPath, bool defaultChecked) { const string separators = " \t \r \n \\()[]{}|.+-*/~!@#$%^&<>?:;"; string fileContent = SampleHelper.ReadFileContent(fileFullPath); IList<ChangeProposal> changeProposals= null; if (string.IsNullOrEmpty(fileContent)) { // return empty change list changeProposals = new List<ChangeProposal>(); } else { int startIndex = 0; int maxIndex = fileContent.Length - 1; string oldName = input.OldName; int oldNameLength = oldName.Length; List<RawChangeInfo> changes = new List<RawChangeInfo>(); while (startIndex < maxIndex) { // Text files do not understand schema object information // We do just case-insensitive string match (without schema info) // Only match whole word int offset = fileContent.IndexOf(oldName, startIndex, StringComparison.OrdinalIgnoreCase); // Cannot find match any more, stop the match if (offset < 0) { break; } startIndex = offset + oldNameLength; // match whole word: check before/after characters are separators if (offset > 0) { char charBeforeMatch = fileContent[offset - 1]; // match starts in the middle of a token, discard and move on if (!separators.Contains(charBeforeMatch.ToString())) { continue; } } if (offset + oldNameLength < maxIndex) { char charAfterMatch = fileContent[offset + oldNameLength]; // match ends in the middle of a token, discard and move on if (!separators.Contains(charAfterMatch.ToString())) { continue; } } RawChangeInfo change = new RawChangeInfo(offset, oldNameLength, input.OldName, input.NewName); changes.Add(change); } // convert index-based offsets to ChangeOffsets in ChangeProposals changeProposals = SampleHelper.ConvertOffsets( projectFullName, fileFullPath, changes, defaultChecked); } return changeProposals; }
Поскольку целевой объект рефакторинга не является скриптом Transact-SQL или объектом схемы, в этом методе не используются типы или методы из Microsoft.Data.Schema.ScriptDom и Microsoft.Data.Schema.Sql.SchemaModel. Этот целевой объект рефакторинга выполняет функцию поиска и замены в текстовых файлах, поскольку у символов в текстовых файлах нет информации для схемы.
В меню Файл выберите команду Сохранить RenameTextContributor.cs.
Далее приступим к конфигурации и построению сборки.
Подписывание и построение сборки
В меню Проект выберите команду Свойства RenameTextContributor.
Выберите вкладку Подпись.
Нажмите кнопку Подписать сборку.
В поле Выберите файл ключей строгого имени щелкните <Создать...>.
В диалоговом окне Создание ключа строгого имени в поле Имя файла ключей введите MyRefKey.
(Необязательно.) Можно задать пароль для файла ключей строгого имени.
Нажмите кнопку ОК.
В меню Файл выберите команду Сохранить все.
В меню Построение выберите Построить решение.
Далее необходимо установить и зарегистрировать сборку, чтобы она стала доступным условием теста.
Установка сборки RenameTextContributor
В папке %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions создайте папку с именем MyExtensions.
Скопируйте подписанную сборку (RenameTextContributor.dll) в папку %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions.
Примечание
Не рекомендуется копировать XML-файлы непосредственно в папку %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions.Если использовать вложенную папку, другие файлы, имеющиеся в Visual Studio, будут защищены от случайного изменения.
Далее необходимо зарегистрировать сборку в качестве расширения функции, чтобы она появилась в Visual Studio.
Регистрация сборки RenameTextContributor
В меню Вид выберите пункт Другие окна, а затем — Окно команд, чтобы открыть командное окно.
В окне Команда введите следующий код. Вместо FilePath подставьте путь и имя откомпилированного DLL-файла. Заключите путь и имя файла в кавычки.
Примечание
По умолчанию путь к откомпилированному DLL-файлу таков: путьКРешению\bin\Debug или путьКРешению\bin\Release.
? System.Reflection.Assembly.LoadFrom("FilePath").FullName
? System.Reflection.Assembly.LoadFrom(@"FilePath").FullName
Нажмите клавишу ВВОД.
Скопируйте результирующую строку в буфер обмена. Эта строка должна выглядеть примерно так:
"RenameTextContributor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nnnnnnnnnnnnnnnn"
Откройте редактор обычного текста, такой как "Блокнот".
Предоставьте следующую информацию, указав собственное имя сборки, маркер открытого ключа и тип расширения:
<?xml version="1.0" encoding="utf-8" ?> <extensions assembly="" version="1" xmlns="urn:Microsoft.Data.Schema.Extensions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:Microsoft.Data.Schema.Extensions Microsoft.Data.Schema.Extensions.xsd"> <extension type="MySamples.Refactoring.RenameReferenceTextContributor" assembly="RenameTextContributor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<enter key here>" enabled="true" /> </extensions>
Зарегистрируйте класс, который наследует от RefactoringContributor:
Сохраните файл RenameTextContributor.extensions.xml в папке %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions.
Закройте среду Visual Studio.
Далее будет создан простой проект базы данных для тестирования нового типа рефакторинга.
Тестирование нового участника рефакторинга
Создание проекта базы данных
В меню Файл последовательно выберите команды Создать и Проект.
В Установленных шаблонах разверните узел База данных и щелкните узел SQL Server.
В списке шаблонов выберите Проект базы данных SQL Server 2008.
Нажмите кнопку ОК, чтобы принять имя проекта по умолчанию и создать проект.
Пустой проект базы данных создан.
Добавление таблицы с первичным ключом
В меню Вид выберите команду Представление схемы базы данных.
В представлении схемы последовательно разверните узлы Схема и dbo, щелкните правой кнопкой мыши узел Таблицы, выберите команду Добавить и пункт Таблица.
В диалоговом окне Добавление нового элемента в поле Имя введите employee.
Примечание
Для ввода первой буквы имени таблицы специально используется нижний регистр.
Нажмите кнопку ОК.
Разверните узел Таблицы, правой кнопкой мыши щелкните узел employee, выберите команду Добавить и пункт Первичный ключ.
В диалоговом окне Добавление нового элемента в поле Имя введите PK_Employee_column_1.
Нажмите кнопку ОК.
Далее в проект базы данных будет добавлен текстовый файл, содержащий ссылку на таблицу сотрудников.
Добавление текстового файла, содержащего имя таблицы
В обозревателе решений щелкните правой кнопкой мыши узел проекта базы данных, выберите команду Добавить и пункт Новый элемент.
В диалоговом окне Добавление нового элемента в списке Категории выберите пункт Шаблоны Visual Studio.
В разделе Шаблоны выберите пункт Текстовый файл.
В поле Имя введите SampleText1.txt.
В редакторе кода добавьте следующий код:
This is documentation for the employee table. Any changes made to the employee table name should also be reflected in this text file.
В меню Файл выберите команду Сохранить SampleText1.txt.
Далее новый тип рефакторинга будет использован для изменения имени таблицы и всех ссылок на нее.
Использование нового участника рефакторинга для обновления имени таблицы
В представлении схемы щелкните правой кнопкой мыши узел employee, выберите команду Рефакторинг, а затем — команду Переименовать.
В диалоговом окне Переименование в поле Новое имя введите [Person].
В диалоговом окне Просмотр изменений просмотрите изменяемые группы и найдите группу Текстовые файлы.
Отображаются оба экземпляра "employee" в текстовом файле. В данном пошаговом руководстве определены классы, реализующие этот тип рефакторинга. Установите флажок рядом с каждым изменением.
Нажмите кнопку Применить.
Имя таблицы будет изменено на "Person" как в представлении схемы, так и в файле SampleText1.txt.
Следующие действия
Таким образом, можно создавать дополнительные целевые объекты или новые типы рефакторинга, тем самым сокращая усилия, уходящие на повторное внесение изменений в проект базы данных.
См. также
Задачи
Пошаговое руководство. Создание нового типа рефакторинга баз данных для изменения регистра символов
Основные понятия
Создание пользовательских типов или целевых объектов рефакторинга базы данных