Поделиться через


Пошаговое руководство. Создание нового типа рефакторинга баз данных для изменения регистра символов

В этом разделе с пошаговыми инструкциями описаны процессы создания, установки, регистрации и тестирования нового типа рефакторинга базы данных. В ходе этой операции рефакторинга первый символ имени заданного объекта будет преобразован в прописной с обновлением всех ссылок на это обновленное имя.

В данном пошаговом руководстве рассмотрены следующие задачи:

  1. Создание новой сборки, содержащей классы для пользовательского типа рефакторинга.

  2. Установка и регистрация сборки, призванная сделать тип рефакторинга доступным в Visual Studio Premium или Visual Studio Ultimate.

  3. Создание простого проекта базы данных для проверки правильности работы типа рефакторинга.

Обязательные компоненты

Ниже приведены компоненты, необходимые для выполнения данного пошагового руководства.

  • Необходимо установить Visual Studio 2010 Premium или Visual Studio 2010 Ultimate.

  • Кроме того, на компьютере должен быть установлен пакет средств разработки программного обеспечения (SDK) Visual Studio 2010. Его можно загрузить со страницы веб-сайта корпорации Майкрософт Visual Studio 2010 SDK.

Создание сборки с пользовательским типом рефакторинга

Чтобы создать пользовательский тип рефакторинга, который сможет преобразовывать первую букву в имени объекта в прописную с обновлением всех ссылок на этот объект, нужно реализовать шесть классов.

  • CasingRefactoringCommand – этот класс предоставляет имя команды для меню рефакторинга, задает элементы модели, для которых будет доступен тип рефакторинга, а также отвечает за вызов операции рефакторинга, когда пользователь выбирает соответствующую команду.

  • CasingRefactoringOperation – этот класс задает взаимодействие операции рефакторинга с окном предварительного просмотра, указывает свойства, которые описывают операцию, и создает класс CasingContributorInput.

  • CasingContributorInput – этот класс предназначен для хранения данных в классе CasingSymbolContributor.

  • CasingSymbolContributor – этот класс строит список предлагаемых изменений для переименованного символа, а также создает класс CasingReferenceContributorInput для обработки обновления ссылок на объект с изменяемым именем.

  • CasingReferenceContributorInput – этот класс предназначен для хранения данных в классе CasingReferenceContributor.

  • CasingReferenceContributor – этот класс строит список предлагаемых изменений, связанных с обновляемыми ссылками на переименованный символ.

Перед созданием этих классов необходимо сначала создать библиотеку классов, затем добавить необходимые ссылки, а также вспомогательный код, упрощающий разработку кода, который будет написан далее в этом пошаговом руководстве.

Создание библиотеки классов и вспомогательного кода

  1. Создайте новый проект библиотеки классов C# с именем CasingRefactoringType.csproj.

  2. Добавьте ссылки на следующие библиотеки классов:

    • Microsoft.Data.Schema.dll;

    • Microsoft.Data.Schema.ScriptDom.dll;

    • Microsoft.Data.Schema.ScriptDom.sql.dll;

    • Microsoft.Data.Schema.Sql.dll;

    • Microsoft.VisualStudio.Data.Schema.Package.dll;

    • Microsoft.VisualStudio.Data.Schema.Package.Sql.dll.

  3. Добавьте ссылки на следующие сборки из пакета средства разработки программного обеспечения (SDK) Visual Studio 2010:

    • Microsoft.VisualStudio.OLE.Interop.dll;

    • Microsoft.VisualStudio.Shell.10.0.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;

    • Microsoft.VisualStudio.TextManager.Interop.dll.

  4. В обозревателе решений переименуйте файл Class1.cs в SampleHelper.cs.

  5. Дважды щелкните файл SampleHelper.cs, чтобы открыть его в редакторе кода.

  6. Замените код в редакторе кода следующим кодом:

    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. e.g. 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;
            }
        }
    }
    
  7. В меню Файл выберите команду Сохранить SampleHelper.cs.

  8. Добавьте в проект класс с именем RawChangeInfo.

  9. В редакторе кода измените код следующим образом:

    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;
                }
            }
        }
    }
    
  10. В меню Файл выберите команду Сохранить RawChangeInfo.cs.

    Далее следует определить класс CasingRefactoringCommand.

Определение класса CasingRefactoringCommand

  1. Добавьте в проект класс с именем CasingRefactorCommand.

  2. В редакторе кода обновите следующим образом операторы using:

    using Microsoft.Data.Schema.Extensibility;
    using Microsoft.Data.Schema.SchemaModel;
    using Microsoft.Data.Schema.SchemaModel.Abstract;
    using Microsoft.Data.Schema.Sql;
    using Microsoft.Data.Schema.Sql.SchemaModel;
    using Microsoft.VisualStudio.Data.Schema.Package.Project;
    using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
    
  3. Замените пространство имен на MySamples.Refactoring:

    namespace MySamples.Refactoring
    
  4. Обновите определение класса следующим образом:

        [DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))]
        internal class CasingRefactorCommand : RefactoringSchemaViewNodeCommand
        {
    }
    

    Чтобы задать поставщиков схемы базы данных, с которыми совместим этот тип рефакторинга, используется соответствующий атрибут. В данном примере новый тип рефакторинга будет работать с любым поставщиком, производным от класса SqlDatabaseSchemaProvider. Созданный класс наследует класс RefactoringSchemaViewNodeCommand. Наследование от этого базового класса свидетельствует о том, что тип рефакторинга доступен для узлов, заданных в представлении схемы. Для узлов файлов и проектов могут быть заданы другие типы рефакторинга.

  5. Далее следует добавить в класс следующий метод переопределения:

    public override void Execute(IDatabaseProjectNode currentProject, IModelElement selectedModelElement)
            {
                CasingRefactorOperation operation = new CasingRefactorOperation(currentProject, selectedModelElement);
                operation.DoOperation();
            }
    

    Этот метод задает поведение при вызове пользователем команды рефакторинга в представлении схемы.

  6. Далее следует добавить в класс следующий метод переопределения:

            public override QueryStatusResult QueryStatus(IModelElement selectedModelElement)
            {
                if (selectedModelElement is IDatabaseColumnSource
                    || selectedModelElement is ISqlSimpleColumn
                    || selectedModelElement is ISqlProcedure
                    || selectedModelElement is ISqlFunction 
                    || selectedModelElement is ISqlIndex
                    || selectedModelElement is ISqlConstraint)
                {
                    return QueryStatusResult.Enabled;
                }
                else
                {
                    return QueryStatusResult.Invisible;
                }
            }
    

    Этот метод определяет в представлении схемы узлы, в которых будет доступна команда рефакторинга.

  7. Наконец следует добавить в класс следующий метод переопределения:

            public override string Text
            {
                get { return "Make First Letter Uppercase"; }
            }
    

    Этот метод предоставляет понятное имя для команды рефакторинга, которая отображается в меню рефакторинга.

  8. В меню Файл выберите Сохранить CasingRefactoringCommand.cs.

    Далее следует определить класс CasingRefactoringOperation.

