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

このトピックの一連の操作手順では、新しい種類のデータベース リファクタリングを作成、インストール、登録、およびテストします。 このリファクタリング操作では、指定されたオブジェクトの名前の頭文字を大文字に変換し、変更された名前に対するすべての参照を更新します。

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

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

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

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

前提条件

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

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

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

カスタムのリファクタリングの種類を含むアセンブリの作成

オブジェクト名の頭文字を大文字に変換し、そのオブジェクトに対するすべての参照を更新するカスタムのリファクタリングの種類を作成するには、次の 6 つのクラスを実装する必要があります。

  • CasingRefactoringCommand - このクラスは、リファクタリング メニューのコマンド名を提供し、どのモデル要素に対して作成するリファクタリングの種類を使用できるようにするかを指定し、コマンドがクリックされたときにリファクタリング操作を呼び出します。

  • CasingRefactoringOperation - このクラスは、リファクタリング操作がどのようにプレビュー ウィンドウと対話するかを指定すると共に、操作を記述するプロパティを指定し、CasingContributorInput クラスを作成します。

  • CasingContributorInput - このクラスは、CasingSymbolContributor クラスへの入力データを格納します。

  • CasingSymbolContributor - このクラスは、名前変更されるシンボルに対する変更提案の一覧を作成すると共に、名前変更されるオブジェクトに対する参照の更新を処理する CasingReferenceContributorInput クラスを作成します。

  • CasingReferenceContributorInput - このクラスは、CasingReferenceContributor クラスへの入力データを格納します。

  • CasingReferenceContributor - このクラスは、名前変更されるシンボルに対する参照の更新に関連する変更提案の一覧を作成します。

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

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

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

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

    • Microsoft.Data.Schema.dll

    • Microsoft.Data.Schema.ScriptDom.dll

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

    • Microsoft.Data.Schema.Sql.dll

    • Microsoft.VisualStudio.Data.Schema.Project.dll

    • 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

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

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

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

    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. e.g. if recursion started with the Solution
            /// node, then : Level 0 -- Solution node, Level 1 -- children of Solution, etc.</param>
            /// <param name="hierIsSolution">true if hierarchy is Solution Node. </param>
            /// <param name="visibleNodesOnly">true if only nodes visible in the Solution Explorer should
            /// be traversed. false if all project items should be traversed.</param>
            private static void EnumProjectItems(IVsHierarchy hierarchy,
                                                string fileExtension,
                                                List<string> files,
                                                uint itemid,
                                                int recursionLevel,
                                                bool hierIsSolution,
                                                bool visibleNodesOnly)  
            {
                int hr;
                IntPtr nestedHierarchyObj;
                uint nestedItemId;
                Guid hierGuid = typeof(IVsHierarchy).GUID;
    
    
                // Check first if this node has a nested hierarchy. 
                hr = hierarchy.GetNestedHierarchy(itemid, ref hierGuid, out nestedHierarchyObj, out nestedItemId);
                if (VSConstants.S_OK == hr && IntPtr.Zero != nestedHierarchyObj)
                {
                    IVsHierarchy nestedHierarchy = Marshal.GetObjectForIUnknown(nestedHierarchyObj) as IVsHierarchy;
                    Marshal.Release(nestedHierarchyObj);    
                    if (nestedHierarchy != null)
                    {
                        EnumProjectItems(nestedHierarchy, fileExtension, files,
                                        nestedItemId,
                                        recursionLevel,
                                        false,
                                        visibleNodesOnly);
                    }
                }
                else
                {
                    // Check if the file extension of this node matches 
                    string fileFullPath;
                    hierarchy.GetCanonicalName(itemid, out fileFullPath);
                    if (CompareExtension(fileFullPath, fileExtension))
                    {
                        // add matched file paths into the list
                        files.Add(fileFullPath);
                    }
    
                    recursionLevel++;
    
                    //Get the first child node of the current hierarchy being walked
                    object pVar;
                    hr = hierarchy.GetProperty(itemid,
                        ((visibleNodesOnly || (hierIsSolution && recursionLevel == 1) ?
                            (int)__VSHPROPID.VSHPROPID_FirstVisibleChild : (int)__VSHPROPID.VSHPROPID_FirstChild)),
                        out pVar);
                    ErrorHandler.ThrowOnFailure(hr);
                    if (VSConstants.S_OK == hr)
                    {
                        // Use Depth first search so at each level we recurse to check if the node has any children
                        // and then look for siblings.
                        uint childId = GetItemId(pVar);
                        while (childId != VSConstants.VSITEMID_NIL)
                        {
                            EnumProjectItems(hierarchy, fileExtension, files, childId, recursionLevel, false, visibleNodesOnly);
                            hr = hierarchy.GetProperty(childId,
                                ((visibleNodesOnly || (hierIsSolution && recursionLevel == 1)) ?
                                    (int)__VSHPROPID.VSHPROPID_NextVisibleSibling : (int)__VSHPROPID.VSHPROPID_NextSibling),
                                out pVar);
                            if (VSConstants.S_OK == hr)
                            {
                                childId = GetItemId(pVar);
                            }
                            else
                            {
                                ErrorHandler.ThrowOnFailure(hr);
                                break;
                            }
                        }
                    }
                }
            }
    
            /// <summary>
            /// Gets the item id.
            /// </summary>
            /// <param name="pvar">VARIANT holding an itemid.</param>
            /// <returns>Item Id of the concerned node</returns>
            private static uint GetItemId(object pvar)
            {
                if (pvar == null) return VSConstants.VSITEMID_NIL;
                if (pvar is int) return (uint)(int)pvar;
                if (pvar is uint) return (uint)pvar;
                if (pvar is short) return (uint)(short)pvar;
                if (pvar is ushort) return (uint)(ushort)pvar;
                if (pvar is long) return (uint)(long)pvar;
                return VSConstants.VSITEMID_NIL;
            }
    
            /// <summary>
            /// Check if the file has the expected extension.
            /// </summary>
            /// <param name="filePath"></param>
            /// <param name="extension"></param>
            /// <returns></returns>
            public static bool CompareExtension(string filePath, string extension)
            {
                bool equals = false;
                if (!string.IsNullOrEmpty(filePath))
                {
                    equals = (string.Compare(System.IO.Path.GetExtension(filePath), extension, StringComparison.OrdinalIgnoreCase) == 0);
                }
                return equals;
            }
    
            /// <summary>
            /// Read file content from a file
            /// </summary>
            /// <param name="filePath"> file path </param>
            /// <returns> file content in a string </returns>
            internal static string ReadFileContent(string filePath)
            {
                //  Ensure that the file exists first.
                if (!File.Exists(filePath))
                {
                    Debug.WriteLine(string.Format("Cannot find the file: '{0}'", filePath));
                    return string.Empty;
                }
    
                string content;
                using (StreamReader reader = new StreamReader(filePath))
                {
                    content = reader.ReadToEnd();
                    reader.Close();
                }
                return content;
            }
    
            /// <summary>
            ///  Check null references and throw
            /// </summary>
            /// <param name="obj"></param>
            /// <param name="?"></param>
            public static void CheckNullArgument(object obj, string objectName)
            {
                if (obj == null)
                {
                    throw new System.ArgumentNullException(objectName);
                }
            }
    
            /// <summary>
            /// Get offset of the fragment from an Identifier if the identifier.value matches the
            /// name we are looking for.
            /// </summary>
            /// <param name="identifier"></param>
            /// <param name="expectedName"></param>
            public static RawChangeInfo AddOffsestFromIdentifier(
               Identifier identifier,
                String expectedName,
                String newName,
                Boolean keepOldQuote)
            {
                RawChangeInfo change = null;
                if (identifier != null && String.Compare(expectedName,identifier.Value,true) == 0)
                {
                    if (keepOldQuote)
                    {
                        QuoteType newQuote = QuoteType.NotQuoted;
                        newName = Identifier.DecodeIdentifier(newName, out newQuote);
                        newName = Identifier.EncodeIdentifier(newName, identifier.QuoteType);
                    }
                    change = new RawChangeInfo(identifier.StartOffset, identifier.FragmentLength, expectedName, newName);
                }
                return change;
            }
    
            public static IList<ChangeProposal> ConvertOffsets(
                string projectFullName,
                string fileFullPath,
                List<RawChangeInfo> changes,
                bool defaultIncluded)
            {
                // Get the file content into IVsTextLines
                IVsTextLines textLines = GetTextLines(fileFullPath);
    
                int changesCount = changes.Count;
                List<ChangeProposal> changeProposals = new List<ChangeProposal>(changesCount);
                for (int changeIndex = 0; changeIndex < changesCount; changeIndex++)
                {
                    int startLine = 0;
                    int startColumn = 0;
                    int endLine = 0;
                    int endColumn = 0;
    
    
                    RawChangeInfo currentChange = changes[changeIndex];
                    int startPosition = currentChange.StartOffset;
                    int endPosition = currentChange.StartOffset + currentChange.Length;
                    int result = textLines.GetLineIndexOfPosition(startPosition, out startLine, out startColumn);
                    if (result == VSConstants.S_OK)
                    {
                        result = textLines.GetLineIndexOfPosition(endPosition, out endLine, out endColumn);
                        if (result == VSConstants.S_OK)
                        {
                            TextChangeProposal changeProposal = new TextChangeProposal(projectFullName, fileFullPath, currentChange.NewText);
                            changeProposal.StartLine = startLine;
                            changeProposal.StartColumn = startColumn;
                            changeProposal.EndLine = endLine;
                            changeProposal.EndColumn = endColumn;
                            changeProposal.Included = defaultIncluded;
                            changeProposals.Add(changeProposal);
                        }
                    }
    
                    if (result != VSConstants.S_OK)
                    {
                        throw new InvalidOperationException("Failed to convert offset");
                    }
                }
                return changeProposals;
            }
    
            /// <summary>
            /// Get IVsTextLines from a file.  If that file is in RDT, get text buffer from it.
            /// If the file is not in RDT, open that file in invisible editor and get text buffer
            /// from it.
            /// If failed to get text buffer, it will return null.        
            /// </summary>
            /// <param name="fullPathFileName">File name with full path.</param>
            /// <returns>Text buffer for that file.</returns>
            private static 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;
            }
        }
    }
    
  6. [ファイル] メニューの [SampleHelper.cs の保存] をクリックします。

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

  8. コード エディタで、コードを次のように更新します。

    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;
                }
            }
        }
      }
    
  9. [ファイル] メニューの [SampleHelper.cs の保存] をクリックします。

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

