チュートリアル : データベースの名前の変更リファクタリングを拡張し、テキスト ファイルで実行する

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

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

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

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

  2. このアセンブリをインストールおよび登録し、Database Edition でリファクタリング ターゲットを使用できるようにする。

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

前提条件

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

  • Visual Studio Team System 2008 Database Edition の GDR (General Distribution Release) がインストールされている必要があります。

  • Microsoft Visual Studio 2008 SDK もインストールされている必要があります。 このキットのダウンロード方法については、Microsoft Web サイトの「Visual Studio 2008 SDK Version 1.0」を参照してください。

カスタム リファクタリング ターゲットを含むアセンブリの作成

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

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

このクラスを作成する前に、クラス ライブラリを作成し、必要な参照を追加し、このチュートリアルの後半で作成するコードの一部を単純化するためのヘルパー コードを追加します。

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

  1. 新しい C# クラス ライブラリ プロジェクトを作成し、RenameTextContributor.csproj という名前を付けます。

  2. 次の .NET アセンブリへの参照を追加します。

    • Microsoft.Data.Schema

    • Microsoft.Data.Schema.ScriptDom

    • Microsoft.Data.Schema.ScriptDom.sql

    • Microsoft.Data.Schema.Sql

    • Microsoft.VisualStudio.Data.Schema.Project

    • Microsoft.VisualStudio.Data.Schema.Project.Sql

  3. SDK の次のクラス ライブラリへの参照を追加します。

    • C:\Program Files\Microsoft Visual Studio 2008 SDK\VisualStudioIntegration\Common\Assemblies\Microsoft.VisualStudio.OLE.Interop.dll

    • C:\Program Files\Microsoft Visual Studio 2008 SDK\VisualStudioIntegration\Common\Assemblies\Microsoft.VisualStudio.Shell.9.0.dll

    • C:\Program Files\Microsoft Visual Studio 2008 SDK\VisualStudioIntegration\Common\Assemblies\Microsoft.VisualStudio.Shell.Interop.dll

    • C:\Program Files\Microsoft Visual Studio 2008 SDK\VisualStudioIntegration\Common\Assemblies\Microsoft.VisualStudio.Shell.Interop.8.0.dll

    • C:\Program Files\Microsoft Visual Studio 2008 SDK\VisualStudioIntegration\Common\Assemblies\Microsoft.VisualStudio.Shell.Interop.9.0.dll

    • C:\Program Files\Microsoft Visual Studio 2008 SDK\VisualStudioIntegration\Common\Assemblies\Microsoft.VisualStudio.TextManager.Interop.dll

  4. ソリューション エクスプローラで、Class1.cs の名前を SampleHelper.cs に変更します。

  5. SampleHelper.cs をダブルクリックしてコード エディタで開きます。

    Dd172123.alert_note(ja-jp,VS.90).gifメモ :

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

  6. コード エディタの内容を次のコードに置き換えます。

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Runtime.InteropServices;
    using Microsoft.Data.Schema.ScriptDom.Sql;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Data.Schema.Project.Common.UI;
    using Microsoft.VisualStudio.Data.Schema.Project.Refactoring;
    using Microsoft.VisualStudio.Shell.Interop;
    using Microsoft.VisualStudio.TextManager.Interop;
    using VsShell = Microsoft.VisualStudio.Shell.Interop;
    using VsTextMgr = Microsoft.VisualStudio.TextManager.Interop;
    using Microsoft.Data.Schema.SchemaModel;
    
    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 VsTextMgr.IVsTextLines GetTextLines(string fullPathFileName)
            {
                System.IServiceProvider serviceProvider = DataPackage.Instance;
                VsTextMgr.IVsTextLines textLines = null;
                VsShell.IVsRunningDocumentTable rdt = (VsShell.IVsRunningDocumentTable)serviceProvider.GetService(typeof(VsShell.SVsRunningDocumentTable));
    
                if (rdt != null)
                {
                    VsShell.IVsHierarchy ppHier = null;
                    uint pitemid, pdwCookie;
                    IntPtr ppunkDocData = IntPtr.Zero;
                    try
                    {
                        rdt.FindAndLockDocument((uint)(VsShell._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)
            {
                //ArgumentValidation.CheckForOutOfRangeException(startOffset, 0, int.MaxValue);
                //ArgumentValidation.CheckForOutOfRangeException(length, 0, int.MaxValue);
                //ArgumentValidation.CheckForNullReference(oldText, "oldText");
                //ArgumentValidation.CheckForNullReference(newText, "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. [ファイル] メニューの [SampleHelper.cs を保存] をクリックします。

    次に、RenameReferenceTextContributor クラスを定義します。

RenameReferenceTextContributor クラスを定義するには

  1. RenameReferenceTextContributor という名前のクラスをプロジェクトに追加します。

  2. コード エディタで、using ステートメントを次のように更新します。

    using System;
    using System.Collections.Generic;
    using Microsoft.Data.Schema.Common;
    using Microsoft.Data.Schema.Extensibility;
    using Microsoft.Data.Schema.Sql.SqlDsp;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Data.Schema.Project.Refactoring;
    using Microsoft.VisualStudio.Data.Schema.Project.Sql.Refactoring.Rename;
    
  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 メソッドをオーバーライドして、変更提案の一覧を返すようにします。

            /// <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. WriteOperationReferenceLogData メソッドをオーバーライドして、リファクタリング ログ ファイルに変更を記録するようにします。

            protected override void WriteOperationReferenceLogData(System.Xml.XmlWriter writer)
            {
                const string XmlElement_TextReferences = "TextReferences";
    
                writer.WriteStartElement(XmlElement_TextReferences);
                if (_changingFiles != null)
                {
                    foreach (String filename in _changingFiles)
                    {
                        writer.WriteStartElement("Reference");
                        writer.WriteAttributeString("TextFilename", filename);
                        writer.WriteEndElement();
                    }
                }
                writer.WriteEndElement();
            }
    
  10. 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);
            }
    
  11. GetChangesForOneTextFile メソッドを実装して、単一のテキスト ファイルに含まれている変更提案の一覧を返すようにします。

            /// <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 (T-SQL) スクリプトまたはスキーマ オブジェクトではないため、このメソッドでは Microsoft.Data.Schema.ScriptDom または Microsoft.Data.Schema.SchemaModel の型やメソッドを使用しません。 テキスト ファイル内のシンボルには利用可能なスキーマ情報がないため、このリファクタリング ターゲットでは、テキスト ファイルに対する検索と置換の実装を提供しています。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  2. 署名済みのアセンブリ (RenameTextContributor.dll) を [Program Files]\Microsoft Visual Studio 9.0\VSTSDB\Extensions\MyExtensions フォルダにコピーします。

    Dd172123.alert_note(ja-jp,VS.90).gifメモ :

    XML ファイルは [Program Files]\Microsoft Visual Studio 9.0\VSTSDB\Extensions フォルダに直接コピーしないことをお勧めします。 代わりにサブフォルダを使用することにより、Database Edition で提供される他のファイルを誤って変更することを防止できます。

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

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

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

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

    Dd172123.alert_note(ja-jp,VS.90).gifメモ :

    既定では、コンパイル済みの .dll ファイルのパスは YourSolutionPath\bin\Debug または YourSolutionPath\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. プレーンテキスト エディタ (メモ帳など) を開きます。

  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 9.0\VSTSDB\Extensions\MyExtensions フォルダに保存します。

  8. Visual Studio を閉じます。

    次に、非常に単純なデータベース プロジェクトを作成し、新しいリファクタリングの種類をテストします。

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

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

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

  2. [プロジェクトの種類] ボックスの一覧で、[データベース プロジェクト] ノードを展開し、[SQL Server 2005] をクリックします。

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

  4. [OK] をクリックし、既定のプロジェクト名をそのまま使用してプロジェクトを作成します。

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

主キーを含むテーブルを追加するには

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

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

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

    Dd172123.alert_note(ja-jp,VS.90).gifメモ :

    意図的に、小文字で始まるテーブル名を使用しています。

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

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

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

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

    次に、データベース プロジェクトに、従業員表への参照を含むテキスト ファイルを追加します。

テーブル名を含むテキスト ファイルを追加するには

  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. [変更のプレビュー] ダイアログ ボックスで、[テキスト ファイル] グループが見つかるまで変更グループをスクロールします。

    テキスト ファイル内の従業員の両方のインスタンスが表示されます。 このチュートリアルでは、この新しい種類のリファクタリングを実行できるようにするクラスを定義しました。 各変更の横にあるチェック ボックスをオンにします。

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

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

次の手順

追加のリファクタリング ターゲットを作成するか、または新しい種類のリファクタリングを作成して、データベース プロジェクトを繰り返し変更する際の作業を軽減します。

参照

処理手順

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

概念

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

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

Database Edition の用語の概要