Определение класса CasingRefactoringOperation

  1. Добавьте в проект класс с именем CasingRefactoringOperation.

  2. В редакторе кода обновите следующим образом операторы using:

    using System;
    using System.Diagnostics;
    using System.Globalization;
    using Microsoft.Data.Schema.SchemaModel;
    using Microsoft.Data.Schema.Sql.SchemaModel;
    using Microsoft.VisualStudio.Data.Schema.Package.Project;
    using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
    
  3. Замените пространство имен на MySamples.Refactoring:

    namespace MySamples.Refactoring
    
  4. Обновите определение класса следующим образом:

        internal class CasingRefactorOperation : RefactoringOperation
        {
    }
    

    Используемый класс должен наследовать от RefactoringOperation.

  5. Добавьте в класс следующую константу и объявления переменных участников:

            #region Const
            private const string CasingRefactorOperationName = @"Make First Letter Uppercase";
            private const string OperationDescription = @"Make First Letter Uppercase";
            private const string OperationTextViewDescription = @"Preview changes:";
            private const string PreviewDialogTitle = @"Preview Changes - {0}";
            private const string ConfirmButtonText = @"&Apply";
            private const string CasingUndoDescription = @"Make first letter uppercase - {0}";
            #endregion
    
            private string _operationName;
            private PreviewWindowInfo _previewWindowInfo;
            private ISqlModelElement _modelElement;
    

    Закрытые константы предоставляют сведения об этой операции, которые будут отображены в окне предварительного просмотра.

  6. Добавьте конструктор для класса:

    public CasingRefactorOperation(IDatabaseProjectNode currentProject, IModelElement selectedModelElement)
                : base(currentProject)
            {
                _operationName = CasingRefactorOperationName;
    
                if (selectedModelElement as ISqlModelElement != null)
                {
                    _modelElement = selectedModelElement as ISqlModelElement;
                }
            }
    

    Конструктор инициализирует имя операции и элемент модели, если они были заданы.

  7. Переопределите свойство PreviewWindowInfo, чтобы получить значения, которые будут отображаться в окне предварительного просмотра в случае применения пользователем данного типа рефакторинга:

            /// <summary>
            /// Preview dialog information for this RenameRefactorOperation.
            /// </summary>
            protected override PreviewWindowInfo PreviewWindowInfo
            {
                get
                {
                    if (_previewWindowInfo == null)
                    {
                        _previewWindowInfo = new PreviewWindowInfo();
                        _previewWindowInfo.ConfirmButtonText = ConfirmButtonText;
                        _previewWindowInfo.Description = OperationDescription;
                        _previewWindowInfo.HelpContext = String.Empty;
                        _previewWindowInfo.TextViewDescription = OperationTextViewDescription;
                        _previewWindowInfo.Title = string.Format(CultureInfo.CurrentCulture,PreviewDialogTitle, CasingRefactorOperationName);
                    }
                    return _previewWindowInfo;
                }
            }
    
  8. Предоставляет дополнительные определения свойства:

            protected override string OperationName
            {
                get
                {
                    return _operationName;
                }
            }
    
            /// <summary>
            /// Undo Description used in undo stack
            /// </summary>
            protected override string UndoDescription
            {
                get
                {
                    return string.Format(CultureInfo.CurrentCulture,
                        CasingUndoDescription, SampleHelper.GetModelElementName(this.ModelElement));
                }
            }
    
            /// <summary>
            /// SchemaIdentifier of currently selected schema object
            /// </summary>
            public ISqlModelElement ModelElement
            {
                get
                {
                    return _modelElement;
                }
                set
                {
                    _modelElement = value;
                }
            }
    
  9. Наконец переопределите метод OnGetContributorInput:

            /// <summary>
            /// According to different selected node, create different CasingContributorInput
            /// </summary>
            /// <returns></returns>
            protected override ContributorInput OnGetContributorInput()
            {
                ContributorInput input = null;
                SqlSchemaModel dataSchemaModel = this.CurrentDataSchemaModel as SqlSchemaModel;
    
                // You might choose to throw an exception here if 
                // schemaModel is null.
                Debug.Assert(dataSchemaModel != null, "DataSchemaModel is null.");
    
                // create contributor input used in this operation
                input = new CasingContributorInput(this.ModelElement);
                return input;
            }
    

    Этот метод создает класс ContributorInput передаваемый участнику рефакторинга для этого типа рефакторинга.

  10. В меню Файл выберите Сохранить CasingRefactoringOperation.cs.

    Далее следует определить класс CasingContributorInput.

