次の方法で共有


チュートリアル: データベースの名前変更リファクタリングを拡張してテキスト ファイルで動作させる

このトピックでは、名前変更リファクタリングの新しいコントリビューターを作成、インストール、登録、およびテストする方法を手順に沿って説明します。 このリファクタリング ターゲットによって Visual Studio Premium または Visual Studio Ultimate の機能が拡張され、データベース リファクタリングで、データベース プロジェクト内のテキスト ファイルに含まれているデータベース オブジェクトへの参照の名前を変更できるようになります。

新しいリファクタリングのコントリビューターを既存のリファクタリングの種類に追加する場合は、既存のコントリビューターの入力クラスを使用する必要があります。

このチュートリアルでは、次の作業について説明します。

  1. カスタム リファクタリング ターゲット用のクラスを含む新しいアセンブリを作成する。

  2. アセンブリをインストールおよび登録して、Visual Studio Premium または Visual Studio Ultimate でリファクタリング ターゲットを使用できるようにする。

  3. 単純なデータベース プロジェクトを作成して、リファクタリング ターゲットが期待どおりに動作することをテストする。

必須コンポーネント

このチュートリアルを実行するには、次のコンポーネントが必要です。

  • Visual Studio 2010 Premium または Visual Studio 2010 Ultimate がインストールされていること。

  • また、コンピューターに Visual Studio 2010 SDK をインストールしておく必要があります。 このキットをダウンロードするには、Microsoft Web サイトの「Visual Studio 2010 SDK (Visual Studio 2010 SDK)」のページを参照してください。

カスタム リファクタリング ターゲットを使用したアセンブリの作成

名前変更リファクタリングをテキスト ファイルで実行できるようにするカスタム リファクタリング ターゲットを作成するには、新しい RefactoringContributor を提供するクラスを 1 つ実装する必要があります。

  • RenameReferenceTextContributorContributor - このクラスは、名前が変更されたシンボルに対する変更提案の一覧を構築します。 変更提案は、データベース プロジェクト内のテキスト ファイルに含まれる参照ごとに作成されます。

このクラスを作成する前に、クラス ライブラリを作成し、必要な参照を追加し、いくつかのヘルパー コードを追加します。ヘルパー コードによって、このチュートリアルの後半で作成するコードの一部が単純化されます。

クラス ライブラリとヘルパー コードを作成するには

  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 をダブルクリックして、コード エディターで開きます。

    注意

    このヘルパー クラスは、カスタムのリファクタリングの種類に関するチュートリアルで使用されたヘルパー テキストと同じです。 そのプロジェクトから新しいプロジェクトにソース コードをコピーすると、時間を節約できます。

  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 から派生するどのデータベース スキーマ プロバイダーとも互換性があることを宣言します。 このクラスは、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.ScriptDom または Microsoft.Data.Schema.Sql.SchemaModel の型やメソッドは使用しません。テキスト ファイル内のシンボルには利用できるスキーマ情報がないため、このリファクタリング ターゲットでは、テキスト ファイルの検索と置換の実装を提供しています。

  11. [ファイル] メニューの [RenameTextContributor.cs の保存] をクリックします。

    次に、アセンブリを構成してビルドします。

アセンブリの署名とビルドを行うには

  1. [プロジェクト] メニューの [RenameTextContributor のプロパティ] をクリックします。

  2. [署名] タブをクリックします。

  3. [アセンブリの署名] をクリックします。

  4. [厳密な名前のキー ファイルを選択してください][<新規>] をクリックします。

  5. [厳密な名前キーの作成] ダイアログ ボックスで、[キー ファイル] に「MyRefKey」と入力します。

  6. (省略可能) 厳密な名前のキー ファイルにはパスワードを指定できます。

  7. [OK] をクリックします。

  8. [ファイル] メニューの [すべてを保存] をクリックします。

  9. [ビルド] メニューの [ソリューションのビルド] をクリックします。

    次に、アセンブリをインストールして登録し、使用できるテスト条件として表示されるようにします。

RenameTextContributor アセンブリをインストールするには

  1. MyExtensions という名前のフォルダーを %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions フォルダー内に作成します。

  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 に用意されている他のファイルを誤って変更することを防げます。

    次に、拡張機能の一種であるこのアセンブリを登録して、Visual Studio に表示されるようにする必要があります。

RenameTextContributor アセンブリを登録するには

  1. [表示] メニューの [その他のウィンドウ] をポイントし、[コマンド ウィンドウ] をクリックして、[コマンド] ウィンドウを開きます。

  2. [コマンド] ウィンドウに、次のコードを入力します。 FilePath をコンパイル済みの .dll ファイルのパスとファイル名に置き換えます。 パスとファイル名は引用符で囲みます。

    注意

    既定では、コンパイル済みの .dll ファイルのパスは YourSolutionPath\bin\Debug または YourSolutionPath\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. ファイルに RenameTextContributor.extensions.xml という名前を付けて %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions フォルダーに保存します。

  8. Visual Studio を閉じます。

    次に、ごく単純なデータベース プロジェクトを作成して、新しいリファクタリング タイプをテストします。

新しいリファクタリング コントリビューターのテスト

データベース プロジェクトを作成するには

  1. [ファイル] メニューの [新規作成] をポイントし、[プロジェクト] をクリックします。

  2. [インストールされたテンプレート] で、[データベース] ノードを展開し、[SQL Server] ノードをクリックします。

  3. テンプレートの一覧の [SQL Server 2008 データベース プロジェクト] をクリックします。

  4. [OK] をクリックして既定のプロジェクト名を受け入れ、プロジェクトを作成します。

    空のデータベース プロジェクトが作成されます。

主キーのあるテーブルを追加するには

  1. [表示] メニューの [スキーマ ビュー] をクリックします。

  2. スキーマ ビューで、[スキーマ] ノードを展開し、[dbo] ノードを展開して、[テーブル] ノードを右クリックします。次に、[追加] をポイントし、[テーブル] をクリックします。

  3. [新しい項目の追加] ダイアログ ボックスで、[名前] に「employee」と入力します。

    注意

    テーブル名の先頭の文字には、故意に小文字を使用しています。

  4. [OK] をクリックします。

  5. [テーブル] ノードを展開し、[employee] ノードを右クリックします。次に、[追加] をポイントし、[主キー] をクリックします。

  6. [新しい項目の追加] ダイアログ ボックスで、[名前] に「PK_Employee_column_1」と入力します。

  7. [OK] をクリックします。

    次に、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. [適用] をクリックします。

    スキーマ ビューと SampleText1.txt ファイルの内容の両方で、テーブル名が Person に更新されます。

次の手順

追加のリファクタリング ターゲットを作成したり、新しいリファクタリングの種類を作成したりすることで、データベース プロジェクトで何度も同じ変更を行う場合の作業を軽減できます。

参照

処理手順

チュートリアル: 新しいデータベース リファクタリングの種類の作成による大文字と小文字の変更

概念

カスタムのデータベース リファクタリングの種類またはターゲットの作成

データベースのコードとデータのリファクタリング