共用方式為


逐步解說:建立新的資料庫重構類型以變更大小寫

在本逐步說明主題中,您將建立、安裝、註冊和測試新類型的「資料庫重構」(Database Refactoring)。 此重構作業會將指定之物件名稱的第一個字母轉換成大寫,並將所有參考更新為這個已更新的名稱。

這個逐步解說將說明下列工作:

  1. 建立新組件,其中包含自訂重構類型的類別。

  2. 安裝並註冊組件,以便在 Visual Studio Premium 或 Visual Studio Ultimate 中使用重構類型。

  3. 建立簡單的「資料庫專案」(Database Project),測試重構類型是否如預期運作。

必要條件

您需要下列元件才能完成此逐步解說:

  • 您必須已安裝 Visual Studio 2010 Premium 或 Visual Studio 2010 Ultimate。

  • 您必須已在電腦上安裝 Visual Studio 2010 軟體開發套件 (SDK)。 若要下載這個套件,請參閱 Microsoft 網站的網頁:Visual Studio 2010 SDK (英文)。

建立具有自訂重構類型的組件

若要建立自訂重構類型,以將物件名稱的第一個字母轉換成大寫,然後更新所有對該物件的參考,您必須實作六個類別:

  • CasingRefactoringCommand - 此類別提供重構功能表的命令名稱,其會指定哪些模型項目可使用重構類型,當使用者按一下該命令時,會呼叫您的重構作業。

  • CasingRefactoringOperation - 此類別會指定重構作業與 [預覽視窗] 的互動方式、指定描述作業的屬性,以及建立 CasingContributorInput 類別。

  • CasingContributorInput - 此類別會將輸入資料儲存到 CasingSymbolContributor 類別。

  • CasingSymbolContributor - 此類別會建置重新命名符號的變更提議清單,也會建立 CasingReferenceContributorInput 類別,以更新所有對目前變更名稱之物件的參考。

  • CasingReferenceContributorInput - 此類別會將輸入資料儲存至 CasingReferenceContributor 類別。

  • CasingReferenceContributor - 此類別會建置一變更提議清單,該清單將預計更新的參考與重新命名的符號關聯。

建立這些類別之前,您將建立類別庫、加入必要的參考,以及加入一些 Helper 程式碼,以簡化您在本逐步解說稍後撰寫的部分程式碼。

若要建立類別庫和 Helper 程式碼

  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. 將來自 Visual Studio 2010 軟體開發套件 (SDK) 的參考加入至下列組件:

    • 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
        {
    }
    

    該屬性可用來指定與此重構類型相容的「資料庫結構描述提供者」(Database Schema Provider)。 對於此範例,您的新重構類型會與從 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. [覆寫 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 的任何「資料庫結構描述提供者」(Database Schema Provider) 相容。 您的類別必須繼承自 CasingContributorInput 類別的 RefactoringContributor

  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. [覆寫 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 的任何「資料庫結構描述提供者」(Database Schema Provider) 相容。 您的類別必須繼承自 CasingReferenceContributorInput 類別的 RefactoringContributor

  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. 在 [建置] 功能表上,按一下 [建置方案]。

    接下來,您必須安裝並註冊組件,以便出現在可用的「測試條件」(Test Condition) 中。

安裝和註冊組件

若要安裝 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 隨附的其他檔案。

    接下來,您必須註冊組件 (一種「擴充功能」(Feature Extension) 類型),以便讓它出現在 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. 按 ENTER。

  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. 將此檔案另存為 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions 資料夾中的 CasingRefactoringType.extensions.xml。

  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。 也會變更主索引鍵中該資料表的參考。

後續步驟

您可以建立自己的其他資料庫重構類型。 您也可以加入其他參與者,讓現有的資料庫重構類型可執行其他類型的檔案或物件。

請參閱

工作

逐步解說:擴充資料庫重新命名重構以處理文字檔

概念

建立自訂資料庫重構型別或目標

重構資料庫程式碼和資料