Определение класса CasingContributorInput

  1. Добавьте в проект класс с именем CasingContributorInput.

  2. В редакторе кода обновите следующим образом операторы using:

    using System;
    using Microsoft.Data.Schema.Sql.SchemaModel;
    using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
    
  3. Замените пространство имен на MySamples.Refactoring:

    namespace MySamples.Refactoring
    
  4. Обновите определение класса следующим образом:

        internal class CasingContributorInput: ContributorInput
        {
        }
    

    Используемый класс должен наследовать от ContributorInput.

  5. Определите одну дополнительную переменную закрытого участника.

            private ISqlModelElement _modelElement;
    

    Этот участник используется для отслеживания элемента модели, с которым выполняются действия.

  6. Добавьте конструктор класса:

            public CasingContributorInput(ISqlModelElement modelElement)
            {
                _modelElement = modelElement;
            }
    

    Конструктор инициализирует элемент модели.

  7. Добавьте доступное только для чтения свойство для элемента модели.

            /// <summary>
            /// Selected model element
            /// </summary>
            public ISqlModelElement ModelElement
            {
                get 
    
                {
                    return _modelElement;
                }
            }
    
  8. Метод Override Equals предоставляет возможность путем сравнения определить, совпадают ли между собой два объекта CasingContributorInput:

            /// <summary>
            /// Override Equals
            /// </summary>
            /// <param name="obj"></param>
            /// <returns></returns>
            public override bool Equals(object obj)
            {
                CasingContributorInput other = obj as CasingContributorInput;
                return _modelElement.Equals(other.ModelElement);
            }
    

    Для этого участника входные данные будут считаться одинаковыми, если они применимы к одному и тому же элементу модели.

  9. Переопределите метод GetHashCode.

            /// <summary>
            /// Override GetHashCode
            /// </summary>
            /// <returns></returns>
            public override int GetHashCode()
            {
                Int32 hash = _modelElement.GetHashCode();
                return hash;
            }
    
  10. В меню Файл выберите команду Сохранить CasingContributorInput.cs.

    Далее следует определить класс CasingSymbolContributor.

