共用方式為


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

在本逐步說明主題中,您將建立、安裝、註冊和測試新的重新命名重構參與者。 此重構「目標」(Target) 將擴充 Visual Studio Premium 或 Visual Studio Ultimate 的功能,可讓您的「資料庫重構」(Database Refactoring) 重新命名您的「資料庫專案」(Database Project) 中文字檔所含「資料庫物件」(Database Object) 的參考。

當您將新的重構參與者加入至現有類型的重構時,它必須使用現有的參與者輸入類別。

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

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

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

  3. 建立簡單的資料庫專案來測試重構目標是否如預期運作。

必要條件

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

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

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

建立具有自訂重構目標的組件

若要建立自訂重構目標,讓重新命名重構處理文字檔,您必須實作一個類別來提供新的 RefactoringContributor:

  • RenameReferenceTextContributorContributor - 此類別會針對重新命名的符號建置變更提議清單。 這些變更提議適用於資料庫專案中文字檔所包含的每個參考。

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

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

  1. 建立新的 C# 類別庫專案並命名為 RenameTextContributor.csproj。

  2. 加入下列 .NET 組件的參考:

    • Microsoft.Data.Schema

    • Microsoft.Data.Schema.ScriptDom

    • Microsoft.Data.Schema.ScriptDom.sql

    • Microsoft.Data.Schema.Sql

  3. 加入下列組件 (可在 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB 資料夾中找到) 的參考。

    • Microsoft.Data.Schema.Tools.Sql.dll

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

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

  4. 將來自 Visual Studio 2010 軟體開發套件 (SDK) 的參考加入至下列組件:

    • Microsoft.VisualStudio.OLE.Interop.dll

    • Microsoft.VisualStudio.Shell.10.0.dll

    • Microsoft.VisualStudio.TextManager.Interop.dll

    • Microsoft.VisualStudio.Shell.Interop.dll

    • Microsoft.VisualStudio.Shell.Interop.8.0.dll

    • Microsoft.VisualStudio.Shell.Interop.9.0.dll

    • Microsoft.VisualStudio.Shell.Interop.10.0.dll

  5. 在 [方案總管] 中,將 Class1.cs 重新命名為 SampleHelper.cs。

  6. 按兩下 SampleHelper.cs,在程式碼編輯器中開啟它。

    注意事項注意事項

    這個協助程式類別與自訂重構類型逐步解說中使用的 Helper 文字相同。 您可以將該專案的原始程式碼複製到新專案以節省時間。

  7. 以下列程式碼取代程式碼編輯器的內容:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Runtime.InteropServices;
    using Microsoft.Data.Schema.SchemaModel;
    using Microsoft.Data.Schema.ScriptDom.Sql;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
    using Microsoft.VisualStudio.Data.Schema.Package.UI;
    using Microsoft.VisualStudio.Shell.Interop;
    using Microsoft.VisualStudio.TextManager.Interop;
    
    namespace MySamples.Refactoring
    {
        internal static class SampleHelper
        {
            public static String GetModelElementName(IModelElement modelElement)
            {
                SampleHelper.CheckNullArgument(modelElement, "modelElement");
                return modelElement.ToString();
            }
    
            /// <summary>
            /// Given a model element, returns its simple name.
            /// </summary>
            public static String GetModelElementSimpleName(IModelElement modelElement)
            {
                String separator = ".";
                String simpleName = String.Empty;
                String fullName = modelElement.ToString();
                String[] nameParts = fullName.Split(separator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                if (nameParts.Length > 0)
                {
                    simpleName = nameParts[nameParts.Length - 1]; // last part 
                }
                if (simpleName.StartsWith("[") && simpleName.EndsWith("]"))
                {
                    simpleName = simpleName.Substring(1, simpleName.Length - 2);
                }
                return simpleName;
            }
    
            /// <summary>
            /// Find all files in the project with the specified file extension
            /// </summary>
            public static List<string> GetAllFilesInProject(IVsHierarchy solutionNode, string fileExtension, bool visibleNodesOnly)
            {
                List<string> files = new List<string>();
                if (null != solutionNode)
                {
                    EnumProjectItems(solutionNode, fileExtension, files,
                                    VSConstants.VSITEMID_ROOT,  // item id of solution root node
                                    0,                          // recursion from solution node
                                    true,                       // hierarchy is Solution node
                                    visibleNodesOnly);          // visibleNodesOnly
                }
                return files;
            }
    
            /// <summary>
            /// Enumerates recursively over the hierarchy items.
            /// </summary>
            /// <param name="hierarchy">hierarchy to enmerate over.</param>
            /// <param name="fileExtension">type of files we need to collect from the project</param>
            /// <param name="files">list of file paths</param>
            /// <param name="itemid">item id of the hierarchy</param>
            /// <param name="recursionLevel">Depth of recursion. For example, if recursion started with the Solution
            /// node, then : Level 0 -- Solution node, Level 1 -- children of Solution, etc.</param>
            /// <param name="hierIsSolution">true if hierarchy is Solution Node. </param>
            /// <param name="visibleNodesOnly">true if only nodes visible in the Solution Explorer should
            /// be traversed. false if all project items should be traversed.</param>
            private static void EnumProjectItems(IVsHierarchy hierarchy,
                                                string fileExtension,
                                                List<string> files,
                                                uint itemid,
                                                int recursionLevel,
                                                bool hierIsSolution,
                                                bool visibleNodesOnly)
            {
                int hr;
                IntPtr nestedHierarchyObj;
                uint nestedItemId;
                Guid hierGuid = typeof(IVsHierarchy).GUID;
    
    
                // Check first if this node has a nested hierarchy. 
                hr = hierarchy.GetNestedHierarchy(itemid, ref hierGuid, out nestedHierarchyObj, out nestedItemId);
                if (VSConstants.S_OK == hr && IntPtr.Zero != nestedHierarchyObj)
                {
                    IVsHierarchy nestedHierarchy = Marshal.GetObjectForIUnknown(nestedHierarchyObj) as IVsHierarchy;
                    Marshal.Release(nestedHierarchyObj);
                    if (nestedHierarchy != null)
                    {
                        EnumProjectItems(nestedHierarchy, fileExtension, files,
                                        nestedItemId,
                                        recursionLevel,
                                        false,
                                        visibleNodesOnly);
                    }
                }
                else
                {
                    // Check if the file extension of this node matches 
                    string fileFullPath;
                    hierarchy.GetCanonicalName(itemid, out fileFullPath);
                    if (CompareExtension(fileFullPath, fileExtension))
                    {
                        // add matched file paths into the list
                        files.Add(fileFullPath);
                    }
    
                    recursionLevel++;
    
                    //Get the first child node of the current hierarchy being walked
                    object pVar;
                    hr = hierarchy.GetProperty(itemid,
                        ((visibleNodesOnly || (hierIsSolution && recursionLevel == 1) ?
                            (int)__VSHPROPID.VSHPROPID_FirstVisibleChild : (int)__VSHPROPID.VSHPROPID_FirstChild)),
                        out pVar);
                    ErrorHandler.ThrowOnFailure(hr);
                    if (VSConstants.S_OK == hr)
                    {
                        // Use Depth first search so at each level we recurse to check if the node has any children
                        // and then look for siblings.
                        uint childId = GetItemId(pVar);
                        while (childId != VSConstants.VSITEMID_NIL)
                        {
                            EnumProjectItems(hierarchy, fileExtension, files, childId, recursionLevel, false, visibleNodesOnly);
                            hr = hierarchy.GetProperty(childId,
                                ((visibleNodesOnly || (hierIsSolution && recursionLevel == 1)) ?
                                    (int)__VSHPROPID.VSHPROPID_NextVisibleSibling : (int)__VSHPROPID.VSHPROPID_NextSibling),
                                out pVar);
                            if (VSConstants.S_OK == hr)
                            {
                                childId = GetItemId(pVar);
                            }
                            else
                            {
                                ErrorHandler.ThrowOnFailure(hr);
                                break;
                            }
                        }
                    }
                }
            }
    
            /// <summary>
            /// Gets the item id.
            /// </summary>
            /// <param name="pvar">VARIANT holding an itemid.</param>
            /// <returns>Item Id of the concerned node</returns>
            private static uint GetItemId(object pvar)
            {
                if (pvar == null) return VSConstants.VSITEMID_NIL;
                if (pvar is int) return (uint)(int)pvar;
                if (pvar is uint) return (uint)pvar;
                if (pvar is short) return (uint)(short)pvar;
                if (pvar is ushort) return (uint)(ushort)pvar;
                if (pvar is long) return (uint)(long)pvar;
                return VSConstants.VSITEMID_NIL;
            }
    
            /// <summary>
            /// Check if the file has the expected extension.
            /// </summary>
            /// <param name="filePath"></param>
            /// <param name="extension"></param>
            /// <returns></returns>
            public static bool CompareExtension(string filePath, string extension)
            {
                bool equals = false;
                if (!string.IsNullOrEmpty(filePath))
                {
                    equals = (string.Compare(System.IO.Path.GetExtension(filePath), extension, StringComparison.OrdinalIgnoreCase) == 0);
                }
                return equals;
            }
    
            /// <summary>
            /// Read file content from a file
            /// </summary>
            /// <param name="filePath"> file path </param>
            /// <returns> file content in a string </returns>
            internal static string ReadFileContent(string filePath)
            {
                //  Ensure that the file exists first.
                if (!File.Exists(filePath))
                {
                    Debug.WriteLine(string.Format("Cannot find the file: '{0}'", filePath));
                    return string.Empty;
                }
    
                string content;
                using (StreamReader reader = new StreamReader(filePath))
                {
                    content = reader.ReadToEnd();
                    reader.Close();
                }
                return content;
            }
    
            /// <summary>
            ///  Check null references and throw
            /// </summary>
            /// <param name="obj"></param>
            /// <param name="?"></param>
            public static void CheckNullArgument(object obj, string objectName)
            {
                if (obj == null)
                {
                    throw new System.ArgumentNullException(objectName);
                }
            }
    
            /// <summary>
            /// Get offset of the fragment from an Identifier if the identifier.value matches the
            /// name we are looking for.
            /// </summary>
            /// <param name="identifier"></param>
            /// <param name="expectedName"></param>
            public static RawChangeInfo AddOffsestFromIdentifier(
               Identifier identifier,
                String expectedName,
                String newName,
                Boolean keepOldQuote)
            {
                RawChangeInfo change = null;
                if (identifier != null && String.Compare(expectedName, identifier.Value, true) == 0)
                {
                    if (keepOldQuote)
                    {
                        QuoteType newQuote = QuoteType.NotQuoted;
                        newName = Identifier.DecodeIdentifier(newName, out newQuote);
                        newName = Identifier.EncodeIdentifier(newName, identifier.QuoteType);
                    }
                    change = new RawChangeInfo(identifier.StartOffset, identifier.FragmentLength, expectedName, newName);
                }
                return change;
            }
    
            public static IList<ChangeProposal> ConvertOffsets(
                string projectFullName,
                string fileFullPath,
                List<RawChangeInfo> changes,
                bool defaultIncluded)
            {
                // Get the file content into IVsTextLines
                IVsTextLines textLines = GetTextLines(fileFullPath);
    
                int changesCount = changes.Count;
                List<ChangeProposal> changeProposals = new List<ChangeProposal>(changesCount);
                for (int changeIndex = 0; changeIndex < changesCount; changeIndex++)
                {
                    int startLine = 0;
                    int startColumn = 0;
                    int endLine = 0;
                    int endColumn = 0;
    
    
                    RawChangeInfo currentChange = changes[changeIndex];
                    int startPosition = currentChange.StartOffset;
                    int endPosition = currentChange.StartOffset + currentChange.Length;
                    int result = textLines.GetLineIndexOfPosition(startPosition, out startLine, out startColumn);
                    if (result == VSConstants.S_OK)
                    {
                        result = textLines.GetLineIndexOfPosition(endPosition, out endLine, out endColumn);
                        if (result == VSConstants.S_OK)
                        {
                            TextChangeProposal changeProposal = new TextChangeProposal(projectFullName, fileFullPath, currentChange.NewText);
                            changeProposal.StartLine = startLine;
                            changeProposal.StartColumn = startColumn;
                            changeProposal.EndLine = endLine;
                            changeProposal.EndColumn = endColumn;
                            changeProposal.Included = defaultIncluded;
                            changeProposals.Add(changeProposal);
                        }
                    }
    
                    if (result != VSConstants.S_OK)
                    {
                        throw new InvalidOperationException("Failed to convert offset");
                    }
                }
                return changeProposals;
            }
    
            /// <summary>
            /// Get IVsTextLines from a file.  If that file is in RDT, get text buffer from it.
            /// If the file is not in RDT, open that file in invisible editor and get text buffer
            /// from it.
            /// If failed to get text buffer, it will return null.        
            /// </summary>
            /// <param name="fullPathFileName">File name with full path.</param>
            /// <returns>Text buffer for that file.</returns>
            private static IVsTextLines GetTextLines(string fullPathFileName)
            {
                System.IServiceProvider serviceProvider = DataPackage.Instance;
                IVsTextLines textLines = null;
                IVsRunningDocumentTable rdt = (IVsRunningDocumentTable)serviceProvider.GetService(typeof(SVsRunningDocumentTable));
    
                if (rdt != null)
                {
                    IVsHierarchy ppHier = null;
                    uint pitemid, pdwCookie;
                    IntPtr ppunkDocData = IntPtr.Zero;
                    try
                    {
                        rdt.FindAndLockDocument((uint)(_VSRDTFLAGS.RDT_NoLock), fullPathFileName, out ppHier, out pitemid, out ppunkDocData, out pdwCookie);
                        if (pdwCookie != 0)
                        {
                            if (ppunkDocData != IntPtr.Zero)
                            {
                                try
                                {
                                    // Get text lines from the doc data
                                    IVsPersistDocData docData = (IVsPersistDocData)Marshal.GetObjectForIUnknown(ppunkDocData);
    
                                    if (docData is IVsTextLines)
                                    {
                                        textLines = (IVsTextLines)docData;
                                    }
                                    else
                                    {
                                        textLines = null;
                                    }
                                }
                                catch (ArgumentException)
                                {
                                    // Do nothing here, it will return null stream at the end.
                                }
                            }
                        }
                        else
                        {
                            // The file is not in RDT, open it in invisible editor and get the text lines from it.
                            IVsInvisibleEditor invisibleEditor = null;
                            TryGetTextLinesAndInvisibleEditor(fullPathFileName, out invisibleEditor, out textLines);
                        }
                    }
                    finally
                    {
                        if (ppunkDocData != IntPtr.Zero)
                            Marshal.Release(ppunkDocData);
                    }
                }
                return textLines;
            }
    
            /// <summary>
            /// Open the file in invisible editor in the running
            /// documents table (RDT), and get text buffer from that editor.
            /// </summary>
            /// <param name="fullPathFileName">File name with full path.</param>
            /// <param name="spEditor">The result invisible editor.</param>
            /// <param name="textLines">The result text buffer.</param>
            /// <returns>True, if the file is opened correctly in invisible editor.</returns>
            private static bool TryGetTextLinesAndInvisibleEditor(string fullPathFileName, out IVsInvisibleEditor spEditor, out IVsTextLines textLines)
            {
                System.IServiceProvider serviceProvider = DataPackage.Instance;
                spEditor = null;
                textLines = null;
    
                // Need to open this file.  Use the invisible editor manager to do so.
                IVsInvisibleEditorManager spIEM;
                IntPtr ppDocData = IntPtr.Zero;
                bool result;
    
                Guid IID_IVsTextLines = typeof(IVsTextLines).GUID;
    
                try
                {
                    spIEM = (IVsInvisibleEditorManager)serviceProvider.GetService(typeof(IVsInvisibleEditorManager));
                    spIEM.RegisterInvisibleEditor(fullPathFileName, null, (uint)_EDITORREGFLAGS.RIEF_ENABLECACHING, null, out spEditor);
                    if (spEditor != null)
                    {
                        int hr = spEditor.GetDocData(0, ref IID_IVsTextLines, out ppDocData);
                        if (hr == VSConstants.S_OK && ppDocData != IntPtr.Zero)
                        {
                            textLines = Marshal.GetTypedObjectForIUnknown(ppDocData, typeof(IVsTextLines)) as IVsTextLines;
                            result = true;
                        }
                        else
                        {
                            result = false;
                        }
                    }
                    else
                    {
                        result = false;
                    }
                }
                finally
                {
                    if (ppDocData != IntPtr.Zero)
                        Marshal.Release(ppDocData);
                }
                return result;
            }
        }
    }
    
  8. 按一下 [檔案] 功能表上的 [儲存 SampleHelper.cs]。

  9. 將名為 RawChangeInfo 的類別加入到專案。

  10. 在程式碼編輯器中,將程式碼更新為符合下列各行程式碼:

    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;
                }
            }
        }
    }
    
  11. 按一下 [檔案] 功能表上的 [儲存 RawChangeInfo.cs]。

    接下來,您將定義 RenameReferenceTextContributor 類別。