CasingRefactoringCommand クラスを定義するには

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

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

    using Microsoft.Data.Schema.Extensibility;
    using Microsoft.Data.Schema.SchemaModel;
    using Microsoft.Data.Schema.SchemaModel.Abstract;
    using Microsoft.Data.Schema.Sql.SchemaModel.SqlServer;
    using Microsoft.Data.Schema.Sql.SqlDsp;
    using Microsoft.VisualStudio.Data.Schema.Project.Refactoring;
    using Microsoft.VisualStudio.Shell.Interop;
    
  3. 名前空間を MySamples.Refactoring に変更します。

    namespace MySamples.Refactoring
    
  4. クラス定義を次のように更新します。

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

    この属性は、このリファクタリングの種類と互換性があるデータベース スキーマ プロバイダを指定するために使用します。 この例では、新しいリファクタリングの種類が SqlDatabaseSchemaProvider から派生した任意のプロバイダと互換性を持つようにします。 作成するクラスは、RefactoringSchemaViewNodeCommand を継承します。 この基本クラスを継承することにより、作成するリファクタリングの種類は、スキーマ ビューで指定されたノードに対して使用できるようになります。 ファイル ノードやプロジェクト ノードを操作対象にする場合は、これ以外の種類のリファクタリングを定義できます。

  5. 次に、次のオーバーライド メソッドをクラスに追加します。

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

    このメソッドにより、スキーマ ビューでリファクタリング コマンドが適用されたときの動作が定義されます。

  6. 次に、次のオーバーライド メソッドをクラスに追加します。

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

    このメソッドは、スキーマ ビューのどのノードに対してリファクタリング コマンドを使用できるようにするかを決定します。

  7. 最後に、次のオーバーライド メソッドをクラスに追加します。

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

    このメソッドは、リファクタリング メニューに表示されるリファクタリング コマンドのわかりやすい名前を定義します。

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

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