Определение класса CasingSymbolContributor

  1. Добавьте в проект класс с именем CasingSymbolContributor.

  2. В редакторе кода обновите следующим образом операторы using:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Globalization;
    using Microsoft.Data.Schema.Extensibility;
    using Microsoft.Data.Schema.SchemaModel;
    using Microsoft.Data.Schema.ScriptDom.Sql;
    using Microsoft.Data.Schema.Sql.SchemaModel;
    using Microsoft.Data.Schema.Sql;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
    
  3. Замените пространство имен на MySamples.Refactoring:

    namespace MySamples.Refactoring
    
  4. Обновите определение класса следующим образом:

        [DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))]
        internal class CasingSymbolContributor : RefactoringContributor<CasingContributorInput>
        {
        }
    

    Задайте атрибут, объявив тем самым, что этот участник совместим со всеми поставщиками схем базы данных, производными от SqlDatabaseSchemaProvider. Создаваемый класс должен унаследовать от класса RefactoringContributor класс CasingContributorInput.

  5. Определите дополнительные константы и закрытые переменные участника:

            #region Const
            private const string PreviewGroupFriendlyName = @"Schema Objects";
            private const string PreviewDescription = @"Uppercasing the first letter of schema object name and all references to this schema object.";
            private const string PreviewWarning = @"Changing casing of the schema object name may cause errors and warnings when your project is using case-sensitive collations. ";
            #endregion
    
            private RefactoringPreviewGroup _previewGroup;
    

    Константы содержат сведения, которые отображаются в окне предварительного просмотра. Для отслеживания группы предварительного просмотра используется дополнительный участник.

  6. Добавьте конструктор класса:

            #region ctor
            public CasingSymbolContributor()
            {
                _previewGroup = new RefactoringPreviewGroup(PreviewGroupFriendlyName);
                _previewGroup.Description = PreviewDescription;
                _previewGroup.WarningMessage = PreviewWarning;
                _previewGroup.EnableChangeGroupUncheck = false;
                _previewGroup.IncludeInCurrentProject = true;
    
                // the default icon will be used if do not register and icon for your file extensions
                //RefactoringPreviewGroup.RegisterIcon("sql", "SqlFileNode.ico");
                //RefactoringPreviewGroup.RegisterIcon(".dbproj", "DatabaseProjectNode.ico");
    
                // For some contributors, you might register a
                // language service here.
                //_previewGroup.RegisterLanguageService(".sql", ); 
    
                base.RegisterGeneratedInputType(typeof(CasingReferenceContributorInput));
            }
            #endregion
    

    Конструктор инициализирует элемент модели, создавая новую группу просмотра и инициализируя ее свойства. Кроме того, здесь можно зарегистрировать значки, которые будут отображаться в окне предварительного просмотра для определенных расширений имени файла, а также языковую службу, используемую для предоставления цветового выделения синтаксических конструкций в файлах с заданным расширением.

  7. Переопределите свойство PreviewGroup, чтобы оно возвращало группу, сформированную при создании этого участника.

            #region overrides
            /// <summary>
            /// Preview group for schema object files
            /// </summary>
            public override RefactoringPreviewGroup PreviewGroup
            {
                get
                {
                    return _previewGroup;
                }
                set
                {
                    _previewGroup = value;
                }
            }
    
  8. Переопределите метод 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(CasingContributorInput input)
            {
                CasingContributorInput casingInput = input as CasingContributorInput;
                if (casingInput == null)
                {
                    throw new ArgumentNullException("input");
                }
    
                string projectFullName;
                casingInput.RefactoringOperation.CurrentProjectHierarchy.GetCanonicalName(VSConstants.VSITEMID_ROOT, out projectFullName);
    
                Tuple<IList<ChangeProposal>, IList<ContributorInput>> changes = GetChangesFromCurrentSymbolScript(
                        projectFullName,
                        casingInput,
                        casingInput.ModelElement,
                        true);
    
                return changes;
            }
    
            #endregion
    
  9. Далее создайте другой тип ContributorInput:

            /// <summary>
            /// Create a CasingReferenceContributorInput according to passed in CasingContributorInput
            /// </summary>
            /// <param name="orginalInput"></param>
            /// <returns></returns>
            internal ContributorInput CreateCasingReferenceInput(ContributorInput orginalInput)
            {
                CasingContributorInput casingInput = orginalInput as CasingContributorInput;
                Debug.Assert(casingInput != null, "casingInput is null");
    
                CasingReferenceContributorInput referenceInput = new CasingReferenceContributorInput(casingInput.ModelElement);
                referenceInput.SchemaObjectsPreviewGroup = this.PreviewGroup;
                referenceInput.RefactoringOperation = casingInput.RefactoringOperation;
                return referenceInput;
            }
    

    Этот дополнительный тип ContributorInput используется для обработки всех ссылок на элемент, для которого обновляется символ. Этот метод будет вызван следующим методом.

  10. Добавьте метод для построения списка изменений в скрипте, содержащем определение символа, для которого выполняется рефакторинг:

            public Tuple<IList<ChangeProposal>, IList<ContributorInput>> GetChangesFromCurrentSymbolScript(
                string projectFullName,
                ContributorInput input,
                ISqlModelElement modelElement,
                Boolean defaultChecked)
            {
                SampleHelper.CheckNullArgument(input, "input");
                SampleHelper.CheckNullArgument(modelElement, "modelElement");
    
                SqlSchemaModel dataSchemaModel = input.RefactoringOperation.CurrentDataSchemaModel as SqlSchemaModel;
                Debug.Assert(dataSchemaModel != null, "DataSchemaModel is null.");
    
                List<ChangeProposal> allChanges = new List<ChangeProposal>();
    
                // list to hold all side effect contributor inputs
                List<ContributorInput> inputs = new List<ContributorInput>();
    
                string fileFullPath = null;
                ISourceInformation elementSource = modelElement.PrimarySource;
                if (elementSource != null)
                {
                    fileFullPath = elementSource.SourceName;
                }
    
                if (!string.IsNullOrEmpty(fileFullPath))
                {
                    List<RawChangeInfo> changes = AnalyzeScript(dataSchemaModel, modelElement);
    
                    // Convert the offsets returned from parser to the line based offsets
                    allChanges.AddRange(SampleHelper.ConvertOffsets(projectFullName, fileFullPath, changes, defaultChecked));
    
                    // Create a CasingReferenceContributorInput, anything reference this schema object
                    // need to contribute changes for this input.
                    inputs.Add(CreateCasingReferenceInput(input));
                }
                return new Tuple<IList<ChangeProposal>, IList<ContributorInput>>(allChanges, inputs);
            }
    

    Этот метод вызывает метод AnalyzeScript для обработки элементов скрипта.

  11. Добавьте метод AnalyzeScript.

            public static List<RawChangeInfo> AnalyzeScript(SqlSchemaModel dataSchemaModel, ISqlModelElement modelElement)
            {
                SampleHelper.CheckNullArgument(dataSchemaModel, "dataSchemaModel");
    
                // get element source
                ISourceInformation elementSource = modelElement.PrimarySource;
                if (elementSource == null)
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                        "Cannot retrieve element source of {0}", SampleHelper.GetModelElementName(modelElement)));
                }
    
                // get sql fragment
                TSqlFragment fragment = elementSource.ScriptDom as TSqlFragment;
                if (fragment == null)
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                        "Cannot retrieve script fragment of {0}", SampleHelper.GetModelElementName(modelElement)));
                }
    
                List<RawChangeInfo> changes = new List<RawChangeInfo>();
                Identifier id = null;
    
                if (fragment is CreateTableStatement)  // Table
                {
                    id = ((CreateTableStatement)fragment).SchemaObjectName.BaseIdentifier;
                }
                else if (fragment is CreateViewStatement)  // View
                {
                    id = ((CreateViewStatement)fragment).SchemaObjectName.BaseIdentifier;
                }
                else if (fragment is ColumnDefinition)   // Column
                {
                    id = ((ColumnDefinition)fragment).ColumnIdentifier;
                }
                else if (fragment is CreateProcedureStatement)  // Proc
                {
                    ProcedureReference procRef = ((CreateProcedureStatement)fragment).ProcedureReference;
                    if (procRef != null)
                    {
                        id = procRef.Name.BaseIdentifier;
                    }
                }
                else if (fragment is CreateFunctionStatement)    // Function
                {
                    id = ((CreateFunctionStatement)fragment).Name.BaseIdentifier;
                }
                else if (fragment is CreateIndexStatement)       // Index
                {
                    id = ((CreateIndexStatement)fragment).Name;
                }
                else if (fragment is Constraint)    // inline constraint
                {
                    id = ((Constraint)fragment).ConstraintIdentifier;
                }
                else if (fragment is AlterTableAddTableElementStatement)     // default/check constraints
                {
                    IList<Constraint> constraints = ((AlterTableAddTableElementStatement)fragment).TableConstraints;
                    Debug.Assert(constraints.Count == 1, string.Format("Only one constraint expected, actual {0}", constraints.Count));
                    id = constraints[0].ConstraintIdentifier;
                }
                else  // anything NYI
                {
                    Debug.WriteLine(string.Format("Uppercasing symbol of type {0} is not implemented yet.", fragment.GetType().Name));
                }
    
                string oldName = SampleHelper.GetModelElementSimpleName(modelElement);
                if (id != null && oldName.Length > 0)
                {
                    string newName = oldName.Substring(0, 1).ToUpper() + oldName.Substring(1); // upper casing the first letter
    
                    if (string.CompareOrdinal(oldName, newName) != 0)
                    {
                        RawChangeInfo change = SampleHelper.AddOffsestFromIdentifier(id, oldName, newName, true);
                        if (change != null)
                        {
                            changes.Add(change);
                        }
                    }
                }
                return changes;
            }
    

    Этот метод получает исходный скрипт для элемента, для которого выполняется рефакторинг. Затем этот метод извлекает фрагмент кода SQL для этого исходного скрипта. Потом он создает новый список изменений, определяет идентификатор для элемента (на основе типа фрагмента) и добавляет новое изменение в список изменений.

  12. В меню Файл выберите команду Сохранить CasingSymbolContributor.cs.

    Далее следует определить класс CasingReferenceContributorInput.