若要定義 RenameReferenceTextContributor 類別

  1. 將名為 RenameReferenceTextContributor 的類別加入到您的專案。

  2. 在程式碼編輯器中,將 using 陳述式更新為符合下列程式碼:

    using System;
    using System.Collections.Generic;
    using Microsoft.Data.Schema.Extensibility;
    using Microsoft.Data.Schema.Sql;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
    using Microsoft.VisualStudio.Data.Schema.Package.Sql.Refactoring;
    
  3. 將命名空間變更為 MySamples.Refactoring:

    namespace MySamples.Refactoring
    
  4. 將類別定義更新為符合下列各行程式碼:

        [DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))]
        internal class RenameReferenceTextContributor : RefactoringContributor<RenameReferenceContributorInput>
        {
        }
    

    指定屬性來宣告此參與者與任何衍生自 SqlDatabaseSchemaProvider 的「資料庫結構描述提供者」(Database Schema Provider) 相容。 您的類別必須繼承自 RenameReferenceContributorInputRefactoringContributor

  5. 定義其他常數和私用成員變數:

            #region const
            private const string TxtExtension = @".txt";
            private const string PreviewFriendlyName = @"Text Files";
            private const string PreviewDescription = @"Update text symbols in text files in the database project.";
            private const string PreviewWarningMessage = @"Updating text symbols in text files in the database project can cause inconsistency.";
    
            #endregion
    
            #region members
            private RefactoringPreviewGroup _textPreviewGroup;
            private List<String> _changingFiles;
            #endregion
    

    常數提供將出現在 [預覽視窗] 中的資訊。 其他成員則用來追蹤預覽群組和將變更的文字檔清單。

  6. 加入類別建構函式:

            #region ctor
            public RenameReferenceTextContributor()
            {
                _textPreviewGroup = new RefactoringPreviewGroup(PreviewFriendlyName);
                _textPreviewGroup.Description = PreviewDescription;
                _textPreviewGroup.WarningMessage = PreviewWarningMessage;
                _textPreviewGroup.EnableChangeGroupUncheck = true;
                _textPreviewGroup.EnableChangeUncheck = true;
                _textPreviewGroup.DefaultChecked = false; 
                _textPreviewGroup.IncludeInCurrentProject = true;
    
                // This sample uses the default icon for the file,
                // but you could provide your own icon here.
                //RefactoringPreviewGroup.RegisterIcon(TxtExtension, "textfile.ico");
            }
            #endregion
    

    建構函式會初始化模型項目,建立新的預覽群組並初始化其屬性。 您也可以在這裡註冊圖示,以顯示在 [預覽視窗] 中來代表特定副檔名。 由於文字檔沒有語法反白顯示,因此您無須為文字檔註冊語言服務。

  7. 覆寫 PreviewGroup 屬性,以傳回建立此參與者時所建立的群組:

            /// <summary>
            /// Preview group for text files
            /// </summary>
            public override RefactoringPreviewGroup PreviewGroup
            {
                get
                {
                    return _textPreviewGroup;
                }
                set
                {
                    _textPreviewGroup = 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(RenameReferenceContributorInput input)
            {
                RenameReferenceContributorInput referenceInput = input as RenameReferenceContributorInput;
                if (referenceInput == null)
                {
                    throw new ArgumentNullException("input");
                }
    
                string projectFullName;
                referenceInput.RefactoringOperation.CurrentProjectHierarchy.GetCanonicalName(VSConstants.VSITEMID_ROOT, out projectFullName);
    
                return GetChangesForAllTextFiles(referenceInput, 
                                                projectFullName, 
                                                _textPreviewGroup.DefaultChecked, 
                                                out _changingFiles);
            }
    

    這個方法會呼叫 GetAllChangesForAllTextFiles 方法來執行大部分的工作。

  9. 加入 GetChangesForAllTextFiles 方法來逐一查看專案中包含的文字檔清單、取得每個檔案的變更,以及將這些變更彙總至變更提議清單:

            /// <summary>
            /// Get all changes from all text files.
            /// </summary>
            private static Tuple<IList<ChangeProposal>, IList<ContributorInput>> GetChangesForAllTextFiles(
                RenameReferenceContributorInput input,
                string projectFullName,
                bool defaultChecked,
                out List<String> changingFiles)
            {
                if (input == null)
                {
                    throw new ArgumentNullException("input");
                }
    
                changingFiles = new List<String>();
                List<ChangeProposal> allChanges = new List<ChangeProposal>();
    
                List<string> files = new List<string>();
                files = SampleHelper.GetAllFilesInProject(input.RefactoringOperation.CurrentProjectHierarchy, TxtExtension, false);
    
                // process the text files one by one
                if (files != null && files.Count > 0)
                {
                    int fileCount = files.Count;
    
                    // Get all the changes for all txt files.
                    for (int fileIndex = 0; fileIndex < fileCount; fileIndex++)
                    {
                        IList<ChangeProposal> changes =
                            GetChangesForOneTextFile(
                                        input,
                                        projectFullName,
                                        files[fileIndex],
                                        defaultChecked);
                        if (changes != null && changes.Count > 0)
                        {
                            allChanges.AddRange(changes);
                            changingFiles.Add(files[fileIndex]);
                        }
                    }
                }
                return new Tuple<IList<ChangeProposal>, IList<ContributorInput>>(allChanges, null);
            }
    
  10. 實作 GetChangesForOneTextFileMethod 方法來傳回單一文字檔中包含的變更提議清單:

            /// <summary>
            /// Get all the change proposals from one text file.
            /// </summary>
            private static IList<ChangeProposal> GetChangesForOneTextFile(
                RenameReferenceContributorInput input,
                string projectFullName,
                string fileFullPath,
                bool defaultChecked)
            {
                const string separators = " \t \r \n \\()[]{}|.+-*/~!@#$%^&<>?:;";
    
                string fileContent = SampleHelper.ReadFileContent(fileFullPath);
    
                IList<ChangeProposal> changeProposals= null;
                if (string.IsNullOrEmpty(fileContent))
                {
                    // return empty change list
                    changeProposals = new List<ChangeProposal>();
                }
                else
                {
                    int startIndex = 0;
                    int maxIndex = fileContent.Length - 1;
                    string oldName = input.OldName;
                    int oldNameLength = oldName.Length;
                    List<RawChangeInfo> changes = new List<RawChangeInfo>();
                    while (startIndex < maxIndex)
                    {
                        // Text files do not understand schema object information
                        // We do just case-insensitive string match (without schema info)
                        // Only match whole word
                        int offset = fileContent.IndexOf(oldName, startIndex, StringComparison.OrdinalIgnoreCase);
    
                        // Cannot find match any more, stop the match
                        if (offset < 0)
                        {
                            break;
                        }
    
                        startIndex = offset + oldNameLength;
    
                        // match whole word: check before/after characters are separators
                        if (offset > 0)
                        {
                            char charBeforeMatch = fileContent[offset - 1];
                            // match starts in the middle of a token, discard and move on
                            if (!separators.Contains(charBeforeMatch.ToString()))
                            {
                                continue;
                            }
                        }
                        if (offset + oldNameLength < maxIndex)
                        {
                            char charAfterMatch = fileContent[offset + oldNameLength];
                            // match ends in the middle of a token, discard and move on
                            if (!separators.Contains(charAfterMatch.ToString()))
                            {
                                continue;
                            }
                        }
    
                        RawChangeInfo change = new RawChangeInfo(offset, oldNameLength, input.OldName, input.NewName);
                        changes.Add(change);
                    }
    
                    // convert index-based offsets to ChangeOffsets in ChangeProposals
                    changeProposals = SampleHelper.ConvertOffsets(
                        projectFullName,
                        fileFullPath,
                        changes,
                        defaultChecked);
                }
                return changeProposals;
            }
    

    由於重構目標不是 Transact-SQL 指令碼或結構描述物件,因此這個方法不會使用 Microsoft.Data.Schema.ScriptDomMicrosoft.Data.Schema.Sql.SchemaModel 的型別或方法。這個重構目標會為文字檔提供尋找和取代實作,因為文字檔中的符號沒有結構描述資訊可用。

  11. 按一下 [檔案] 功能表上的 [儲存 RenameTextContributor.cs]。

    接下來,您將設定和建置組件。

若要簽署和建置組件

  1. 按一下 [專案] 功能表上的 [RenameTextContributor 屬性]。

  2. 按一下 [簽署] 索引標籤。

  3. 按一下 [簽署組件]。

  4. 在 [選擇強式名稱金鑰檔] 中,按一下 [<新增>]。

  5. 在 [建立強式名稱金鑰] 對話方塊的 [金鑰檔名稱] 中,輸入 MyRefKey。

  6. (選擇性) 您可以為強式名稱金鑰檔指定密碼。

  7. 按一下 [確定]。

  8. 在 [檔案] 功能表上按一下 [全部儲存]。

  9. 在 [建置] 功能表上,按一下 [建置方案]。

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

若要安裝 RenameTextContributor 組件

  1. 在 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions 資料夾中建立名為 MyExtensions 的資料夾。

  2. 將已簽署的組件 (RenameTextContributor.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 中。

若要註冊 RenameTextContributor 組件

  1. 按一下 [檢視] 功能表上的 [其他視窗],然後按一下 [命令視窗] 開啟 [命令視窗]。

  2. 在 [命令] 視窗中輸入下列程式碼。 將 FilePath 替代為已編譯之 .dll 檔案的路徑和檔案名稱。 請在路徑和檔案名稱周圍加上引號。

    注意事項注意事項

    根據預設,已編譯之 .dll 檔案的路徑為 <您的方案路徑>\bin\Debug 或 <您的方案路徑>\bin\Release。

    ? System.Reflection.Assembly.LoadFrom("FilePath").FullName
    
    ? System.Reflection.Assembly.LoadFrom(@"FilePath").FullName
    
  3. 按 ENTER。

  4. 將產生的程式碼行複製到剪貼簿中。 此程式碼行應該與下列程式碼相似:

    "RenameTextContributor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nnnnnnnnnnnnnnnn"
    
  5. 開啟純文字編輯器,如 [記事本]。

  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.RenameReferenceTextContributor" 
    assembly="RenameTextContributor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<enter key here>" enabled="true" />
    </extensions>
    

    註冊繼承自 RefactoringContributor 的類別。

  7. 將此檔案另存為 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions 資料夾中的 RenameTextContributor.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. 按一下 [確定]。

    接下來,您會將文字檔加入至資料庫專案,其中包含 employee 資料表的參考。

若要加入包含資料表名稱的文字檔

  1. 在 [方案總管] 中,以滑鼠右鍵按一下資料庫專案節點,指向 [加入],然後按一下 [新增項目]。

  2. 在 [加入新項目] 對話方塊的 [類別] 清單中,按一下 [Visual Studio 樣板]。

  3. 在 [範本] 清單中,按一下 [文字檔]。

  4. 在 [名稱] 方塊中,輸入 SampleText1.txt。

  5. 在程式碼編輯器中加入下列文字:

    This is documentation for the employee table.
    Any changes made to the employee table name should also be reflected in this text file.
    
  6. 按一下 [檔案] 功能表上的 [儲存 SampleText1.txt]。

    接下來,您將使用新的重構類型來變更資料表名稱及其所有參考。

若要使用新的重構參與者更新資料表名稱

  1. 在 [結構描述檢視] 中,以滑鼠右鍵按一下 [employee] 資料表節點,指向 [重構],然後按一下 [重新命名]。

  2. 在 [重新命名] 對話方塊的 [新名稱] 中,輸入 [Person]。

  3. 在 [預覽變更] 對話方塊中,捲動變更群組直到看到 [文字檔] 群組。

    文字檔中的兩個 employee 執行個體將會列出。 您已定義類別來啟用此逐步解說中的新重構類型。 選取每項變更旁的核取方塊。

  4. 按一下 [套用]。

    資料表名稱會更新為 Person,包括在 [結構描述檢視] 以及在 SampleText1.txt 檔案的內容中。

後續步驟

您可以建立其他重構目標,或可以建立新的重構類型,以減少重複變更資料庫專案所需的人力。

請參閱

工作

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

概念

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

重構資料庫程式碼和資料