CasingRefactoringOperation クラスを定義するには

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

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

    using System;
    using System.Diagnostics;
    using System.Globalization;
    using Microsoft.Data.Schema.SchemaModel;
    using Microsoft.Data.Schema.Sql.SchemaModel.SqlServer;
    using Microsoft.VisualStudio.Data.Schema.Project.Refactoring;
    using Microsoft.VisualStudio.Shell.Interop;
    
  3. 名前空間を MySamples.Refactoring に変更します。

    namespace MySamples.Refactoring
    
  4. クラス定義を次のように更新します。

        internal class CasingRefactorOperation : RefactoringOperation
        {
    }
    

    作成するクラスは、RefactoringOperation を継承する必要があります。

  5. 次の定数とメンバ変数の宣言をクラスに追加します。

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

    これらのプライベート定数は、プレビュー ウィンドウに表示されるこの操作の情報を定義します。

  6. クラスのコンストラクタを追加します。

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

    このコンストラクタは、操作名とモデル要素が指定されている場合に、これらを初期化します。

  7. PreviewWindowInfo プロパティをオーバーライドして、作成するリファクタリングの種類が適用されたときにプレビュー ウィンドウに表示される値を取得します。

            /// <summary>
            /// Preview dialog information for this RenameRefactorOperation.
            /// </summary>
            protected override PreviewWindowInfo PreviewWindowInfo
            {
                get
                {
                    if (_previewWindowInfo == null)
                    {
                        _previewWindowInfo = new PreviewWindowInfo();
                        _previewWindowInfo.ConfirmButtonText = ConfirmButtonText;
                        _previewWindowInfo.Description = OperationDescription;
                        _previewWindowInfo.HelpContext = String.Empty;
                        _previewWindowInfo.TextViewDescription = OperationTextViewDescription;
                        _previewWindowInfo.Title = string.Format(CultureInfo.CurrentCulture,PreviewDialogTitle, CasingRefactorOperationName);
                    }
                    return _previewWindowInfo;
                }
            }
    
  8. その他のプロパティの定義を追加します。

            protected override string OperationName
            {
                get
                {
                    return _operationName;
                }
            }
    
            /// <summary>
            /// Undo Description used in undo stack
            /// </summary>
            protected override string UndoDescription
            {
                get
                {
                    return string.Format(CultureInfo.CurrentCulture,
                        CasingUndoDescription, SampleHelper.GetModelElementName(this.ModelElement));
                }
            }
    
            /// <summary>
            /// SchemaIdentifier of currently selected schema object
            /// </summary>
            public ISqlModelElement ModelElement
            {
                get
                {
                    return _modelElement;
                }
                set
                {
                    _modelElement = value;
                }
            }
    
  9. 最後に、OnGetContributorInput メソッドをオーバーライドします。

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

    このメソッドは、このリファクタリングの種類のリファクタリング コントリビュータに渡される ContributorInput を作成します。

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

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