Определение класса CasingReferenceContributorInput

  1. Добавьте в проект класс с именем CasingReferenceContributorInput.

  2. В редакторе кода обновите следующим образом операторы using:

    using System;
    using Microsoft.Data.Schema.Sql.SchemaModel;
    using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
    
  3. Замените пространство имен на MySamples.Refactoring:

    namespace MySamples.Refactoring
    
  4. Обновите определение класса следующим образом:

        internal class CasingReferenceContributorInput: ContributorInput
        {        
        }        
    

    Используемый класс должен наследовать от ContributorInput.

  5. Определите дополнительные переменные закрытых участников:

            private ISqlModelElement _modelElement;
            private RefactoringPreviewGroup _previewGroup;
    

    Эти элементы используются для отслеживания элемента модели, с которым выполняются операции, и просмотра группы, к которой принадлежат изменения.

  6. Добавьте конструктор класса:

            public CasingReferenceContributorInput(ISqlModelElement modelElement)
            {
                _modelElement = modelElement;
            }
    

    Конструктор инициализирует элемент модели.

  7. Добавьте доступное только для чтения свойство для элемента модели.

            /// <summary>
            /// Selected model element
            /// </summary>
            public ISqlModelElement ModelElement
            {
                get
                {
                    return _modelElement;
                }
            }
    
  8. Определите дополнительную группу просмотра для изменений, выявленных данным конструктором.

            /// <summary>
            /// Preview group that change proposals belong to
            /// </summary>
            public RefactoringPreviewGroup SchemaObjectsPreviewGroup
            {
                get
                {
                    return _previewGroup;
                }
                set 
                { 
                    _previewGroup = value; 
                }
            }
    
  9. Метод Override Equals предоставляет возможность путем сравнения определить, совпадают ли между собой два объекта CasingReferenceContributorInput:

            /// <summary>
            /// Override Equals
            /// </summary>
            /// <param name="obj"></param>
            /// <returns></returns>
            public override bool Equals(object obj)
            {
                CasingContributorInput other = obj as CasingContributorInput;
                return _modelElement.Equals(other.ModelElement);
            }
    

    Для этого участника входные данные будут считаться одинаковыми, если они применимы к одному и тому же элементу модели.

  10. Переопределите метод GetHashCode.

            /// <summary>
            /// Override GetHashCode
            /// </summary>
            /// <returns></returns>
            public override int GetHashCode()
            {
                Int32 hash = _modelElement.GetHashCode();
                return hash;
            }
    
  11. В меню Файл выберите Сохранить CasingReferenceContributorInput.cs.

    Далее следует определить класс CasingReferenceContributor.

