チュートリアル: 新しいデータベース リファクタリングの種類の作成による大文字と小文字の変更
このトピックでは、新しいデータベース リファクタリングの種類を作成、インストール、登録、およびテストする方法を手順に沿って説明します。 このリファクタリング操作によって、指定したオブジェクトの名前の先頭文字が大文字に変換され、その名前へのすべての参照が更新されます。
このチュートリアルでは、次の作業について説明します。
カスタム リファクタリングの種類のクラスを含む新しいアセンブリを作成する。
アセンブリをインストールおよび登録して、そのリファクタリングの種類を Visual Studio Premium または Visual Studio Ultimate で使用できるようにする。
単純なデータベース プロジェクトを作成して、リファクタリングの種類が期待どおりに動作することをテストする。
必須コンポーネント
このチュートリアルを実行するには、次のコンポーネントが必要です。
Visual Studio 2010 Premium または Visual Studio 2010 Ultimate がインストールされていること。
コンピューターに Visual Studio 2010 ソフトウェア開発キット (SDK) がインストールされていることも必要です。 このキットをダウンロードするには、Microsoft Web サイトの Visual Studio 2010 SDK のページを参照してください。
カスタム リファクタリングの種類を使用したアセンブリの作成
オブジェクト名の先頭文字を大文字に変換し、そのオブジェクトへのすべての参照を更新するカスタム リファクタリングの種類を作成するには、次の 6 つのクラスを実装する必要があります。
CasingRefactoringCommand — このクラスは、リファクタリング メニューにコマンド名を追加し、リファクタリングの種類を使用できるモデル要素を指定し、ユーザーがそのコマンドをクリックしたときにリファクタリング操作を呼び出します。
CasingRefactoringOperation — このクラスは、リファクタリング操作がどのようにプレビュー ウィンドウと対話するかを指定し、操作を記述するプロパティを指定し、CasingContributorInput を作成します。
CasingContributorInput — このクラスは、CasingSymbolContributor クラスに入力データを格納します。
CasingSymbolContributor — このクラスは、名前が変更されたシンボルに対する変更提案の一覧を構築し、名前が変更されたオブジェクトへの参照の更新を処理する CasingReferenceContributorInput クラスを作成します。
CasingReferenceContributorInput — このクラスは、CasingReferenceContributor クラスに入力データを格納します。
CasingReferenceContributor — このクラスは、名前が変更されたシンボルへの参照の更新に関連する変更提案の一覧を構築します。
これらのクラスを作成する前に、クラス ライブラリを作成し、必要な参照を追加し、(このチュートリアルでこの後作成する) 一部のコードを簡略化するいくつかのヘルパー コードを追加します。
クラス ライブラリとヘルパー コードを作成するには
新しい C# クラス ライブラリ プロジェクトを作成し、CasingRefactoringType.csproj という名前を付けます。
次のクラス ライブラリへの参照を追加します。
Microsoft.Data.Schema.dll
Microsoft.Data.Schema.ScriptDom.dll
Microsoft.Data.Schema.ScriptDom.sql.dll
Microsoft.Data.Schema.Sql.dll
Microsoft.VisualStudio.Data.Schema.Package.dll
Microsoft.VisualStudio.Data.Schema.Package.Sql.dll
Visual Studio 2010 ソフトウェア開発キット (SDK) の次のアセンブリへの参照を追加します。
Microsoft.VisualStudio.OLE.Interop.dll
Microsoft.VisualStudio.Shell.10.0.dll
Microsoft.VisualStudio.Shell.Interop.dll
Microsoft.VisualStudio.Shell.Interop.8.0.dll
Microsoft.VisualStudio.Shell.Interop.9.0.dll
Microsoft.VisualStudio.Shell.Interop.10.0.dll
Microsoft.VisualStudio.TextManager.Interop.dll
ソリューション エクスプローラーで、Class1.cs の名前を SampleHelper.cs に変更します。
SampleHelper.cs をダブルクリックして、コード エディターで開きます。
コード エディターの内容を次のコードで置き換えます。
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using Microsoft.Data.Schema.SchemaModel; using Microsoft.Data.Schema.ScriptDom.Sql; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Data.Schema.Package.Refactoring; using Microsoft.VisualStudio.Data.Schema.Package.UI; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TextManager.Interop; namespace MySamples.Refactoring { internal static class SampleHelper { public static String GetModelElementName(IModelElement modelElement) { SampleHelper.CheckNullArgument(modelElement, "modelElement"); return modelElement.ToString(); } /// <summary> /// Given a model element, returns its simple name. /// </summary> public static String GetModelElementSimpleName(IModelElement modelElement) { String separator = "."; String simpleName = String.Empty; String fullName = modelElement.ToString(); String[] nameParts = fullName.Split(separator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); if (nameParts.Length > 0) { simpleName = nameParts[nameParts.Length - 1]; // last part } if (simpleName.StartsWith("[") && simpleName.EndsWith("]")) { simpleName = simpleName.Substring(1, simpleName.Length - 2); } return simpleName; } /// <summary> /// Find all files in the project with the specified file extension /// </summary> public static List<string> GetAllFilesInProject(IVsHierarchy solutionNode, string fileExtension, bool visibleNodesOnly) { List<string> files = new List<string>(); if (null != solutionNode) { EnumProjectItems(solutionNode, fileExtension, files, VSConstants.VSITEMID_ROOT, // item id of solution root node 0, // recursion from solution node true, // hierarchy is Solution node visibleNodesOnly); // visibleNodesOnly } return files; } /// <summary> /// Enumerates recursively over the hierarchy items. /// </summary> /// <param name="hierarchy">hierarchy to enmerate over.</param> /// <param name="fileExtension">type of files we need to collect from the project</param> /// <param name="files">list of file paths</param> /// <param name="itemid">item id of the hierarchy</param> /// <param name="recursionLevel">Depth of recursion. e.g. if recursion started with the Solution /// node, then : Level 0 -- Solution node, Level 1 -- children of Solution, etc.</param> /// <param name="hierIsSolution">true if hierarchy is Solution Node. </param> /// <param name="visibleNodesOnly">true if only nodes visible in the Solution Explorer should /// be traversed. false if all project items should be traversed.</param> private static void EnumProjectItems(IVsHierarchy hierarchy, string fileExtension, List<string> files, uint itemid, int recursionLevel, bool hierIsSolution, bool visibleNodesOnly) { int hr; IntPtr nestedHierarchyObj; uint nestedItemId; Guid hierGuid = typeof(IVsHierarchy).GUID; // Check first if this node has a nested hierarchy. hr = hierarchy.GetNestedHierarchy(itemid, ref hierGuid, out nestedHierarchyObj, out nestedItemId); if (VSConstants.S_OK == hr && IntPtr.Zero != nestedHierarchyObj) { IVsHierarchy nestedHierarchy = Marshal.GetObjectForIUnknown(nestedHierarchyObj) as IVsHierarchy; Marshal.Release(nestedHierarchyObj); if (nestedHierarchy != null) { EnumProjectItems(nestedHierarchy, fileExtension, files, nestedItemId, recursionLevel, false, visibleNodesOnly); } } else { // Check if the file extension of this node matches string fileFullPath; hierarchy.GetCanonicalName(itemid, out fileFullPath); if (CompareExtension(fileFullPath, fileExtension)) { // add matched file paths into the list files.Add(fileFullPath); } recursionLevel++; //Get the first child node of the current hierarchy being walked object pVar; hr = hierarchy.GetProperty(itemid, ((visibleNodesOnly || (hierIsSolution && recursionLevel == 1) ? (int)__VSHPROPID.VSHPROPID_FirstVisibleChild : (int)__VSHPROPID.VSHPROPID_FirstChild)), out pVar); ErrorHandler.ThrowOnFailure(hr); if (VSConstants.S_OK == hr) { // Use Depth first search so at each level we recurse to check if the node has any children // and then look for siblings. uint childId = GetItemId(pVar); while (childId != VSConstants.VSITEMID_NIL) { EnumProjectItems(hierarchy, fileExtension, files, childId, recursionLevel, false, visibleNodesOnly); hr = hierarchy.GetProperty(childId, ((visibleNodesOnly || (hierIsSolution && recursionLevel == 1)) ? (int)__VSHPROPID.VSHPROPID_NextVisibleSibling : (int)__VSHPROPID.VSHPROPID_NextSibling), out pVar); if (VSConstants.S_OK == hr) { childId = GetItemId(pVar); } else { ErrorHandler.ThrowOnFailure(hr); break; } } } } } /// <summary> /// Gets the item id. /// </summary> /// <param name="pvar">VARIANT holding an itemid.</param> /// <returns>Item Id of the concerned node</returns> private static uint GetItemId(object pvar) { if (pvar == null) return VSConstants.VSITEMID_NIL; if (pvar is int) return (uint)(int)pvar; if (pvar is uint) return (uint)pvar; if (pvar is short) return (uint)(short)pvar; if (pvar is ushort) return (uint)(ushort)pvar; if (pvar is long) return (uint)(long)pvar; return VSConstants.VSITEMID_NIL; } /// <summary> /// Check if the file has the expected extension. /// </summary> /// <param name="filePath"></param> /// <param name="extension"></param> /// <returns></returns> public static bool CompareExtension(string filePath, string extension) { bool equals = false; if (!string.IsNullOrEmpty(filePath)) { equals = (string.Compare(System.IO.Path.GetExtension(filePath), extension, StringComparison.OrdinalIgnoreCase) == 0); } return equals; } /// <summary> /// Read file content from a file /// </summary> /// <param name="filePath"> file path </param> /// <returns> file content in a string </returns> internal static string ReadFileContent(string filePath) { // Ensure that the file exists first. if (!File.Exists(filePath)) { Debug.WriteLine(string.Format("Cannot find the file: '{0}'", filePath)); return string.Empty; } string content; using (StreamReader reader = new StreamReader(filePath)) { content = reader.ReadToEnd(); reader.Close(); } return content; } /// <summary> /// Check null references and throw /// </summary> /// <param name="obj"></param> /// <param name="?"></param> public static void CheckNullArgument(object obj, string objectName) { if (obj == null) { throw new System.ArgumentNullException(objectName); } } /// <summary> /// Get offset of the fragment from an Identifier if the identifier.value matches the /// name we are looking for. /// </summary> /// <param name="identifier"></param> /// <param name="expectedName"></param> public static RawChangeInfo AddOffsestFromIdentifier( Identifier identifier, String expectedName, String newName, Boolean keepOldQuote) { RawChangeInfo change = null; if (identifier != null && String.Compare(expectedName, identifier.Value, true) == 0) { if (keepOldQuote) { QuoteType newQuote = QuoteType.NotQuoted; newName = Identifier.DecodeIdentifier(newName, out newQuote); newName = Identifier.EncodeIdentifier(newName, identifier.QuoteType); } change = new RawChangeInfo(identifier.StartOffset, identifier.FragmentLength, expectedName, newName); } return change; } public static IList<ChangeProposal> ConvertOffsets( string projectFullName, string fileFullPath, List<RawChangeInfo> changes, bool defaultIncluded) { // Get the file content into IVsTextLines IVsTextLines textLines = GetTextLines(fileFullPath); int changesCount = changes.Count; List<ChangeProposal> changeProposals = new List<ChangeProposal>(changesCount); for (int changeIndex = 0; changeIndex < changesCount; changeIndex++) { int startLine = 0; int startColumn = 0; int endLine = 0; int endColumn = 0; RawChangeInfo currentChange = changes[changeIndex]; int startPosition = currentChange.StartOffset; int endPosition = currentChange.StartOffset + currentChange.Length; int result = textLines.GetLineIndexOfPosition(startPosition, out startLine, out startColumn); if (result == VSConstants.S_OK) { result = textLines.GetLineIndexOfPosition(endPosition, out endLine, out endColumn); if (result == VSConstants.S_OK) { TextChangeProposal changeProposal = new TextChangeProposal(projectFullName, fileFullPath, currentChange.NewText); changeProposal.StartLine = startLine; changeProposal.StartColumn = startColumn; changeProposal.EndLine = endLine; changeProposal.EndColumn = endColumn; changeProposal.Included = defaultIncluded; changeProposals.Add(changeProposal); } } if (result != VSConstants.S_OK) { throw new InvalidOperationException("Failed to convert offset"); } } return changeProposals; } /// <summary> /// Get IVsTextLines from a file. If that file is in RDT, get text buffer from it. /// If the file is not in RDT, open that file in invisible editor and get text buffer /// from it. /// If failed to get text buffer, it will return null. /// </summary> /// <param name="fullPathFileName">File name with full path.</param> /// <returns>Text buffer for that file.</returns> private static IVsTextLines GetTextLines(string fullPathFileName) { System.IServiceProvider serviceProvider = DataPackage.Instance; IVsTextLines textLines = null; IVsRunningDocumentTable rdt = (IVsRunningDocumentTable)serviceProvider.GetService(typeof(SVsRunningDocumentTable)); if (rdt != null) { IVsHierarchy ppHier = null; uint pitemid, pdwCookie; IntPtr ppunkDocData = IntPtr.Zero; try { rdt.FindAndLockDocument((uint)(_VSRDTFLAGS.RDT_NoLock), fullPathFileName, out ppHier, out pitemid, out ppunkDocData, out pdwCookie); if (pdwCookie != 0) { if (ppunkDocData != IntPtr.Zero) { try { // Get text lines from the doc data IVsPersistDocData docData = (IVsPersistDocData)Marshal.GetObjectForIUnknown(ppunkDocData); if (docData is IVsTextLines) { textLines = (IVsTextLines)docData; } else { textLines = null; } } catch (ArgumentException) { // Do nothing here, it will return null stream at the end. } } } else { // The file is not in RDT, open it in invisible editor and get the text lines from it. IVsInvisibleEditor invisibleEditor = null; TryGetTextLinesAndInvisibleEditor(fullPathFileName, out invisibleEditor, out textLines); } } finally { if (ppunkDocData != IntPtr.Zero) Marshal.Release(ppunkDocData); } } return textLines; } /// <summary> /// Open the file in invisible editor in the running /// documents table (RDT), and get text buffer from that editor. /// </summary> /// <param name="fullPathFileName">File name with full path.</param> /// <param name="spEditor">The result invisible editor.</param> /// <param name="textLines">The result text buffer.</param> /// <returns>True, if the file is opened correctly in invisible editor.</returns> private static bool TryGetTextLinesAndInvisibleEditor(string fullPathFileName, out IVsInvisibleEditor spEditor, out IVsTextLines textLines) { System.IServiceProvider serviceProvider = DataPackage.Instance; spEditor = null; textLines = null; // Need to open this file. Use the invisible editor manager to do so. IVsInvisibleEditorManager spIEM; IntPtr ppDocData = IntPtr.Zero; bool result; Guid IID_IVsTextLines = typeof(IVsTextLines).GUID; try { spIEM = (IVsInvisibleEditorManager)serviceProvider.GetService(typeof(IVsInvisibleEditorManager)); spIEM.RegisterInvisibleEditor(fullPathFileName, null, (uint)_EDITORREGFLAGS.RIEF_ENABLECACHING, null, out spEditor); if (spEditor != null) { int hr = spEditor.GetDocData(0, ref IID_IVsTextLines, out ppDocData); if (hr == VSConstants.S_OK && ppDocData != IntPtr.Zero) { textLines = Marshal.GetTypedObjectForIUnknown(ppDocData, typeof(IVsTextLines)) as IVsTextLines; result = true; } else { result = false; } } else { result = false; } } finally { if (ppDocData != IntPtr.Zero) Marshal.Release(ppDocData); } return result; } } }
[ファイル] メニューの [SampleHelper.cs の保存] をクリックします。
RawChangeInfo という名前のクラスをプロジェクトに追加します。
コード エディターで、コードを次のように更新します。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MySamples.Refactoring { /// <summary> /// Helper class to encapsulate StartOffset, FragmentLength and change string from /// parser and SchemaAnalzyer. /// </summary> internal sealed class RawChangeInfo { private int _startOffset; private int _length; private string _oldText; private string _newText; public RawChangeInfo(int startOffset, int length, string oldText, string newText) { _startOffset = startOffset; _length = length; _oldText = oldText; _newText = newText; } public int StartOffset { get { return _startOffset; } set { _startOffset = value; } } public int Length { get { return _length; } } public string OldText { get { return _oldText; } } public string NewText { get { return _newText; } set { _newText = value; } } } }
[ファイル] メニューの [RawChangeInfo.cs の保存] をクリックします。
次に、CasingRefactoringCommand クラスを定義します。
CasingRefactoringCommand クラスを定義するには
CasingRefactorCommand という名前のクラスをプロジェクトに追加します。
コード エディターで、using ステートメントを次のように更新します。
using Microsoft.Data.Schema.Extensibility; using Microsoft.Data.Schema.SchemaModel; using Microsoft.Data.Schema.SchemaModel.Abstract; using Microsoft.Data.Schema.Sql; using Microsoft.Data.Schema.Sql.SchemaModel; using Microsoft.VisualStudio.Data.Schema.Package.Project; using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
名前空間を MySamples.Refactoring に変更します。
namespace MySamples.Refactoring
クラス定義を次のように更新します。
[DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))] internal class CasingRefactorCommand : RefactoringSchemaViewNodeCommand { }
この属性を使用して、このリファクタリングの種類と互換性のあるデータベース スキーマ プロバイダーを指定します。 この例では、新しいリファクタリングの種類を SqlDatabaseSchemaProvider から派生したプロバイダーで使用します。 クラスは RefactoringSchemaViewNodeCommand を継承します。 この基本クラスの継承は、このリファクタリングの種類がスキーマ ビューの指定されたノードで使用できることを意味します。 他にも、ファイル ノードやプロジェクト ノードを操作の対象とするリファクタリングの種類を定義できます。
次に、クラスに次のオーバーライド メソッドを追加します。
public override void Execute(IDatabaseProjectNode currentProject, IModelElement selectedModelElement) { CasingRefactorOperation operation = new CasingRefactorOperation(currentProject, selectedModelElement); operation.DoOperation(); }
このメソッドは、ユーザーがスキーマ ビューでリファクタリング コマンドを適用したときに動作を提供します。
次に、クラスに次のオーバーライド メソッドを追加します。
public override QueryStatusResult QueryStatus(IModelElement selectedModelElement) { if (selectedModelElement is IDatabaseColumnSource || selectedModelElement is ISqlSimpleColumn || selectedModelElement is ISqlProcedure || selectedModelElement is ISqlFunction || selectedModelElement is ISqlIndex || selectedModelElement is ISqlConstraint) { return QueryStatusResult.Enabled; } else { return QueryStatusResult.Invisible; } }
このメソッドは、スキーマ ビューのどのノードでリファクタリング コマンドを使用できるかを決定します。
最後に、クラスに次のオーバーライド メソッドを追加します。
public override string Text { get { return "Make First Letter Uppercase"; } }
このメソッドは、リファクタリング メニューに表示される、リファクタリング コマンドの表示名を提供します。
[ファイル] メニューの [CasingRefactoringCommand.cs の保存] をクリックします。
次に、CasingRefactoringOperation クラスを定義します。
CasingRefactoringOperation クラスを定義するには
CasingRefactoringOperation という名前のクラスをプロジェクトに追加します。
コード エディターで、using ステートメントを次のように更新します。
using System; using System.Diagnostics; using System.Globalization; using Microsoft.Data.Schema.SchemaModel; using Microsoft.Data.Schema.Sql.SchemaModel; using Microsoft.VisualStudio.Data.Schema.Package.Project; using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
名前空間を MySamples.Refactoring に変更します。
namespace MySamples.Refactoring
クラス定義を次のように更新します。
internal class CasingRefactorOperation : RefactoringOperation { }
このクラスは、RefactoringOperation を継承している必要があります。
クラスに次の定数宣言およびメンバー変数宣言を追加します。
#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;
プライベート定数は、プレビュー ウィンドウに表示される、この操作に関する情報を提供します。
クラスのコンストラクターを追加します。
public CasingRefactorOperation(IDatabaseProjectNode currentProject, IModelElement selectedModelElement) : base(currentProject) { _operationName = CasingRefactorOperationName; if (selectedModelElement as ISqlModelElement != null) { _modelElement = selectedModelElement as ISqlModelElement; } }
このコンストラクターは、操作名とモデル要素 (指定されている場合) を初期化します。
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; } }
追加のプロパティ定義を提供します。
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; } }
最後に、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 を作成します。
[ファイル] メニューの [CasingRefactoringOperation.cs の保存] をクリックします。
次に、CasingContributorInput クラスを定義します。
CasingContributorInput クラスを定義するには
CasingContributorInput という名前のクラスをプロジェクトに追加します。
コード エディターで、using ステートメントを次のように更新します。
using System; using Microsoft.Data.Schema.Sql.SchemaModel; using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
名前空間を MySamples.Refactoring に変更します。
namespace MySamples.Refactoring
クラス定義を次のように更新します。
internal class CasingContributorInput: ContributorInput { }
このクラスは、ContributorInput を継承している必要があります。
1 つの追加のプライベート メンバー変数を定義します。
private ISqlModelElement _modelElement;
このメンバーは、操作の対象とするモデル要素を追跡するために使用します。
クラス コンストラクターを追加します。
public CasingContributorInput(ISqlModelElement modelElement) { _modelElement = modelElement; }
このコンストラクターはモデル要素を初期化します。
モデル要素の読み取り専用のパブリック プロパティを追加します。
/// <summary> /// Selected model element /// </summary> public ISqlModelElement ModelElement { get { return _modelElement; } }
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); }
このコントリビューターの場合、入力は同じモデル要素に適用される場合に同一であると見なされます。
GetHashCode メソッドをオーバーライドします。
/// <summary> /// Override GetHashCode /// </summary> /// <returns></returns> public override int GetHashCode() { Int32 hash = _modelElement.GetHashCode(); return hash; }
[ファイル] メニューの [CasingContributorInput.cs の保存] をクリックします。
次に、CasingSymbolContributor クラスを定義します。
CasingSymbolContributor クラスを定義するには
CasingSymbolContributor という名前のクラスをプロジェクトに追加します。
コード エディターで、using ステートメントを次のように更新します。
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using Microsoft.Data.Schema.Extensibility; using Microsoft.Data.Schema.SchemaModel; using Microsoft.Data.Schema.ScriptDom.Sql; using Microsoft.Data.Schema.Sql.SchemaModel; using Microsoft.Data.Schema.Sql; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
名前空間を MySamples.Refactoring に変更します。
namespace MySamples.Refactoring
クラス定義を次のように更新します。
[DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))] internal class CasingSymbolContributor : RefactoringContributor<CasingContributorInput> { }
属性を指定して、このコントリビューターが、SqlDatabaseSchemaProvider から派生するどのデータベース スキーマ プロバイダーとも互換性があることを宣言します。 このクラスは、CasingContributorInput クラスの RefactoringContributor を継承する必要があります。
追加の定数とプライベート メンバー変数を定義します。
#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;
この定数は、プレビュー ウィンドウに表示される情報を指定します。 この追加のメンバーは、プレビュー グループの追跡に使用します。
クラス コンストラクターを追加します。
#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
コンストラクターは、モデル要素を初期化し、新しいプレビュー グループを作成して、そのプロパティを初期化します。 プレビュー ウィンドウに表示する特定のファイル名拡張子のアイコンを登録することもでき、さらに指定した拡張子を持つファイルについて構文の色指定を提供する言語サービスも登録できます。
PreviewGroup プロパティをオーバーライドして、このコントリビューターが作成されたときに作成されたグループを返します。
#region overrides /// <summary> /// Preview group for schema object files /// </summary> public override RefactoringPreviewGroup PreviewGroup { get { return _previewGroup; } set { _previewGroup = value; } }
ContributeChanges(Boolean) メソッドをオーバーライドして、変更提案の一覧を返します。
/// <summary> /// Contribute to the change proposals /// </summary> /// <param name="input">contributor input</param> /// <returns>List of change proposals with corresponding contributor inputs</returns> protected override Tuple<IList<ChangeProposal>, IList<ContributorInput>> ContributeChanges(CasingContributorInput input) { CasingContributorInput casingInput = input as CasingContributorInput; if (casingInput == null) { throw new ArgumentNullException("input"); } string projectFullName; casingInput.RefactoringOperation.CurrentProjectHierarchy.GetCanonicalName(VSConstants.VSITEMID_ROOT, out projectFullName); Tuple<IList<ChangeProposal>, IList<ContributorInput>> changes = GetChangesFromCurrentSymbolScript( projectFullName, casingInput, casingInput.ModelElement, true); return changes; } #endregion
次に、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 の追加の型は、シンボルが更新された要素へのすべての参照を処理するために使用します。 このメソッドは次のメソッドによって呼び出されます。
リファクタリングされるシンボルの定義が含まれるスクリプトの変更一覧を構築するメソッドを追加します。
public Tuple<IList<ChangeProposal>, IList<ContributorInput>> GetChangesFromCurrentSymbolScript( string projectFullName, ContributorInput input, ISqlModelElement modelElement, Boolean defaultChecked) { SampleHelper.CheckNullArgument(input, "input"); SampleHelper.CheckNullArgument(modelElement, "modelElement"); SqlSchemaModel dataSchemaModel = input.RefactoringOperation.CurrentDataSchemaModel as SqlSchemaModel; Debug.Assert(dataSchemaModel != null, "DataSchemaModel is null."); List<ChangeProposal> allChanges = new List<ChangeProposal>(); // list to hold all side effect contributor inputs List<ContributorInput> inputs = new List<ContributorInput>(); string fileFullPath = null; ISourceInformation elementSource = modelElement.PrimarySource; if (elementSource != null) { fileFullPath = elementSource.SourceName; } if (!string.IsNullOrEmpty(fileFullPath)) { List<RawChangeInfo> changes = AnalyzeScript(dataSchemaModel, modelElement); // Convert the offsets returned from parser to the line based offsets allChanges.AddRange(SampleHelper.ConvertOffsets(projectFullName, fileFullPath, changes, defaultChecked)); // Create a CasingReferenceContributorInput, anything reference this schema object // need to contribute changes for this input. inputs.Add(CreateCasingReferenceInput(input)); } return new Tuple<IList<ChangeProposal>, IList<ContributorInput>>(allChanges, inputs); }
このメソッドは AnalyzeScript メソッドを呼び出してスクリプト要素を処理します。
AnalyzeScript メソッドを追加します。
public static List<RawChangeInfo> AnalyzeScript(SqlSchemaModel dataSchemaModel, ISqlModelElement modelElement) { SampleHelper.CheckNullArgument(dataSchemaModel, "dataSchemaModel"); // get element source ISourceInformation elementSource = modelElement.PrimarySource; if (elementSource == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Cannot retrieve element source of {0}", SampleHelper.GetModelElementName(modelElement))); } // get sql fragment TSqlFragment fragment = elementSource.ScriptDom as TSqlFragment; if (fragment == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Cannot retrieve script fragment of {0}", SampleHelper.GetModelElementName(modelElement))); } List<RawChangeInfo> changes = new List<RawChangeInfo>(); Identifier id = null; if (fragment is CreateTableStatement) // Table { id = ((CreateTableStatement)fragment).SchemaObjectName.BaseIdentifier; } else if (fragment is CreateViewStatement) // View { id = ((CreateViewStatement)fragment).SchemaObjectName.BaseIdentifier; } else if (fragment is ColumnDefinition) // Column { id = ((ColumnDefinition)fragment).ColumnIdentifier; } else if (fragment is CreateProcedureStatement) // Proc { ProcedureReference procRef = ((CreateProcedureStatement)fragment).ProcedureReference; if (procRef != null) { id = procRef.Name.BaseIdentifier; } } else if (fragment is CreateFunctionStatement) // Function { id = ((CreateFunctionStatement)fragment).Name.BaseIdentifier; } else if (fragment is CreateIndexStatement) // Index { id = ((CreateIndexStatement)fragment).Name; } else if (fragment is Constraint) // inline constraint { id = ((Constraint)fragment).ConstraintIdentifier; } else if (fragment is AlterTableAddTableElementStatement) // default/check constraints { IList<Constraint> constraints = ((AlterTableAddTableElementStatement)fragment).TableConstraints; Debug.Assert(constraints.Count == 1, string.Format("Only one constraint expected, actual {0}", constraints.Count)); id = constraints[0].ConstraintIdentifier; } else // anything NYI { Debug.WriteLine(string.Format("Uppercasing symbol of type {0} is not implemented yet.", fragment.GetType().Name)); } string oldName = SampleHelper.GetModelElementSimpleName(modelElement); if (id != null && oldName.Length > 0) { string newName = oldName.Substring(0, 1).ToUpper() + oldName.Substring(1); // upper casing the first letter if (string.CompareOrdinal(oldName, newName) != 0) { RawChangeInfo change = SampleHelper.AddOffsestFromIdentifier(id, oldName, newName, true); if (change != null) { changes.Add(change); } } } return changes; }
このメソッドは、リファクタリングされるシンボルのソース スクリプトを取得します。 次に、このメソッドはソース スクリプトの SQL フラグメントを取得します。 さらに、このメソッドは新しい変更一覧を作成し、要素の識別子を (フラグメントの種類に基づいて) 特定し、変更一覧に新しい変更を追加します。
[ファイル] メニューの [CasingSymbolContributor.cs の保存] をクリックします。
次に、CasingReferenceContributorInput クラスを定義します。
CasingReferenceContributorInput クラスを定義するには
CasingReferenceContributorInput という名前のクラスをプロジェクトに追加します。
コード エディターで、using ステートメントを次のように更新します。
using System; using Microsoft.Data.Schema.Sql.SchemaModel; using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
名前空間を MySamples.Refactoring に変更します。
namespace MySamples.Refactoring
クラス定義を次のように更新します。
internal class CasingReferenceContributorInput: ContributorInput { }
このクラスは、ContributorInput を継承している必要があります。
追加のプライベート メンバー変数を定義します。
private ISqlModelElement _modelElement; private RefactoringPreviewGroup _previewGroup;
これらのメンバーは、操作の対象とするモデル要素と変更が属するプレビュー グループを追跡するために使用します。
クラス コンストラクターを追加します。
public CasingReferenceContributorInput(ISqlModelElement modelElement) { _modelElement = modelElement; }
このコンストラクターはモデル要素を初期化します。
モデル要素の読み取り専用のパブリック プロパティを追加します。
/// <summary> /// Selected model element /// </summary> public ISqlModelElement ModelElement { get { return _modelElement; } }
このコントリビューターによって識別される変更の追加のプレビュー グループを定義します。
/// <summary> /// Preview group that change proposals belong to /// </summary> public RefactoringPreviewGroup SchemaObjectsPreviewGroup { get { return _previewGroup; } set { _previewGroup = value; } }
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); }
このコントリビューターの場合、入力は同じモデル要素に適用される場合に同一であると見なされます。
GetHashCode メソッドをオーバーライドします。
/// <summary> /// Override GetHashCode /// </summary> /// <returns></returns> public override int GetHashCode() { Int32 hash = _modelElement.GetHashCode(); return hash; }
[ファイル] メニューの [CasingReferenceContributorInput.cs の保存] をクリックします。
次に、CasingReferenceContributor クラスを定義します。
CasingReferenceContributor クラスを定義するには
CasingReferenceContributor という名前のクラスをプロジェクトに追加します。
コード エディターで、using ステートメントを次のように更新します。
using System; using System.Collections.Generic; using System.Diagnostics; using Microsoft.Data.Schema.Extensibility; using Microsoft.Data.Schema.SchemaModel; using Microsoft.Data.Schema.ScriptDom.Sql; using Microsoft.Data.Schema.Sql.SchemaModel; using Microsoft.Data.Schema.Sql; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
名前空間を MySamples.Refactoring に変更します。
namespace MySamples.Refactoring
クラス定義を次のように更新します。
[DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))] internal class CasingReferenceContributor : RefactoringContributor<CasingReferenceContributorInput> { }
属性を指定して、このコントリビューターが、SqlDatabaseSchemaProvider から派生するどのデータベース スキーマ プロバイダーとも互換性があることを宣言します。 このクラスは、CasingReferenceContributorInput クラスの RefactoringContributor を継承する必要があります。
追加の定数とプライベート メンバー変数を定義します。
#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;
この定数は、プレビュー ウィンドウに表示される情報を指定します。 この追加のメンバーは、プレビュー グループの追跡に使用します。
クラス コンストラクターを追加します。
public CasingReferenceContributor() { }
PreviewGroup プロパティをオーバーライドして、このコントリビューターが作成されたときに作成されたグループを返します。
#region overrides /// <summary> /// Preview group for text files /// </summary> public override RefactoringPreviewGroup PreviewGroup { get { return _previewGroup; } set { _previewGroup = value; } } #endregion
ContributeChanges(Boolean) メソッドをオーバーライドして、変更提案の一覧を返します。
/// <summary> /// Contribute to the change proposals /// </summary> /// <param name="input">contributor input</param> /// <returns>List of change proposals with corresponding contributor inputs</returns> protected override Tuple<IList<ChangeProposal>, IList<ContributorInput>> ContributeChanges(CasingReferenceContributorInput input) { // cast input into reference input CasingReferenceContributorInput casingReferenceInput = input as CasingReferenceContributorInput; if (casingReferenceInput == null) { throw new ArgumentNullException("input"); } // Make sure CasingReferenceContributor and CasingSymbolContributor for a same refactoring operation // share the same preview group instance. if (casingReferenceInput.SchemaObjectsPreviewGroup != null) { _previewGroup = casingReferenceInput.SchemaObjectsPreviewGroup; } string projectFullName; casingReferenceInput.RefactoringOperation.CurrentProjectHierarchy.GetCanonicalName(VSConstants.VSITEMID_ROOT, out projectFullName); Tuple<IList<ChangeProposal>, IList<ContributorInput>> changes = GetChangesFromReferencedSymbolScripts( projectFullName, casingReferenceInput, casingReferenceInput.ModelElement, true ); return changes; }
ContributeChangesMethod は GetChangesFromReferencedSymbolScripts メソッドを呼び出します。
更新されるシンボルへの参照が含まれるスクリプトの変更提案の一覧を返す GetChangesFromReferencedSymbolScripts メソッドを実装します。
public static Tuple<IList<ChangeProposal>, IList<ContributorInput>> GetChangesFromReferencedSymbolScripts( string projectFullName, ContributorInput input, ISqlModelElement modelElement, bool defaultChecked // if the preview group is by default checked in the preview window ) { SampleHelper.CheckNullArgument(input, "input"); SqlSchemaModel dataSchemaModel = input.RefactoringOperation.CurrentDataSchemaModel as SqlSchemaModel; Debug.Assert(dataSchemaModel != null, "The DataSchemaModel is null for current Database project."); // Get all the changes for these schema objects that referencing the changed IModelElement. List<ChangeProposal> allChanges = new List<ChangeProposal>(); Dictionary<string, List<RawChangeInfo>> fileChanges = new Dictionary<string, List<RawChangeInfo>>(); List<RelationshipEntrySource> relationshipEntrySources = GetDependentEntries(dataSchemaModel, modelElement, true, true); foreach (var entry in relationshipEntrySources) { string fileFullPath = entry.Item1.SourceName; if (!string.IsNullOrEmpty(fileFullPath)) { IList<RawChangeInfo> result = AnalyzeRelationshipEntrySource(dataSchemaModel, modelElement, entry.Item2, entry.Item1); if (result != null) { List<RawChangeInfo> fileChange = null; if (!fileChanges.TryGetValue(fileFullPath, out fileChange)) { fileChange = new List<RawChangeInfo>(); fileChanges.Add(fileFullPath, fileChange); } fileChange.AddRange(result); } } } // Convert the offsets returned from ScriptDom to the line based offsets foreach (string fileFullPath in fileChanges.Keys) { allChanges.AddRange(SampleHelper.ConvertOffsets(projectFullName, fileFullPath, fileChanges[fileFullPath], defaultChecked)); } // Change propagation is not considered in this sample. // Thus the second value in the returned Tuple is set to null return new Tuple<IList<ChangeProposal>, IList<ContributorInput>>(allChanges, null); }
このメソッドは、更新されたシンボルのすべての依存関係が含まれる一覧を取得します。 次に、このメソッドは各参照について AnalyzeRelationshipEntrySource メソッドを呼び出し、必要な追加の変更を識別します。
AnalyzeRelationshipEntrySource メソッドを追加します。
public static IList<RawChangeInfo> AnalyzeRelationshipEntrySource( SqlSchemaModel dataSchemaModel, ISqlModelElement modelElement, ISourceInformation relationshipEntrySource) { SampleHelper.CheckNullArgument(dataSchemaModel, "dataSchemaModel"); List<Identifier> identifiers = new List<Identifier>(); TSqlFragment fragment = relationshipEntrySource.ScriptDom as TSqlFragment; // handle expressions if (fragment is SelectColumn) { Expression exp = ((SelectColumn)fragment).Expression;// as Expression; fragment = exp as TSqlFragment; } else if (fragment is ExpressionWithSortOrder) { Expression exp = ((ExpressionWithSortOrder)fragment).Expression; // as Expression; fragment = exp as TSqlFragment; } else if (fragment is ExpressionGroupingSpecification) { Expression exp = ((ExpressionGroupingSpecification)fragment).Expression; // as Expression; fragment = exp as TSqlFragment; } // handle different fragment if (fragment is Identifier) { identifiers.Add((Identifier)fragment); ; } else if (fragment is Column) { identifiers.AddRange(((Column)fragment).Identifiers); } else if (fragment is ColumnWithSortOrder) { identifiers.Add(((ColumnWithSortOrder)fragment).ColumnIdentifier); } else if (fragment is SchemaObjectName) { identifiers.Add(((SchemaObjectName)fragment).BaseIdentifier); } else if (fragment is SchemaObjectTableSource) { identifiers.Add(((SchemaObjectTableSource)fragment).SchemaObject.BaseIdentifier); } else if (fragment is SchemaObjectDataModificationTarget) { identifiers.Add(((SchemaObjectDataModificationTarget)fragment).SchemaObject.BaseIdentifier); } else if (fragment is FunctionCall) { FunctionCall funcCall = (FunctionCall)fragment; IdentifiersCallTarget identsCallTarget = funcCall.CallTarget as IdentifiersCallTarget; if (identsCallTarget != null) { identifiers.AddRange(identsCallTarget.Identifiers); } identifiers.Add(funcCall.FunctionName); } else if (fragment is ProcedureReference) { SchemaObjectName procRefName = ((ProcedureReference)fragment).Name; if (procRefName != null) { identifiers.Add(procRefName.BaseIdentifier); } } else if (fragment is TriggerObject) { SchemaObjectName triggerName = ((TriggerObject)fragment).Name; if (triggerName != null) { identifiers.Add(triggerName.BaseIdentifier); } } else if (fragment is FullTextIndexColumn) { identifiers.Add(((FullTextIndexColumn)fragment).Name); } else if (fragment is SecurityTargetObject) { identifiers.AddRange(((SecurityTargetObject)fragment).ObjectName.Identifiers); } else // other types of fragments are not handled in this sample { Debug.WriteLine(string.Format("Uppercasing referencing object of type {0} is not implemented yet.", fragment.GetType().Name)); } List<RawChangeInfo> changes = new List<RawChangeInfo>(); string oldName = SampleHelper.GetModelElementSimpleName(modelElement); if (identifiers.Count > 0 && oldName.Length > 0) { string newName = oldName.Substring(0, 1).ToUpper() + oldName.Substring(1); // upper casing the first letter if (string.CompareOrdinal(oldName, newName) != 0) { // list of changes for this relationship entry RawChangeInfo change = null; foreach (Identifier idf in identifiers) { change = SampleHelper.AddOffsestFromIdentifier(idf, oldName, newName, true); if (change != null) { changes.Add(change); } } } } return changes; }
このメソッドは、更新されたシンボルに依存するスクリプト フラグメントに加える必要のある変更の一覧を取得します。
GetDependentEntries メソッドを追加します。
/// <summary> /// Get all relating relationship entries for the model element and its composing and hierarchical children /// </summary> internal static List<System.Tuple<ISourceInformation, IModelRelationshipEntry>> GetDependentEntries( SqlSchemaModel dataSchemaModel, ISqlModelElement modelElement, bool ignoreComposedRelationship, bool includeChildDependencies) { SampleHelper.CheckNullArgument(dataSchemaModel, "dataSchemaModel"); SampleHelper.CheckNullArgument(modelElement, "modelElement"); var dependencies = new List<System.Tuple<ISourceInformation, IModelRelationshipEntry>>(); List<IModelRelationshipEntry> relatingRelationships = new List<IModelRelationshipEntry>(); GetDependentEntries(modelElement, dataSchemaModel, new Dictionary<IModelElement, Object>(), relatingRelationships, includeChildDependencies); foreach (IModelRelationshipEntry entry in relatingRelationships) { ModelRelationshipType relationshipType = entry.RelationshipClass.ModelRelationshipType; if (!ignoreComposedRelationship || (relationshipType != ModelRelationshipType.Composing)) { ISqlModelElement relatingElement = entry.FromElement as ISqlModelElement; Debug.Assert(relatingElement != null, "Relating element got from ModelStore is null."); foreach (var si in relatingElement.GetRelationshipEntrySources(entry)) { dependencies.Add(new System.Tuple<ISourceInformation, IModelRelationshipEntry>(si, entry)); } } } return dependencies; } private static void GetDependentEntries( IModelElement modelElement, DataSchemaModel dataSchemaModel, Dictionary<IModelElement, Object> visitElement, List<IModelRelationshipEntry> relationshipEntries, Boolean includeChildDependencies) { if (modelElement != null && !visitElement.ContainsKey(modelElement)) { visitElement[modelElement] = null; IList<IModelRelationshipEntry> relatingRelationships = modelElement.GetReferencingRelationshipEntries(); relationshipEntries.AddRange(relatingRelationships); if (includeChildDependencies) { // First loop through all composed children of this element, and get their relationship entries as well foreach (IModelRelationshipEntry entry in modelElement.GetReferencedRelationshipEntries()) { if (entry.RelationshipClass.ModelRelationshipType == ModelRelationshipType.Composing) { GetDependentEntries(entry.Element, dataSchemaModel, visitElement, relationshipEntries, includeChildDependencies); } } // Then loop through all hierarchical children of this element, add their dependents to the list. foreach (IModelRelationshipEntry entry in relatingRelationships) { if (entry.RelationshipClass.ModelRelationshipType == ModelRelationshipType.Hierarchical) { GetDependentEntries(entry.FromElement, dataSchemaModel, visitElement, relationshipEntries, includeChildDependencies); } } } } }
[ファイル] メニューの [CasingReferenceContributor.cs の保存] をクリックします。
次に、アセンブリを構成してビルドします。
アセンブリの署名とビルドを行うには
[プロジェクト] メニューの [CasingRefactoringType のプロパティ] をクリックします。
[署名] タブをクリックします。
[アセンブリの署名] をクリックします。
[厳密な名前のキー ファイルを選択してください] の [<新規>] をクリックします。
[厳密な名前キーの作成] ダイアログ ボックスで、[キー ファイル] に「MyRefKey」と入力します。
(省略可能) 厳密な名前のキー ファイルにはパスワードを指定できます。
[OK] をクリックします。
[ファイル] メニューの [すべてを保存] をクリックします。
[ビルド] メニューの [ソリューションのビルド] をクリックします。
次に、アセンブリをインストールして登録し、使用できるテスト条件として表示されるようにします。
アセンブリのインストールおよび登録
CasingRefactoringType アセンブリをインストールするには
MyExtensions という名前のフォルダーを %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions フォルダー内に作成します。
署名済みのアセンブリ (CasingRefactoringType.dll) を %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions フォルダーにコピーします。
注意
XML ファイルは、%Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions フォルダーに直接コピーしないことをお勧めします。 代わりにサブフォルダーを使用すると、Visual Studio に用意されている他のファイルを誤って変更することを防げます。
次に、拡張機能の一種であるアセンブリを登録し、それが Visual Studio に表示されるようにします。
CasingRefactoringType アセンブリを登録するには
[表示] メニューの [その他のウィンドウ] をポイントし、[コマンド ウィンドウ] をクリックして、[コマンド] ウィンドウを開きます。
[コマンド] ウィンドウに、次のコードを入力します。 FilePath をコンパイル済みの .dll ファイルのパスとファイル名に置き換えます。 パスとファイル名は引用符で囲みます。
注意
既定では、コンパイル済みの .dll ファイルのパスは YourSolutionPath\bin\Debug または YourSolutionPath\bin\Release です。
? System.Reflection.Assembly.LoadFrom("FilePath").FullName
? System.Reflection.Assembly.LoadFrom(@"FilePath").FullName
Enter キーを押します。
作成された行をクリップボードにコピーします。 行は次のようになります。
"GeneratorAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nnnnnnnnnnnnnnnn"
プレーンテキスト エディター (メモ帳など) を開きます。
重要
Windows Vista および Microsoft Windows Server 2008 では、ファイルを Program Files フォルダーに保存できるように、エディターを管理者として開きます。
次の情報を入力し、独自のアセンブリ名、公開キー トークン、および拡張機能の型を指定します。
<?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 から派生するすべての関連クラスを登録します。
ファイルに CasingRefactoringType.extensions.xml という名前を付けて %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions フォルダーに保存します。
Visual Studio を閉じます。
次に、ごく単純なデータベース プロジェクトを作成して、新しいリファクタリング タイプをテストします。
新しいリファクタリングの種類のテスト
データベース プロジェクトを作成するには
[ファイル] メニューの [新規作成] をポイントし、[プロジェクト] をクリックします。
[インストールされたテンプレート] で、[データベース] ノードを展開し、[SQL Server] ノードをクリックします。
テンプレートの一覧の [SQL Server 2008 データベース プロジェクト] をクリックします。
[OK] をクリックして既定のプロジェクト名を受け入れ、プロジェクトを作成します。
空のデータベース プロジェクトが作成されます。
主キーのあるテーブルを追加するには
[表示] メニューの [スキーマ ビュー] をクリックします。
スキーマ ビューで、[スキーマ] ノードを展開し、[dbo] ノードを展開して、[テーブル] ノードを右クリックします。次に、[追加] をポイントし、[テーブル] をクリックします。
[新しい項目の追加] ダイアログ ボックスで、[名前] に「employee」と入力します。
注意
テーブル名の先頭の文字には、故意に小文字を使用しています。
[OK] をクリックします。
[テーブル] ノードを展開し、[employee] ノードを右クリックします。次に、[追加] をポイントし、[主キー] をクリックします。
[新しい項目の追加] ダイアログ ボックスで、[名前] に「PK_Employee_column_1」と入力します。
[OK] をクリックします。
次に、新しいリファクタリングの種類を使用して、テーブル名とそれに対するすべての参照を変更します。
新しいリファクタリングの種類を使用してテーブル名を更新するには
スキーマ ビューで、employee テーブル ノードを右クリックし、[リファクター] をポイントして、[最初の文字を大文字にする] をクリックします。
このチュートリアルで、この新しいリファクタリングの種類を定義しました。
[変更のプレビュー] ダイアログ ボックスで変更を確認し、[適用] をクリックします。
テーブル名が Employee に更新されます。 主キーに含まれる、このテーブルへの参照も更新されます。
次の手順
他にも独自のデータベース リファクタリングの種類を作成できます。 既存のデータベース リファクタリングの種類を他のファイルやオブジェクトの種類にも適用できるようにするコントリビューターを追加することもできます。
参照
処理手順
チュートリアル: データベースの名前変更リファクタリングを拡張してテキスト ファイルで動作させる