CasingContributorInput クラスを定義するには

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

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

    using System;
    using Microsoft.Data.Schema.Sql.SchemaModel.SqlServer;
    using Microsoft.VisualStudio.Data.Schema.Project.Refactoring;
    
  3. 名前空間を MySamples.Refactoring に変更します。

    namespace MySamples.Refactoring
    
  4. クラス定義を次のように更新します。

        internal class CasingContributorInput: ContributorInput
        {
        }
    

    作成するクラスは、ContributorInput を継承する必要があります。

  5. 次に示す追加のプライベート メンバ変数を定義します。

            private ISqlModelElement _modelElement;
    

    このメンバは、操作対象のモデル要素を追跡するために使用されます。

  6. クラス コンストラクタを追加します。

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

    このコンストラクタは、モデル要素を初期化します。

  7. モデル要素を表す読み取り専用のパブリック プロパティを追加します。

            /// <summary>
            /// Selected model element
            /// </summary>
            public ISqlModelElement ModelElement
            {
                get 
    
                {
                    return _modelElement;
                }
            }
    
  8. 次に示す Override Equals メソッドは、2 つの CasingContributorInput オブジェクトが同じであるかどうかを判断するための比較を実行します。

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

    このコントリビュータでは、入力が同じモデル要素に該当する場合は、同じであると判断されます。

  9. GetHashCode メソッドをオーバーライドします。

            /// <summary>
            /// Override GetHashCode
            /// </summary>
            /// <returns></returns>
            public override int GetHashCode()
            {
                Int32 hash = _modelElement.GetHashCode();
                return hash;
            }
    
  10. [ファイル] メニューの [CasingContributorInput.cs の保存] をクリックします。

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