Определение класса CasingReferenceContributor

  1. Добавьте в проект класс с именем CasingReferenceContributor.

  2. В редакторе кода обновите следующим образом операторы using:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using Microsoft.Data.Schema.Extensibility;
    using Microsoft.Data.Schema.SchemaModel;
    using Microsoft.Data.Schema.ScriptDom.Sql;
    using Microsoft.Data.Schema.Sql.SchemaModel;
    using Microsoft.Data.Schema.Sql;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
    
  3. Замените пространство имен на MySamples.Refactoring:

    namespace MySamples.Refactoring
    
  4. Обновите определение класса следующим образом:

    [DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))]
        internal class CasingReferenceContributor : RefactoringContributor<CasingReferenceContributorInput>
        {
        }
    

    Задайте атрибут, объявив тем самым, что этот участник совместим со всеми поставщиками схем базы данных, производными от SqlDatabaseSchemaProvider. Создаваемый класс должен унаследовать от класса RefactoringContributor класс CasingReferenceContributorInput.

  5. Определите дополнительные константы и закрытые переменные участника:

            #region Const
            private const string PreviewGroupFriendlyName = @"Schema Objects";
            private const string PreviewDescription = @"Uppercasing the name of this schema object and all references to this schema object.";
            private const string PreviewWarning = @"Changing casing of the schema object name may cause errors and warnings when your project is using case-sensitive collations. ";
            #endregion
    
            private RefactoringPreviewGroup _previewGroup;
    

    Константы содержат сведения, которые отображаются в окне предварительного просмотра. Для отслеживания группы предварительного просмотра используется дополнительный участник.

  6. Добавьте конструктор класса:

            public CasingReferenceContributor()
            {
            }
    
  7. Переопределите свойство PreviewGroup, чтобы оно возвращало группу, сформированную при создании этого участника.

            #region overrides
            /// <summary>
            /// Preview group for text files
            /// </summary>
            public override RefactoringPreviewGroup PreviewGroup
            {
                get
                {
                    return _previewGroup;
                }
                set
                {
                    _previewGroup = value;
                }
            }
            #endregion
    
  8. Переопределите метод 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(CasingReferenceContributorInput input)
            {
                // cast input into reference input
                CasingReferenceContributorInput casingReferenceInput = input as CasingReferenceContributorInput;
                if (casingReferenceInput == null)
                {
                    throw new ArgumentNullException("input");
                }
    
                // Make sure CasingReferenceContributor and CasingSymbolContributor for a same refactoring operation
                // share the same preview group instance.
                if (casingReferenceInput.SchemaObjectsPreviewGroup != null)
                {
                    _previewGroup = casingReferenceInput.SchemaObjectsPreviewGroup;
                }
    
                string projectFullName;
                casingReferenceInput.RefactoringOperation.CurrentProjectHierarchy.GetCanonicalName(VSConstants.VSITEMID_ROOT, out projectFullName);
    
                Tuple<IList<ChangeProposal>, IList<ContributorInput>> changes = GetChangesFromReferencedSymbolScripts(
                        projectFullName,
                        casingReferenceInput,
                        casingReferenceInput.ModelElement,
                        true
                   );
    
                return changes;
            }
    

    ContributeChangesMethod вызывает метод GetChangesFromReferencedSymbolScripts.

  9. Реализуйте метод GetChangesFromReferencedSymbolScripts, чтобы он возвращал список предложенных изменений в скриптах, содержащих ссылки на обновляемый символ:

            public static Tuple<IList<ChangeProposal>, IList<ContributorInput>> GetChangesFromReferencedSymbolScripts(
                string projectFullName,
                ContributorInput input,
                ISqlModelElement modelElement,
                bool defaultChecked  // if the preview group is by default checked in the preview window
                )
            {
                SampleHelper.CheckNullArgument(input, "input");
    
                SqlSchemaModel dataSchemaModel = input.RefactoringOperation.CurrentDataSchemaModel as SqlSchemaModel;
                Debug.Assert(dataSchemaModel != null, "The DataSchemaModel is null for current Database project.");
    
                // Get all the changes for these schema objects that referencing the changed IModelElement.
                List<ChangeProposal> allChanges = new List<ChangeProposal>();
                Dictionary<string, List<RawChangeInfo>> fileChanges = new Dictionary<string, List<RawChangeInfo>>();
    
                List<RelationshipEntrySource> relationshipEntrySources = GetDependentEntries(dataSchemaModel, modelElement, true, true);
                foreach (var entry in relationshipEntrySources)
                {
                    string fileFullPath = entry.Item1.SourceName;
                    if (!string.IsNullOrEmpty(fileFullPath))
                    {
                        IList<RawChangeInfo> result = AnalyzeRelationshipEntrySource(dataSchemaModel, modelElement, entry.Item2, entry.Item1);
                        if (result != null)
                        {
                            List<RawChangeInfo> fileChange = null;
                            if (!fileChanges.TryGetValue(fileFullPath, out fileChange))
                            {
                                fileChange = new List<RawChangeInfo>();
                                fileChanges.Add(fileFullPath, fileChange);
                            }
                            fileChange.AddRange(result);
                        }
                    }
                }
    
                // Convert the offsets returned from ScriptDom to the line based offsets
                foreach (string fileFullPath in fileChanges.Keys)
                {
                    allChanges.AddRange(SampleHelper.ConvertOffsets(projectFullName,
                                                    fileFullPath,
                                                    fileChanges[fileFullPath],
                                                    defaultChecked));
                }
    
                // Change propagation is not considered in this sample. 
                // Thus the second value in the returned Tuple is set to null
                return new Tuple<IList<ChangeProposal>, IList<ContributorInput>>(allChanges, null);
            }
    

    Этот метод извлекает список всех зависимостей обновляемого символа. Затем он вызывает метод AnalyzeRelationshipEntrySource для каждой ссылки для определения всех обязательных дополнительных изменений.

  10. Добавьте метод AnalyzeRelationshipEntrySource:

            public static IList<RawChangeInfo> AnalyzeRelationshipEntrySource(
                SqlSchemaModel dataSchemaModel,
                ISqlModelElement modelElement,
                ISourceInformation relationshipEntrySource)
            {
                SampleHelper.CheckNullArgument(dataSchemaModel, "dataSchemaModel");
    
                List<Identifier> identifiers = new List<Identifier>();
    
                TSqlFragment fragment = relationshipEntrySource.ScriptDom as TSqlFragment;
    
                // handle expressions
                if (fragment is SelectColumn)
                {
                    Expression exp = ((SelectColumn)fragment).Expression;// as Expression;
                    fragment = exp as TSqlFragment;
                }
                else if (fragment is ExpressionWithSortOrder)
                {
                    Expression exp = ((ExpressionWithSortOrder)fragment).Expression; // as Expression;
                    fragment = exp as TSqlFragment;
                }
                else if (fragment is ExpressionGroupingSpecification)
                {
                    Expression exp = ((ExpressionGroupingSpecification)fragment).Expression; // as Expression;
                    fragment = exp as TSqlFragment;
                }
    
                // handle different fragment
                if (fragment is Identifier)
                {
                    identifiers.Add((Identifier)fragment); ;
                }
                else if (fragment is Column)
                {
                    identifiers.AddRange(((Column)fragment).Identifiers);
                }
                else if (fragment is ColumnWithSortOrder)
                {
                    identifiers.Add(((ColumnWithSortOrder)fragment).ColumnIdentifier);
                }
                else if (fragment is SchemaObjectName)
                {
                    identifiers.Add(((SchemaObjectName)fragment).BaseIdentifier);
                }
                else if (fragment is SchemaObjectTableSource)
                {
                    identifiers.Add(((SchemaObjectTableSource)fragment).SchemaObject.BaseIdentifier);
                }
                else if (fragment is SchemaObjectDataModificationTarget)
                {
                    identifiers.Add(((SchemaObjectDataModificationTarget)fragment).SchemaObject.BaseIdentifier);
                }
                else if (fragment is FunctionCall)
                {
                    FunctionCall funcCall = (FunctionCall)fragment;
                    IdentifiersCallTarget identsCallTarget = funcCall.CallTarget as IdentifiersCallTarget;
                    if (identsCallTarget != null)
                    {
                        identifiers.AddRange(identsCallTarget.Identifiers);
                    }
                    identifiers.Add(funcCall.FunctionName);
                }
                else if (fragment is ProcedureReference)
                {
                    SchemaObjectName procRefName = ((ProcedureReference)fragment).Name;
                    if (procRefName != null)
                    {
                        identifiers.Add(procRefName.BaseIdentifier);
                    }
                }
                else if (fragment is TriggerObject)
                {
                    SchemaObjectName triggerName = ((TriggerObject)fragment).Name;
                    if (triggerName != null)
                    {
                        identifiers.Add(triggerName.BaseIdentifier);
                    }
                }
                else if (fragment is FullTextIndexColumn)
                {
                    identifiers.Add(((FullTextIndexColumn)fragment).Name);
                }
                else if (fragment is SecurityTargetObject)
                {
                    identifiers.AddRange(((SecurityTargetObject)fragment).ObjectName.Identifiers);
                }
                else  // other types of fragments are not handled in this sample
                {
                    Debug.WriteLine(string.Format("Uppercasing referencing object of type {0} is not implemented yet.", fragment.GetType().Name));
                }
    
                List<RawChangeInfo> changes = new List<RawChangeInfo>();
                string oldName = SampleHelper.GetModelElementSimpleName(modelElement);
                if (identifiers.Count > 0 && oldName.Length > 0)
                {
                    string newName = oldName.Substring(0, 1).ToUpper() + oldName.Substring(1); // upper casing the first letter
    
                    if (string.CompareOrdinal(oldName, newName) != 0)
                    {
                        // list of changes for this relationship entry
                        RawChangeInfo change = null;
                        foreach (Identifier idf in identifiers)
                        {
                            change = SampleHelper.AddOffsestFromIdentifier(idf, oldName, newName, true);
                            if (change != null)
                            {
                                changes.Add(change);
                            }
                        }
                    }
                }
                return changes;
            }
    

    Этот список извлекает список изменений, которые необходимо внести во фрагменты скрипта, зависящие от обновленного символа.

  11. Добавьте метод GetDependentEntries:

            /// <summary>
            ///  Get all relating relationship entries for the model element and its composing and hierarchical children
            /// </summary>
            internal static List<System.Tuple<ISourceInformation, IModelRelationshipEntry>> GetDependentEntries(
                SqlSchemaModel dataSchemaModel,
                ISqlModelElement modelElement,
                bool ignoreComposedRelationship,
                bool includeChildDependencies)
            {
                SampleHelper.CheckNullArgument(dataSchemaModel, "dataSchemaModel");
                SampleHelper.CheckNullArgument(modelElement, "modelElement");
    
                var dependencies = new List<System.Tuple<ISourceInformation, IModelRelationshipEntry>>();
    
                List<IModelRelationshipEntry> relatingRelationships = new List<IModelRelationshipEntry>();
                GetDependentEntries(modelElement,
                                    dataSchemaModel,
                                    new Dictionary<IModelElement, Object>(),
                                    relatingRelationships,
                                    includeChildDependencies);
    
                foreach (IModelRelationshipEntry entry in relatingRelationships)
                {
                    ModelRelationshipType relationshipType = entry.RelationshipClass.ModelRelationshipType;
                    if (!ignoreComposedRelationship ||
                        (relationshipType != ModelRelationshipType.Composing))
                    {
                        ISqlModelElement relatingElement = entry.FromElement as ISqlModelElement;
                        Debug.Assert(relatingElement != null, "Relating element got from ModelStore is null.");
    
                        foreach (var si in relatingElement.GetRelationshipEntrySources(entry))
                        {
                            dependencies.Add(new System.Tuple<ISourceInformation, IModelRelationshipEntry>(si, entry));
                        }
                    }
                }
                return dependencies;
            }
    
            private static void GetDependentEntries(
                IModelElement modelElement,
                DataSchemaModel dataSchemaModel,
                Dictionary<IModelElement, Object> visitElement,
                List<IModelRelationshipEntry> relationshipEntries,
                Boolean includeChildDependencies)
            {
                if (modelElement != null &&
                    !visitElement.ContainsKey(modelElement))
                {
                    visitElement[modelElement] = null;
    
                    IList<IModelRelationshipEntry> relatingRelationships = modelElement.GetReferencingRelationshipEntries();
                    relationshipEntries.AddRange(relatingRelationships);
    
                    if (includeChildDependencies)
                    {
                        // First loop through all composed children of this element, and get their relationship entries as well
                        foreach (IModelRelationshipEntry entry in modelElement.GetReferencedRelationshipEntries())
                        {
                            if (entry.RelationshipClass.ModelRelationshipType == ModelRelationshipType.Composing)
                            {
                                GetDependentEntries(entry.Element, dataSchemaModel, visitElement, relationshipEntries, includeChildDependencies);
                            }
                        }
    
                        // Then loop through all hierarchical children of this element, add their dependents to the list.
                        foreach (IModelRelationshipEntry entry in relatingRelationships)
                        {
                            if (entry.RelationshipClass.ModelRelationshipType == ModelRelationshipType.Hierarchical)
                            {
                                GetDependentEntries(entry.FromElement, dataSchemaModel, visitElement, relationshipEntries, includeChildDependencies);
                            }
                        }
                    }
                }
            }
    

    В меню Файл выберите Сохранить CasingReferenceContributor.cs.

    Далее приступим к настройке и построению сборки.

