逐步解說:建立新的資料庫重構類型以變更大小寫
在本逐步說明主題中,您將建立、安裝、註冊和測試新類型的「資料庫重構」(Database Refactoring)。 此重構作業會將指定之物件名稱的第一個字母轉換成大寫,並將所有參考更新為這個已更新的名稱。
這個逐步解說將說明下列工作:
建立新組件,其中包含自訂重構類型的類別。
安裝並註冊組件,以便在 Visual Studio Premium 或 Visual Studio Ultimate 中使用重構類型。
建立簡單的「資料庫專案」(Database Project),測試重構類型是否如預期運作。
必要條件
您需要下列元件才能完成此逐步解說:
您必須已安裝 Visual Studio 2010 Premium 或 Visual Studio 2010 Ultimate。
您必須已在電腦上安裝 Visual Studio 2010 軟體開發套件 (SDK)。 若要下載這個套件,請參閱 Microsoft 網站的網頁:Visual Studio 2010 SDK (英文)。
建立具有自訂重構類型的組件
若要建立自訂重構類型,以將物件名稱的第一個字母轉換成大寫,然後更新所有對該物件的參考,您必須實作六個類別:
CasingRefactoringCommand - 此類別提供重構功能表的命令名稱,其會指定哪些模型項目可使用重構類型,當使用者按一下該命令時,會呼叫您的重構作業。
CasingRefactoringOperation - 此類別會指定重構作業與 [預覽視窗] 的互動方式、指定描述作業的屬性,以及建立 CasingContributorInput 類別。
CasingContributorInput - 此類別會將輸入資料儲存到 CasingSymbolContributor 類別。
CasingSymbolContributor - 此類別會建置重新命名符號的變更提議清單,也會建立 CasingReferenceContributorInput 類別,以更新所有對目前變更名稱之物件的參考。
CasingReferenceContributorInput - 此類別會將輸入資料儲存至 CasingReferenceContributor 類別。
CasingReferenceContributor - 此類別會建置一變更提議清單,該清單將預計更新的參考與重新命名的符號關聯。
建立這些類別之前,您將建立類別庫、加入必要的參考,以及加入一些 Helper 程式碼,以簡化您在本逐步解說稍後撰寫的部分程式碼。
若要建立類別庫和 Helper 程式碼
建立新的 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 { }
該屬性可用來指定與此重構類型相容的「資料庫結構描述提供者」(Database Schema Provider)。 對於此範例,您的新重構類型會與從 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。
定義一個其他私用成員變數:
private ISqlModelElement _modelElement;
此成員可用於追蹤您正在操作的模型項目。
加入類別建構函式:
public CasingContributorInput(ISqlModelElement modelElement) { _modelElement = modelElement; }
建構函式會初始化模型項目。
加入模型項目的唯讀公用屬性:
/// <summary> /// Selected model element /// </summary> public ISqlModelElement ModelElement { get { return _modelElement; } }
[覆寫 Equals] 方法提供比較,並且判斷兩個 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 的任何「資料庫結構描述提供者」(Database Schema Provider) 相容。 您的類別必須繼承自 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; } }
[覆寫 Equals] 方法提供比較,並且判斷兩個 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 的任何「資料庫結構描述提供者」(Database Schema Provider) 相容。 您的類別必須繼承自 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。
(選擇性) 您可以為強式名稱金鑰檔指定密碼。
按一下 [確定]。
在 [檔案] 功能表上按一下 [全部儲存]。
在 [建置] 功能表上,按一下 [建置方案]。
接下來,您必須安裝並註冊組件,以便出現在可用的「測試條件」(Test Condition) 中。
安裝和註冊組件
若要安裝 CasingRefactoringType 組件
在 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions 資料夾中建立名為 MyExtensions 的資料夾。
將已簽署的組件 (CasingRefactoringType.dll) 複製到 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions 資料夾。
注意事項
建議您不要直接將 XML 檔案複製到 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions 資料夾中。 如果改用子資料夾,可以防止不小心變更 Visual Studio 隨附的其他檔案。
接下來,您必須註冊組件 (一種「擴充功能」(Feature Extension) 類型),以便讓它出現在 Visual Studio 中。
若要註冊 CasingRefactoringType 組件
按一下 [檢視] 功能表上的 [其他視窗],然後按一下 [命令視窗] 開啟 [命令] 視窗。
在 [命令] 視窗中輸入下列程式碼。 將 FilePath 替代為已編譯之 .dll 檔案的路徑和檔案名稱。 請在路徑和檔案名稱周圍加上引號。
注意事項
根據預設,已編譯之 .dll 檔案的路徑為 <您的方案路徑>\bin\Debug 或 <您的方案路徑>\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 的所有相關類別。
將此檔案另存為 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions 資料夾中的 CasingRefactoringType.extensions.xml。
關閉 Visual Studio。
接下來,您將建立非常簡單的資料庫專案來測試新的重構類型。
測試新的重構類型
若要建立資料庫專案
在 [檔案] 功能表中,指向 [新增],然後按一下 [專案]。
展開 [已安裝的範本] 底下的 [資料庫] 節點,然後按一下 [SQL Server] 節點。
在範本清單中,按一下 [SQL Server 2008 資料庫專案]。
按一下 [確定] 接受預設專案名稱並建立專案。
空資料庫專案隨即建立。
若要加入具有主索引鍵的資料表
按一下 [檢視] 功能表上的 [資料庫結構描述檢視]。
在 [結構描述檢視] 中依序展開 [結構描述] 節點和 [dbo] 節點,以滑鼠右鍵按一下 [資料表] 節點,指向 [加入],然後按一下 [資料表]。
在 [加入新項目] 對話方塊的 [名稱] 中,輸入 employee。
注意事項
這裡會刻意使用小寫字母做為資料表名稱的開頭。
按一下 [確定]。
展開 [資料表] 節點,以滑鼠右鍵按一下 [employee] 節點,指向 [加入],然後按一下 [主索引鍵]。
在 [加入新項目] 對話方塊的 [名稱] 中,輸入 PK_Employee_column_1。
按一下 [確定]。
接下來,您將使用新的重構類型來變更資料表名稱及其所有參考。
若要使用新的重構類型更新資料表名稱
在 [結構描述檢視] 中,以滑鼠右鍵按一下 [employee] 資料表節點,指向 [重構],然後按一下 [讓第一個字母大寫]。
您已定義此逐步解說中的新重構類型。
在 [預覽變更] 對話方塊中,檢視變更,然後按一下 [套用]。
資料表名稱會更新為 Employee。 也會變更主索引鍵中該資料表的參考。
後續步驟
您可以建立自己的其他資料庫重構類型。 您也可以加入其他參與者,讓現有的資料庫重構類型可執行其他類型的檔案或物件。