CasingSymbolContributor クラスを定義するには

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

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

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Globalization;
    using Microsoft.Data.Schema.Common;
    using Microsoft.Data.Schema.Extensibility;
    using Microsoft.Data.Schema.SchemaModel;
    using Microsoft.Data.Schema.ScriptDom.Sql;
    using Microsoft.Data.Schema.Sql.SchemaModel.SqlServer;
    using Microsoft.Data.Schema.Sql.SqlDsp;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Data.Schema.Project.Refactoring;
    
  3. 名前空間を MySamples.Refactoring に変更します。

    namespace MySamples.Refactoring
    
  4. クラス定義を次のように更新します。

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

    この属性は、このコントリビュータが SqlDatabaseSchemaProvider から派生したすべてのデータベース スキーマ プロバイダと互換性があることを宣言するために指定します。 作成するクラスは、CasingContributorInput クラスの RefactoringContributor を継承する必要があります。

  5. 追加の定数とプライベート メンバ変数を定義します。

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

    この定数は、プレビュー ウィンドウに表示される情報を提供します。 追加のメンバは、プレビュー グループを追跡するために使用します。

  6. クラス コンストラクタを追加します。

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

    このコンストラクタは、モデル要素を初期化して、新しいプレビュー グループを作成し、そのプロパティを初期化します。 ここでは、プレビュー ウィンドウで特定のファイル名拡張子に対して表示されるアイコンを登録できるほか、指定された拡張子を持つファイルの色指定を提供するために使用される言語サービスを登録できます。

  7. PreviewGroup プロパティをオーバーライドして、このコントリビュータの作成時に作成されたグループを返すようにします。

            #region overrides
            /// <summary>
            /// Preview group for schema object files
            /// </summary>
            public override RefactoringPreviewGroup PreviewGroup
            {
                get
                {
                    return _previewGroup;
                }
                set
                {
                    _previewGroup = value;
                }
            }
    
  8. ContributeChanges メソッドをオーバーライドして、変更提案の一覧を返すようにします。

            /// <summary>
            /// Contribute to the change proposals
            /// </summary>
            /// <param name="input">contributor input</param>
            /// <returns>List of change proposals with corresponding contributor inputs</returns>
            protected override Tuple<IList<ChangeProposal>, IList<ContributorInput>> ContributeChanges(CasingContributorInput input)
            {
                CasingContributorInput casingInput = input as CasingContributorInput;
                if (casingInput == null)
                {
                    throw new ArgumentNullException("input");
                }
    
                string projectFullName;
                casingInput.RefactoringOperation.CurrentProjectHierarchy.GetCanonicalName(VSConstants.VSITEMID_ROOT, out projectFullName);
    
                Tuple<IList<ChangeProposal>, IList<ContributorInput>> changes = GetChangesFromCurrentSymbolScript(
                        projectFullName,
                        casingInput,
                        casingInput.ModelElement,
                        true);
    
                return changes;
            }
    
            #endregion
    
  9. 次に、別の種類の ContributorInput を作成します。

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

    この別の種類の ContributorInput は、シンボルが更新された要素に対するすべての参照を処理するために使用されます。 このメソッドは、次のメソッドによって呼び出されます。

  10. リファクタリング対象のシンボルの定義を含むスクリプトに対する変更提案の一覧を作成するメソッドを追加します。

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

    このメソッドは、スクリプト要素を処理するための AnalyzeScript メソッドを呼び出します。

  11. AnalyzeScript メソッドを追加します。

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

    このメソッドは、リファクタリング対象要素のソース スクリプトを取得し、 そのソース スクリプトの SQL フラグメントを取得します。 その後、新しい変更リストを作成し、フラグメントの種類に基づいて要素の識別子を決定し、新しい変更を変更リストに追加します。

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

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