Подписывание и построение сборки

  1. В меню Проект выберите пункт CasingRefactoringType: свойства.

  2. Выберите вкладку Подпись.

  3. Нажмите кнопку Подписать сборку.

  4. В поле Выберите файл ключей строгого имени щелкните <Создать...>.

  5. В диалоговом окне Создание ключа строгого имени в поле Имя файла ключей введите MyRefKey.

  6. (Необязательно.) Можно задать пароль для файла ключей строгого имени.

  7. Нажмите кнопку ОК.

  8. В меню Файл выберите команду Сохранить все.

  9. В меню Построение выберите Построить решение.

    Далее необходимо установить и зарегистрировать сборку, чтобы она стала доступным условием теста.

Установка и регистрация сборки

Установка сборки CasingRefactoringType

  1. В папке %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions создайте папку с именем MyExtensions.

  2. Скопируйте подписанную сборку (CasingRefactoringType.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.

Регистрация сборки CasingRefactoringType

  1. В меню Вид выберите пункт Другие окна, а затем Окно команд, чтобы открыть командное окно.

  2. В окне Команда введите следующий код. Вместо FilePath подставьте путь и имя откомпилированного DLL-файла. Заключите путь и имя файла в кавычки.

    Примечание

    По умолчанию путь к откомпилированному DLL-файлу таков: путьКРешению\bin\Debug или путьКРешению\bin\Release.

    ? System.Reflection.Assembly.LoadFrom("FilePath").FullName
    
    ? System.Reflection.Assembly.LoadFrom(@"FilePath").FullName
    
  3. Нажмите клавишу ВВОД.

  4. Скопируйте результирующую строку в буфер обмена. Эта строка должна выглядеть примерно так:

    "GeneratorAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nnnnnnnnnnnnnnnn"
    
  5. Откройте редактор обычного текста, такой как "Блокнот".

    Важно!

    В Windows Vista и Microsoft Windows Server 2008 откройте редактор от имени администратора, чтобы можно было сохранить файл в папке Program Files.

  6. Предоставьте следующую информацию, указав собственное имя сборки, маркер открытого ключа и тип расширения:

    <?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.CasingRefactorCommand" 
    assembly=" CasingRefactoringType, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<enter key here>" enabled="true" />
      <extension type="MySamples.Refactoring.CasingSymbolContributor" 
            assembly="CasingRefactoringType, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<enter key here>" enabled="true" />
      <extension type="MySamples.Refactoring.CasingReferenceContributor" 
            assembly="CasingRefactoringType, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<enter key here>" enabled="true" />
    </extensions>
    

    Этот XML-файл используется для регистрации класса, наследующего от RefactoringCommand, и всех связанных классов, производных от RefactoringContributor.

  7. Сохраните файл CasingRefactoringType.extensions.xml в папке %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions.

  8. Закройте среду Visual Studio.

    Далее будет создан простой проект базы данных для тестирования нового типа рефакторинга.

Тестирование нового типа рефакторинга

Создание проекта базы данных

  1. В меню Файл последовательно выберите Создать и Проект.

  2. В области Установленные шаблоны разверните узел База данных и щелкните узел SQL Server.

  3. В списке шаблонов выберите Проект базы данных SQL Server 2008.

  4. Нажмите кнопку ОК, чтобы принять имя проекта по умолчанию и создать проект.

    Пустой проект базы данных создан.

Добавление таблицы с первичным ключом

  1. В меню Вид выберите команду Представление схемы базы данных.

  2. В представлении схемы последовательно разверните узлы Схема и dbo, щелкните правой кнопкой мыши узел Таблицы, выберите команду Добавить и пункт Таблица.

  3. В диалоговом окне Добавление нового элемента в поле Имя введите employee.

    Примечание

    Для ввода первой буквы имени таблицы специально используется нижний регистр.

  4. Нажмите кнопку ОК.

  5. Разверните узел Таблицы, правой кнопкой мыши щелкните узел employee, выберите команду Добавить и пункт Первичный ключ.

  6. В диалоговом окне Добавление нового элемента в поле Имя введите PK_Employee_column_1.

  7. Нажмите кнопку ОК.

    Далее новый тип рефакторинга будет использован для изменения имени таблицы и всех ссылок на нее.

Использование нового типа рефакторинга для обновления имени таблицы

  1. В представлении схемы щелкните правой кнопкой мыши узел "employee", выберите команду Рефакторинг, а затем Сделать первую букву прописной.

    Этот новый тип рефакторинга был определен в этом пошаговом руководстве.

  2. В диалоговом окне Просмотр изменений просмотрите изменения и нажмите кнопку Применить.

    Имя таблицы обновится и станет Employee. Обновится также ссылка на эту таблицу в первичном ключе.

Следующие действия

Пользователь может создать собственные дополнительные типы рефакторинга базы данных. Кроме того, можно добавить другие участники, обеспечив возможность выполнения рефакторинга базы данных для дополнительных типов файлов или объектов.

См. также

Задачи

Пошаговое руководство. Распространение рефакторинга с переименованием для баз данных на текстовые файлы

Основные понятия

Создание пользовательских типов или целевых объектов рефакторинга базы данных

Рефакторинг кода и данных базы данных