CasingReferenceContributorInput クラスを定義するには

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

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

    using System;
    using Microsoft.Data.Schema.Sql.SchemaModel.SqlServer;
    using Microsoft.VisualStudio.Data.Schema.Project.Refactoring;
    
  3. 名前空間を MySamples.Refactoring に変更します。

    namespace MySamples.Refactoring
    
  4. クラス定義を次のように更新します。

        internal class CasingReferenceContributorInput: ContributorInput
        {        
        }        
    

    作成するクラスは、ContributorInput を継承する必要があります。

  5. 追加のプライベート メンバ変数を定義します。

            private ISqlModelElement _modelElement;
            private RefactoringPreviewGroup _previewGroup;
    

    これらのメンバは、操作対象のモデル要素と、変更の所属先のプレビュー グループを追跡するために使用されます。

  6. クラス コンストラクタを追加します。

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

    このコンストラクタは、モデル要素を初期化します。

  7. モデル要素を表す読み取り専用のパブリック プロパティを追加します。

            /// <summary>
            /// Selected model element
            /// </summary>
            public ISqlModelElement ModelElement
            {
                get
                {
                    return _modelElement;
                }
            }
    
  8. このコントリビュータによって特定される変更用の追加のプレビュー グループを定義します。

            /// <summary>
            /// Preview group that change proposals belong to
            /// </summary>
            public RefactoringPreviewGroup SchemaObjectsPreviewGroup
            {
                get
                {
                    return _previewGroup;
                }
                set 
                { 
                    _previewGroup = value; 
                }
            }
    
  9. 次に示す Override Equals メソッドは、2 つの CasingReferenceContributorInput オブジェクトが同じであるかどうかを判断するための比較を実行します。

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

    このコントリビュータでは、入力が同じモデル要素に該当する場合は、同じであると判断されます。

  10. GetHashCode メソッドをオーバーライドします。

            /// <summary>
            /// Override GetHashCode
            /// </summary>
            /// <returns></returns>
            public override int GetHashCode()
            {
                Int32 hash = _modelElement.GetHashCode();
                return hash;
            }
    
  11. [ファイル] メニューの [CasingReferenceContributorInput.cs の保存] をクリックします。

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

CasingReferenceContributor クラスを定義するには

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

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

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using Microsoft.Data.Schema.Common;
    using Microsoft.Data.Schema.Extensibility;
    using Microsoft.Data.Schema.SchemaModel;
    using Microsoft.Data.Schema.ScriptDom.Sql;
    using Microsoft.Data.Schema.Sql.SchemaModel.SqlServer;
    using Microsoft.Data.Schema.Sql.SqlDsp;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Data.Schema.Project.Refactoring;
    
  3. 名前空間を MySamples.Refactoring に変更します。

    namespace MySamples.Refactoring
    
  4. クラス定義を次のように更新します。

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

    この属性は、このコントリビュータが SqlDatabaseSchemaProvider から派生したすべてのデータベース スキーマ プロバイダと互換性があることを宣言するために指定します。 作成するクラスは、CasingReferenceContributorInput クラスの RefactoringContributor を継承する必要があります。

  5. 追加の定数とプライベート メンバ変数を定義します。

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

    この定数は、プレビュー ウィンドウに表示される情報を提供します。 追加のメンバは、プレビュー グループを追跡するために使用します。

  6. クラス コンストラクタを追加します。

            public CasingReferenceContributor()
            {
            }
    
  7. PreviewGroup プロパティをオーバーライドして、このコントリビュータの作成時に作成されたグループを返すようにします。

            #region overrides
            /// <summary>
            /// Preview group for text files
            /// </summary>
            public override RefactoringPreviewGroup PreviewGroup
            {
                get
                {
                    return _previewGroup;
                }
                set
                {
                    _previewGroup = value;
                }
            }
            #endregion
    
  8. ContributeChanges メソッドをオーバーライドして、変更提案の一覧を返すようにします。

            /// <summary>
            /// Contribute to the change proposals
            /// </summary>
            /// <param name="input">contributor input</param>
            /// <returns>List of change proposals with corresponding contributor inputs</returns>
            protected override Tuple<IList<ChangeProposal>, IList<ContributorInput>> ContributeChanges(CasingReferenceContributorInput input)
            {
                // cast input into reference input
                CasingReferenceContributorInput casingReferenceInput = input as CasingReferenceContributorInput;
                if (casingReferenceInput == null)
                {
                    throw new ArgumentNullException("input");
                }
    
                // Make sure CasingReferenceContributor and CasingSymbolContributor for a same refactoring operation
                // share the same preview group instance.
                if (casingReferenceInput.SchemaObjectsPreviewGroup != null)
                {
                    _previewGroup = casingReferenceInput.SchemaObjectsPreviewGroup;
                }
    
                string projectFullName;
                casingReferenceInput.RefactoringOperation.CurrentProjectHierarchy.GetCanonicalName(VSConstants.VSITEMID_ROOT, out projectFullName);
    
                Tuple<IList<ChangeProposal>, IList<ContributorInput>> changes = GetChangesFromReferencedSymbolScripts(
                        projectFullName,
                        casingReferenceInput,
                        casingReferenceInput.ModelElement,
                        true
                   );
    
                return changes;
            }
    

    ContributeChangesMethod は、GetChangesFromReferencedSymbolScripts メソッドを呼び出します。

  9. GetChangesFromReferencedSymbolScripts メソッドを実装して、更新されるシンボルへの参照を含むスクリプトの変更提案の一覧を返すようにします。

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

    このメソッドは、更新されたシンボルのすべての依存関係の一覧を取得します。 その後、各参照に対して AnalyzeRelationshipEntrySource メソッドを呼び出して、必要な追加の変更を特定します。

  10. AnalyzeRelationshipEntrySource メソッドを追加します。

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

    このメソッドは、更新されたシンボルに依存するスクリプト フラグメントに対して適用する必要のある変更の一覧を取得します。

  11. GetDependentEntries メソッドを追加します。

            /// <summary>
            ///  Get all relating relationship entries for the model element and its composing and hierarchical children
            /// </summary>
            internal static List<RelationshipEntrySource> GetDependentEntries(
                SqlSchemaModel dataSchemaModel,
                ISqlModelElement modelElement,
                bool ignoreComposedRelationship,
                bool includeChildDependencies)
            {
                SampleHelper.CheckNullArgument(dataSchemaModel, "dataSchemaModel");
                SampleHelper.CheckNullArgument(modelElement, "modelElement");
    
                List<RelationshipEntrySource> dependencies = new List<RelationshipEntrySource>();
    
                List<IModelRelationshipEntry> relatingRelationships = new List<IModelRelationshipEntry>();
                GetDependentEntries(modelElement,
                                    dataSchemaModel,
                                    new Dictionary<IModelElement, Object>(),
                                    relatingRelationships,
                                    includeChildDependencies);
    
                foreach (IModelRelationshipEntry entry in relatingRelationships)
                {
                    ModelRelationshipType relationshipType = entry.RelationshipClass.ModelRelationshipType;
                    if (!ignoreComposedRelationship ||
                        (relationshipType != ModelRelationshipType.Composing &&
                         relationshipType != ModelRelationshipType.ComposingRelationshipSurrogate))
                    {
                        ISqlModelElement relatingElement = entry.RelatingElement as ISqlModelElement;
                        Debug.Assert(relatingElement != null, "Relating element got from ModelStore is null.");
    
                        IList<RelationshipEntrySource> relationshipEntrySources = relatingElement.GetRelationshipEntrySources(entry);
                        if (relationshipEntrySources != null)
                        {
                            dependencies.AddRange(relationshipEntrySources);
                        }
                    }
                }
                return dependencies;
            }
    
            private static void GetDependentEntries(
                IModelElement modelElement,
                DataSchemaModel dataSchemaModel,
                Dictionary<IModelElement, Object> visitElement,
                List<IModelRelationshipEntry> relationshipEntries,
                Boolean includeChildDependencies)
            {
                if (modelElement != null &&
                    !visitElement.ContainsKey(modelElement))
                {
                    visitElement[modelElement] = null;
    
                    IList<IModelRelationshipEntry> relatingRelationships = modelElement.GetRelatingRelationshipEntries();
                    relationshipEntries.AddRange(relatingRelationships);
    
                    if (includeChildDependencies)
                    {
                        // First loop through all composed children of this element, and get their relationship entries as well
                        foreach (IModelRelationshipEntry entry in modelElement.GetRelatedRelationshipEntries())
                        {
                            if (entry.RelationshipClass.ModelRelationshipType == ModelRelationshipType.Composing)
                            {
                                GetDependentEntries(entry.Element, dataSchemaModel, visitElement, relationshipEntries, includeChildDependencies);
                            }
                        }
    
                        // Then loop through all hierarchical children of this element, add their dependents to the list.
                        foreach (IModelRelationshipEntry entry in relatingRelationships)
                        {
                            if (entry.RelationshipClass.ModelRelationshipType == ModelRelationshipType.Hierarchical)
                            {
                                GetDependentEntries(entry.RelatingElement, dataSchemaModel, visitElement, relationshipEntries, includeChildDependencies);
                            }
                        }
                    }
                }
            }
    
  12. [ファイル] メニューの [CasingReferenceContributor.cs の保存] をクリックします。

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

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

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

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

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

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

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

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

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

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

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

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

アセンブリのインストールと登録

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

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

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

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

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

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

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

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

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

    Dd193265.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. プレーンテキスト エディタ (メモ帳など) を開きます。

    Dd193265.alert_caution(ja-jp,VS.90).gif重要 :

    Windows Vista および Microsoft Windows Server 2008 上では、ファイルを Program Files フォルダに保存できるように、管理者としてエディタを開きます。

  6. 次の情報を入力し、独自のアセンブリ名、公開キー トークン、および拡張機能の種類を指定します。

    <?xml version="1.0" encoding="utf-8" ?> 
    <extensions assembly="" version="1" xmlns="urn:Microsoft.Data.Schema.Extensions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:Microsoft.Data.Schema.Extensions Microsoft.Data.Schema.Extensions.xsd">
      <extension type="MySamples.Refactoring.CasingRefactorCommand" 
    assembly=" CasingRefactoringType, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<enter key here>" enabled="true" />
      <extension type="MySamples.Refactoring.CasingSymbolContributor" 
            assembly=" CasingRefactoringType, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<enter key here>" enabled="true" />
      <extension type="MySamples.Refactoring.CasingReferenceContributor" 
            assembly=" CasingRefactoringType, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<enter key here>" enabled="true" />
    </extensions>
    

    この XML ファイルを使用して、RefactoringCommand の派生クラスと、RefactoringContributor から派生したすべての関連クラスを登録します。

  7. このファイルに CasingRefactoringType.extensions.xml という名前を付けて、[Program Files]\Microsoft Visual Studio 9.0\VSTSDB\Extensions\MyExtensions フォルダに保存します。

  8. Visual Studio を閉じます。

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

新しいリファクタリングの種類のテスト

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    次に、新しいリファクタリングの種類を使用して、テーブル名とそれに対するすべての参照を変更します。

新しいリファクタリングの種類を使用してテーブル名を更新するには

  1. スキーマ ビューで、[employee] テーブル ノードを右クリックし、[リファクタ] をポイントし、[Make First Letter Uppercase] をクリックします。

    このチュートリアルでは、この新しい種類のリファクタリングを定義しました。

  2. [変更のプレビュー] ダイアログ ボックスで変更を確認し、[適用] をクリックします。

    テーブル名が Employee に更新されます。 主キー内のこのテーブルへの参照も更新されます。

次の手順

これ以外にも、独自の種類のデータベース リファクタリングを作成できます。 また、既存の種類のデータベース リファクタリングが別の種類のファイルやオブジェクトに対して機能するよう、さらにコントリビュータを追加することもできます。

参照

処理手順

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

概念

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

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

Database Edition の用語の概要