Exemplarische Vorgehensweise: Erweitern der Datenbankumbenennung durch Umgestaltung auf die Verarbeitung von Textdateien
In diesem Thema erstellen, installieren, registrieren und testen Sie schrittweise einen neuen Contributor für Umgestaltungen mit Umbenennung. Dieses Umgestaltungsziel erweitert die Funktionen von Visual Studio Premium oder Visual Studio Ultimate, sodass bei der Datenbankumgestaltung Verweise auf Datenbankobjekte umbenannt werden können, die in Textdateien des Datenbankprojekts enthalten sind.
Wenn Sie einem vorhandenen Umgestaltungstyp einen neuen Umgestaltungscontributor hinzufügen, muss eine vorhandene Contributor-Eingabeklasse verwendet werden.
In dieser exemplarischen Vorgehensweise werden die folgenden Aufgaben veranschaulicht:
Erstellen Sie eine neue Assembly mit den Klassen für ein benutzerdefiniertes Umgestaltungsziel.
Installieren und registrieren Sie die Assembly, sodass das Umgestaltungsziel in Visual Studio Premium oder Visual Studio Ultimate verfügbar ist.
Erstellen Sie ein einfaches Datenbankprojekt, um zu testen, ob das Umgestaltungsziel wie erwartet verwendet werden kann.
Vorbereitungsmaßnahmen
Zum Durchführen dieser exemplarischen Vorgehensweise benötigen Sie die folgenden Komponenten:
Visual Studio 2010 Premium oder Visual Studio 2010 Ultimate muss installiert sein.
Zudem muss auf Ihrem Computer auch Visual Studio 2010 SDK installiert sein. Sie können dieses Kit auf der Microsoft-Website auf der Seite Visual Studio 2010 SDK herunterladen.
Erstellen einer Assembly mit einem benutzerdefinierten Umgestaltungsziel
Zum Erstellen eines benutzerdefinierten Umgestaltungsziels, das Umgestaltungen mit Umbenennung für Textdateien ermöglicht, müssen Sie eine einzelne Klasse implementieren, von der ein neuer RefactoringContributor bereitgestellt wird:
- RenameReferenceTextContributorContributor – Diese Klasse erstellt die Liste der Änderungsvorschläge für das umbenannte Symbol. Die Änderungsvorschläge gelten für jeden Verweis in einer Textdatei, die sich im Datenbankprojekt befindet.
Bevor Sie diese Klasse erstellen, müssen Sie eine Klassenbibliothek erstellen und erforderliche Verweise sowie Hilfscode hinzufügen, der einen Teil von Code vereinfacht, den Sie später in dieser exemplarischen Vorgehensweise erstellen.
So erstellen Sie die Klassenbibliothek und Hilfscode
Erstellen Sie ein neues C#-Klassenbibliotheksprojekt, und benennen Sie dieses mit RenameTextContributor.csproj.
Fügen Sie Verweise auf die folgenden .NET-Assemblys hinzu:.
Microsoft.Data.Schema
Microsoft.Data.Schema.ScriptDom
Microsoft.Data.Schema.ScriptDom.sql
Microsoft.Data.Schema.Sql
Fügen Sie Verweise auf die folgenden Assemblys hinzu, die sich im Verzeichnis "%Programme%\Microsoft Visual Studio 10.0\VSTSDB" befinden:
Microsoft.Data.Schema.Tools.Sql.dll
Microsoft.VisualStudio.Data.Schema.Package.dll
Microsoft.VisualStudio.Data.Schema.Package.Sql.dll
Fügen Sie Verweise auf die folgenden Assemblys aus dem Visual Studio 2010 Software Development Kit (SDK) hinzu:
Microsoft.VisualStudio.OLE.Interop.dll
Microsoft.VisualStudio.Shell.10.0.dll
Microsoft.VisualStudio.TextManager.Interop.dll
Microsoft.VisualStudio.Shell.Interop.dll
Microsoft.VisualStudio.Shell.Interop.8.0.dll
Microsoft.VisualStudio.Shell.Interop.9.0.dll
Microsoft.VisualStudio.Shell.Interop.10.0.dll
Benennen Sie im Projektmappen-Explorer die Datei Class1.cs in SampleHelper.cs um.
Doppelklicken Sie auf SampleHelper.cs, um die Datei im Code-Editor zu öffnen.
Tipp
Diese Hilfsklasse ist mit dem Hilfstext identisch, der in der exemplarischen Vorgehensweise für benutzerdefinierte Umgestaltungstypen verwendet wurde. Um Zeit zu sparen, können Sie den Quellcode aus diesem Projekt in das neue Projekt kopieren.
Ersetzen Sie den Inhalt des Code-Editors durch folgenden Code:
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using Microsoft.Data.Schema.SchemaModel; using Microsoft.Data.Schema.ScriptDom.Sql; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Data.Schema.Package.Refactoring; using Microsoft.VisualStudio.Data.Schema.Package.UI; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TextManager.Interop; namespace MySamples.Refactoring { internal static class SampleHelper { public static String GetModelElementName(IModelElement modelElement) { SampleHelper.CheckNullArgument(modelElement, "modelElement"); return modelElement.ToString(); } /// <summary> /// Given a model element, returns its simple name. /// </summary> public static String GetModelElementSimpleName(IModelElement modelElement) { String separator = "."; String simpleName = String.Empty; String fullName = modelElement.ToString(); String[] nameParts = fullName.Split(separator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); if (nameParts.Length > 0) { simpleName = nameParts[nameParts.Length - 1]; // last part } if (simpleName.StartsWith("[") && simpleName.EndsWith("]")) { simpleName = simpleName.Substring(1, simpleName.Length - 2); } return simpleName; } /// <summary> /// Find all files in the project with the specified file extension /// </summary> public static List<string> GetAllFilesInProject(IVsHierarchy solutionNode, string fileExtension, bool visibleNodesOnly) { List<string> files = new List<string>(); if (null != solutionNode) { EnumProjectItems(solutionNode, fileExtension, files, VSConstants.VSITEMID_ROOT, // item id of solution root node 0, // recursion from solution node true, // hierarchy is Solution node visibleNodesOnly); // visibleNodesOnly } return files; } /// <summary> /// Enumerates recursively over the hierarchy items. /// </summary> /// <param name="hierarchy">hierarchy to enmerate over.</param> /// <param name="fileExtension">type of files we need to collect from the project</param> /// <param name="files">list of file paths</param> /// <param name="itemid">item id of the hierarchy</param> /// <param name="recursionLevel">Depth of recursion. For example, if recursion started with the Solution /// node, then : Level 0 -- Solution node, Level 1 -- children of Solution, etc.</param> /// <param name="hierIsSolution">true if hierarchy is Solution Node. </param> /// <param name="visibleNodesOnly">true if only nodes visible in the Solution Explorer should /// be traversed. false if all project items should be traversed.</param> private static void EnumProjectItems(IVsHierarchy hierarchy, string fileExtension, List<string> files, uint itemid, int recursionLevel, bool hierIsSolution, bool visibleNodesOnly) { int hr; IntPtr nestedHierarchyObj; uint nestedItemId; Guid hierGuid = typeof(IVsHierarchy).GUID; // Check first if this node has a nested hierarchy. hr = hierarchy.GetNestedHierarchy(itemid, ref hierGuid, out nestedHierarchyObj, out nestedItemId); if (VSConstants.S_OK == hr && IntPtr.Zero != nestedHierarchyObj) { IVsHierarchy nestedHierarchy = Marshal.GetObjectForIUnknown(nestedHierarchyObj) as IVsHierarchy; Marshal.Release(nestedHierarchyObj); if (nestedHierarchy != null) { EnumProjectItems(nestedHierarchy, fileExtension, files, nestedItemId, recursionLevel, false, visibleNodesOnly); } } else { // Check if the file extension of this node matches string fileFullPath; hierarchy.GetCanonicalName(itemid, out fileFullPath); if (CompareExtension(fileFullPath, fileExtension)) { // add matched file paths into the list files.Add(fileFullPath); } recursionLevel++; //Get the first child node of the current hierarchy being walked object pVar; hr = hierarchy.GetProperty(itemid, ((visibleNodesOnly || (hierIsSolution && recursionLevel == 1) ? (int)__VSHPROPID.VSHPROPID_FirstVisibleChild : (int)__VSHPROPID.VSHPROPID_FirstChild)), out pVar); ErrorHandler.ThrowOnFailure(hr); if (VSConstants.S_OK == hr) { // Use Depth first search so at each level we recurse to check if the node has any children // and then look for siblings. uint childId = GetItemId(pVar); while (childId != VSConstants.VSITEMID_NIL) { EnumProjectItems(hierarchy, fileExtension, files, childId, recursionLevel, false, visibleNodesOnly); hr = hierarchy.GetProperty(childId, ((visibleNodesOnly || (hierIsSolution && recursionLevel == 1)) ? (int)__VSHPROPID.VSHPROPID_NextVisibleSibling : (int)__VSHPROPID.VSHPROPID_NextSibling), out pVar); if (VSConstants.S_OK == hr) { childId = GetItemId(pVar); } else { ErrorHandler.ThrowOnFailure(hr); break; } } } } } /// <summary> /// Gets the item id. /// </summary> /// <param name="pvar">VARIANT holding an itemid.</param> /// <returns>Item Id of the concerned node</returns> private static uint GetItemId(object pvar) { if (pvar == null) return VSConstants.VSITEMID_NIL; if (pvar is int) return (uint)(int)pvar; if (pvar is uint) return (uint)pvar; if (pvar is short) return (uint)(short)pvar; if (pvar is ushort) return (uint)(ushort)pvar; if (pvar is long) return (uint)(long)pvar; return VSConstants.VSITEMID_NIL; } /// <summary> /// Check if the file has the expected extension. /// </summary> /// <param name="filePath"></param> /// <param name="extension"></param> /// <returns></returns> public static bool CompareExtension(string filePath, string extension) { bool equals = false; if (!string.IsNullOrEmpty(filePath)) { equals = (string.Compare(System.IO.Path.GetExtension(filePath), extension, StringComparison.OrdinalIgnoreCase) == 0); } return equals; } /// <summary> /// Read file content from a file /// </summary> /// <param name="filePath"> file path </param> /// <returns> file content in a string </returns> internal static string ReadFileContent(string filePath) { // Ensure that the file exists first. if (!File.Exists(filePath)) { Debug.WriteLine(string.Format("Cannot find the file: '{0}'", filePath)); return string.Empty; } string content; using (StreamReader reader = new StreamReader(filePath)) { content = reader.ReadToEnd(); reader.Close(); } return content; } /// <summary> /// Check null references and throw /// </summary> /// <param name="obj"></param> /// <param name="?"></param> public static void CheckNullArgument(object obj, string objectName) { if (obj == null) { throw new System.ArgumentNullException(objectName); } } /// <summary> /// Get offset of the fragment from an Identifier if the identifier.value matches the /// name we are looking for. /// </summary> /// <param name="identifier"></param> /// <param name="expectedName"></param> public static RawChangeInfo AddOffsestFromIdentifier( Identifier identifier, String expectedName, String newName, Boolean keepOldQuote) { RawChangeInfo change = null; if (identifier != null && String.Compare(expectedName, identifier.Value, true) == 0) { if (keepOldQuote) { QuoteType newQuote = QuoteType.NotQuoted; newName = Identifier.DecodeIdentifier(newName, out newQuote); newName = Identifier.EncodeIdentifier(newName, identifier.QuoteType); } change = new RawChangeInfo(identifier.StartOffset, identifier.FragmentLength, expectedName, newName); } return change; } public static IList<ChangeProposal> ConvertOffsets( string projectFullName, string fileFullPath, List<RawChangeInfo> changes, bool defaultIncluded) { // Get the file content into IVsTextLines IVsTextLines textLines = GetTextLines(fileFullPath); int changesCount = changes.Count; List<ChangeProposal> changeProposals = new List<ChangeProposal>(changesCount); for (int changeIndex = 0; changeIndex < changesCount; changeIndex++) { int startLine = 0; int startColumn = 0; int endLine = 0; int endColumn = 0; RawChangeInfo currentChange = changes[changeIndex]; int startPosition = currentChange.StartOffset; int endPosition = currentChange.StartOffset + currentChange.Length; int result = textLines.GetLineIndexOfPosition(startPosition, out startLine, out startColumn); if (result == VSConstants.S_OK) { result = textLines.GetLineIndexOfPosition(endPosition, out endLine, out endColumn); if (result == VSConstants.S_OK) { TextChangeProposal changeProposal = new TextChangeProposal(projectFullName, fileFullPath, currentChange.NewText); changeProposal.StartLine = startLine; changeProposal.StartColumn = startColumn; changeProposal.EndLine = endLine; changeProposal.EndColumn = endColumn; changeProposal.Included = defaultIncluded; changeProposals.Add(changeProposal); } } if (result != VSConstants.S_OK) { throw new InvalidOperationException("Failed to convert offset"); } } return changeProposals; } /// <summary> /// Get IVsTextLines from a file. If that file is in RDT, get text buffer from it. /// If the file is not in RDT, open that file in invisible editor and get text buffer /// from it. /// If failed to get text buffer, it will return null. /// </summary> /// <param name="fullPathFileName">File name with full path.</param> /// <returns>Text buffer for that file.</returns> private static IVsTextLines GetTextLines(string fullPathFileName) { System.IServiceProvider serviceProvider = DataPackage.Instance; IVsTextLines textLines = null; IVsRunningDocumentTable rdt = (IVsRunningDocumentTable)serviceProvider.GetService(typeof(SVsRunningDocumentTable)); if (rdt != null) { IVsHierarchy ppHier = null; uint pitemid, pdwCookie; IntPtr ppunkDocData = IntPtr.Zero; try { rdt.FindAndLockDocument((uint)(_VSRDTFLAGS.RDT_NoLock), fullPathFileName, out ppHier, out pitemid, out ppunkDocData, out pdwCookie); if (pdwCookie != 0) { if (ppunkDocData != IntPtr.Zero) { try { // Get text lines from the doc data IVsPersistDocData docData = (IVsPersistDocData)Marshal.GetObjectForIUnknown(ppunkDocData); if (docData is IVsTextLines) { textLines = (IVsTextLines)docData; } else { textLines = null; } } catch (ArgumentException) { // Do nothing here, it will return null stream at the end. } } } else { // The file is not in RDT, open it in invisible editor and get the text lines from it. IVsInvisibleEditor invisibleEditor = null; TryGetTextLinesAndInvisibleEditor(fullPathFileName, out invisibleEditor, out textLines); } } finally { if (ppunkDocData != IntPtr.Zero) Marshal.Release(ppunkDocData); } } return textLines; } /// <summary> /// Open the file in invisible editor in the running /// documents table (RDT), and get text buffer from that editor. /// </summary> /// <param name="fullPathFileName">File name with full path.</param> /// <param name="spEditor">The result invisible editor.</param> /// <param name="textLines">The result text buffer.</param> /// <returns>True, if the file is opened correctly in invisible editor.</returns> private static bool TryGetTextLinesAndInvisibleEditor(string fullPathFileName, out IVsInvisibleEditor spEditor, out IVsTextLines textLines) { System.IServiceProvider serviceProvider = DataPackage.Instance; spEditor = null; textLines = null; // Need to open this file. Use the invisible editor manager to do so. IVsInvisibleEditorManager spIEM; IntPtr ppDocData = IntPtr.Zero; bool result; Guid IID_IVsTextLines = typeof(IVsTextLines).GUID; try { spIEM = (IVsInvisibleEditorManager)serviceProvider.GetService(typeof(IVsInvisibleEditorManager)); spIEM.RegisterInvisibleEditor(fullPathFileName, null, (uint)_EDITORREGFLAGS.RIEF_ENABLECACHING, null, out spEditor); if (spEditor != null) { int hr = spEditor.GetDocData(0, ref IID_IVsTextLines, out ppDocData); if (hr == VSConstants.S_OK && ppDocData != IntPtr.Zero) { textLines = Marshal.GetTypedObjectForIUnknown(ppDocData, typeof(IVsTextLines)) as IVsTextLines; result = true; } else { result = false; } } else { result = false; } } finally { if (ppDocData != IntPtr.Zero) Marshal.Release(ppDocData); } return result; } } }
Klicken Sie im Menü Datei auf SampleHelper.cs speichern.
Fügen Sie dem Projekt die Klasse RawChangeInfo hinzu.
Aktualisieren Sie den Code im Code-Editor wie folgt:
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; } } } }
Klicken Sie im Menü Datei auf RawChangeInfo.cs speichern.
Definieren Sie danach die RenameReferenceTextContributor-Klasse.
So definieren Sie die RenameReferenceTextContributor-Klasse
Fügen Sie dem Projekt die RenameReferenceTextContributor-Klasse hinzu.
Aktualisieren Sie die using-Anweisungen im Code-Editor wie folgt:
using System; using System.Collections.Generic; using Microsoft.Data.Schema.Extensibility; using Microsoft.Data.Schema.Sql; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Data.Schema.Package.Refactoring; using Microsoft.VisualStudio.Data.Schema.Package.Sql.Refactoring;
Ändern Sie den Namespace in MySamples.Refactoring:
namespace MySamples.Refactoring
Aktualisieren Sie die Klassendefinition wie folgt:
[DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))] internal class RenameReferenceTextContributor : RefactoringContributor<RenameReferenceContributorInput> { }
Geben Sie das Attribut an, um zu deklarieren, dass dieser Contributor mit allen von SqlDatabaseSchemaProvider abgeleiteten Datenbankschema-Anbietern kompatibel ist. Die Klasse muss von RefactoringContributor für den RenameReferenceContributorInput erben.
Definieren Sie zusätzliche Konstanten und private Membervariablen:
#region const private const string TxtExtension = @".txt"; private const string PreviewFriendlyName = @"Text Files"; private const string PreviewDescription = @"Update text symbols in text files in the database project."; private const string PreviewWarningMessage = @"Updating text symbols in text files in the database project can cause inconsistency."; #endregion #region members private RefactoringPreviewGroup _textPreviewGroup; private List<String> _changingFiles; #endregion
Die Konstanten stellen Informationen bereit, die im Vorschaufenster angezeigt werden. Mit den zusätzlichen Membern werden Änderungen an der Vorschaugruppe und der Liste von Textdateien nachverfolgt.
Fügen Sie den Klassenkonstruktor hinzu:
#region ctor public RenameReferenceTextContributor() { _textPreviewGroup = new RefactoringPreviewGroup(PreviewFriendlyName); _textPreviewGroup.Description = PreviewDescription; _textPreviewGroup.WarningMessage = PreviewWarningMessage; _textPreviewGroup.EnableChangeGroupUncheck = true; _textPreviewGroup.EnableChangeUncheck = true; _textPreviewGroup.DefaultChecked = false; _textPreviewGroup.IncludeInCurrentProject = true; // This sample uses the default icon for the file, // but you could provide your own icon here. //RefactoringPreviewGroup.RegisterIcon(TxtExtension, "textfile.ico"); } #endregion
Der Konstruktor initialisiert das Modellelement und erstellt dabei eine neue Vorschaugruppe und initialisiert deren Eigenschaften. Sie könnten an dieser Stelle auch Symbole registrieren, die im Vorschaufenster für bestimmte Dateinamenerweiterungen angezeigt werden sollen. Da für Textdateien keine Syntaxhervorhebung verfügbar ist, registrieren Sie für die Textdateien keinen Sprachdienst.
Überschreiben Sie die PreviewGroup-Eigenschaft, um die Gruppe zurückzugeben, die beim Erstellen dieses Contributors erstellt wurde:
/// <summary> /// Preview group for text files /// </summary> public override RefactoringPreviewGroup PreviewGroup { get { return _textPreviewGroup; } set { _textPreviewGroup = value; } }
Überschreiben Sie die ContributeChanges(Boolean)-Methode, um eine Liste von Änderungsvorschlägen zurückzugeben:
/// <summary> /// Contribute to the change proposals /// </summary> /// <param name="input">contributor input</param> /// <returns>List of change proposals with corresponding contributor inputs</returns> protected override Tuple<IList<ChangeProposal>, IList<ContributorInput>> ContributeChanges(RenameReferenceContributorInput input) { RenameReferenceContributorInput referenceInput = input as RenameReferenceContributorInput; if (referenceInput == null) { throw new ArgumentNullException("input"); } string projectFullName; referenceInput.RefactoringOperation.CurrentProjectHierarchy.GetCanonicalName(VSConstants.VSITEMID_ROOT, out projectFullName); return GetChangesForAllTextFiles(referenceInput, projectFullName, _textPreviewGroup.DefaultChecked, out _changingFiles); }
Diese Methode ruft die GetAllChangesForAllTextFiles-Methode auf, die den größten Teil der Aufgaben übernimmt:
Fügen Sie die GetChangesForAllTextFiles-Methode hinzu, um die Liste von Textdateien im Projekt zu durchlaufen, die Änderungen für jede Datei abzurufen und Sie diese Änderungen in einer Liste von Änderungsvorschlägen zusammenzufassen:
/// <summary> /// Get all changes from all text files. /// </summary> private static Tuple<IList<ChangeProposal>, IList<ContributorInput>> GetChangesForAllTextFiles( RenameReferenceContributorInput input, string projectFullName, bool defaultChecked, out List<String> changingFiles) { if (input == null) { throw new ArgumentNullException("input"); } changingFiles = new List<String>(); List<ChangeProposal> allChanges = new List<ChangeProposal>(); List<string> files = new List<string>(); files = SampleHelper.GetAllFilesInProject(input.RefactoringOperation.CurrentProjectHierarchy, TxtExtension, false); // process the text files one by one if (files != null && files.Count > 0) { int fileCount = files.Count; // Get all the changes for all txt files. for (int fileIndex = 0; fileIndex < fileCount; fileIndex++) { IList<ChangeProposal> changes = GetChangesForOneTextFile( input, projectFullName, files[fileIndex], defaultChecked); if (changes != null && changes.Count > 0) { allChanges.AddRange(changes); changingFiles.Add(files[fileIndex]); } } } return new Tuple<IList<ChangeProposal>, IList<ContributorInput>>(allChanges, null); }
Implementieren Sie die GetChangesForOneTextFileMethod-Methode, um eine Liste von Änderungsvorschlägen in einer einzelnen Textdatei zurückzugeben:
/// <summary> /// Get all the change proposals from one text file. /// </summary> private static IList<ChangeProposal> GetChangesForOneTextFile( RenameReferenceContributorInput input, string projectFullName, string fileFullPath, bool defaultChecked) { const string separators = " \t \r \n \\()[]{}|.+-*/~!@#$%^&<>?:;"; string fileContent = SampleHelper.ReadFileContent(fileFullPath); IList<ChangeProposal> changeProposals= null; if (string.IsNullOrEmpty(fileContent)) { // return empty change list changeProposals = new List<ChangeProposal>(); } else { int startIndex = 0; int maxIndex = fileContent.Length - 1; string oldName = input.OldName; int oldNameLength = oldName.Length; List<RawChangeInfo> changes = new List<RawChangeInfo>(); while (startIndex < maxIndex) { // Text files do not understand schema object information // We do just case-insensitive string match (without schema info) // Only match whole word int offset = fileContent.IndexOf(oldName, startIndex, StringComparison.OrdinalIgnoreCase); // Cannot find match any more, stop the match if (offset < 0) { break; } startIndex = offset + oldNameLength; // match whole word: check before/after characters are separators if (offset > 0) { char charBeforeMatch = fileContent[offset - 1]; // match starts in the middle of a token, discard and move on if (!separators.Contains(charBeforeMatch.ToString())) { continue; } } if (offset + oldNameLength < maxIndex) { char charAfterMatch = fileContent[offset + oldNameLength]; // match ends in the middle of a token, discard and move on if (!separators.Contains(charAfterMatch.ToString())) { continue; } } RawChangeInfo change = new RawChangeInfo(offset, oldNameLength, input.OldName, input.NewName); changes.Add(change); } // convert index-based offsets to ChangeOffsets in ChangeProposals changeProposals = SampleHelper.ConvertOffsets( projectFullName, fileFullPath, changes, defaultChecked); } return changeProposals; }
Da das Umgestaltungsziel kein Transact-SQL-Skript oder ein Schemaobjekt bildet, verwendet diese Methode keine Typen oder Methoden aus Microsoft.Data.Schema.ScriptDom oder Microsoft.Data.Schema.Sql.SchemaModel. Dieses Umgestaltungsziel stellt eine Suchen-und-Ersetzen-Implementierung für Textdateien bereit, da für Symbole in Textdateien keine Schemainformationen verfügbar sind.
Klicken Sie im Menü Datei auf RenameTextContributor.cs speichern.
Anschließend konfigurieren Sie und erstellen die Assembly.
So signieren und erstellen Sie die Assembly
Klicken Sie im Menü Projekt auf Eigenschaften von RenameTextContributor.
Klicken Sie auf die Registerkarte Signierung.
Klicken Sie auf Assembly signieren.
Klicken Sie im Feld Schlüsseldatei mit starkem Namen auswählen auf <Neu>.
Geben Sie im Dialogfeld Schlüssel für einen starken Namen erstellen unter Schlüsseldateiname den Namen MyRefKey ein.
(optional) Sie können für die Schlüsseldatei mit starkem Namen ein Kennwort angeben.
Klicken Sie auf OK.
Klicken Sie im Menü Datei auf Alle speichern.
Klicken Sie im Menü Erstellen auf Projektmappe erstellen.
Danach müssen Sie die Assembly installieren und registrieren, sodass diese als verfügbare Testbedingung angezeigt wird.
So installieren Sie die Assembly RenameTextContributor
Erstellen Sie im Verzeichnis %Programme%\Microsoft Visual Studio 10.0\VSTSDB\Extensions den Ordner MyExtensions.
Kopieren Sie die signierte Assembly (RenameTextContributor.dll) in das Verzeichnis %Programme%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions.
Tipp
Es wird empfohlen, die XML-Dateien nicht direkt in das Verzeichnis %Programme%\Microsoft Visual Studio 10.0\VSTSDB\Extensions zu kopieren. Wenn Sie stattdessen einen Unterordner verwenden, vermeiden Sie versehentliche Änderungen an den anderen Dateien, die mit Visual Studio bereitgestellt werden.
Danach müssen Sie die Assembly, eine Art Funktionserweiterung, registrieren, sodass diese in Visual Studio angezeigt wird.
So registrieren Sie die RenameTextContributor-Assembly
Klicken Sie im Menü Ansicht auf Weitere Fenster, und klicken Sie dann auf Befehlsfenster, um das Befehlsfenster zu öffnen.
Geben Sie im Befehlsfenster folgenden Code ein. Ersetzen Sie FilePath durch den Pfad und Dateinamen der kompilierten DLL-Datei. Schließen Sie Pfad und Dateiname in Anführungszeichen ein.
Tipp
Der Pfad der kompilierten DLL-Datei lautet standardmäßig Projektmappenpfad\bin\Debug oder Projektmappenpfad\bin\Release.
? System.Reflection.Assembly.LoadFrom("FilePath").FullName
? System.Reflection.Assembly.LoadFrom(@"FilePath").FullName
Drücken Sie die EINGABETASTE.
Kopieren Sie die resultierende Zeile in die Zwischenablage. Die Zeile sollte der folgenden entsprechen:
"RenameTextContributor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nnnnnnnnnnnnnnnn"
Öffnen Sie einen Text-Editor, z. B. Editor.
Stellen Sie die folgenden Informationen bereit, und geben Sie dabei eigene Werte für Assemblyname, öffentliches Schlüsseltoken und Erweiterungstyp an:
<?xml version="1.0" encoding="utf-8" ?> <extensions assembly="" version="1" xmlns="urn:Microsoft.Data.Schema.Extensions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:Microsoft.Data.Schema.Extensions Microsoft.Data.Schema.Extensions.xsd"> <extension type="MySamples.Refactoring.RenameReferenceTextContributor" assembly="RenameTextContributor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<enter key here>" enabled="true" /> </extensions>
Registrieren Sie die Klasse, die von RefactoringContributor erbt:
Speichern Sie die Datei im Verzeichnis %Programme%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions unter RenameTextContributor.extensions.xml.
Schließen Sie Visual Studio.
Anschließend erstellen Sie ein sehr einfaches Datenbankprojekt, um den neuen Umgestaltungstyp zu testen.
Testen des neuen Umgestaltungscontributors
So erstellen Sie ein Datenbankprojekt
Zeigen Sie im Menü Datei auf Neu, und klicken Sie auf Projekt.
Erweitern Sie unter Installierte Vorlagen den Knoten Datenbank, und klicken Sie anschließend auf den Knoten SQL Server.
Klicken Sie in der Liste der Vorlagen auf SQL Server 2008-Datenbankprojekt.
Klicken Sie auf OK, um den Projektnamen zu übernehmen und das Projekt zu erstellen.
Ein leeres Datenbankprojekt wird erstellt.
So fügen Sie eine Tabelle mit einem Primärschlüssel hinzu
Klicken Sie im Menü Ansicht auf Schemaansicht der Datenbank.
Erweitern Sie in Schemaansicht den Knoten Schemas und den Knoten dbo, klicken Sie mit der rechten Maustaste auf den Knoten Tabellen, zeigen Sie auf Hinzufügen, und klicken Sie auf Tabelle.
Geben Sie im Dialogfeld Neues Element hinzufügen unter Name den Namen employee ein.
Tipp
Sie verwenden am Anfang des Tabellennamens bewusst einen Kleinbuchstaben.
Klicken Sie auf OK.
Erweitern Sie den Knoten Tabellen, klicken Sie mit der rechten Maustaste auf den Knoten employee, zeigen Sie auf Hinzufügen, und klicken Sie auf Primärschlüssel.
Geben Sie im Dialogfeld Neues Element hinzufügen unter Name die Bezeichnung PK_Employee_column_1 ein.
Klicken Sie auf OK.
Fügen Sie dem Datenbankprojekt dann eine Textdatei mit einem Verweis auf die Tabelle employee hinzu.
So fügen Sie eine Textdatei mit dem Tabellennamen hinzu
Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Knoten für das Datenbankprojekt, zeigen Sie auf Hinzufügen und klicken Sie dann auf Neues Element.
Klicken Sie im Dialogfeld Neues Element hinzufügen in der Liste Kategorien auf Visual Studio-Vorlagen.
Klicken Sie in der Liste Vorlagen auf Textdatei.
Geben Sie unter Name die Datei SampleText1.txt ein.
Fügen Sie im Code-Editor folgenden Text hinzu:
This is documentation for the employee table. Any changes made to the employee table name should also be reflected in this text file.
Klicken Sie im Menü Datei auf SampleText1.txt speichern.
Ändern Sie dann mithilfe des neuen Umgestaltungstyps den Tabellennamen und sämtliche Verweise auf diesen.
So aktualisieren Sie den Tabellennamen mit dem neuen Umgestaltungscontributor
Klicken Sie in Schemaansicht mit der rechten Maustaste auf den Knoten employee, zeigen Sie auf Umgestalten, und klicken Sie auf Umbenennen.
Geben Sie im Dialogfeld Umbenennen unter Neuer Name die Bezeichnung [Person] ein.
Führen Sie im Dialogfeld Vorschau der Änderungen einen Bildlauf durch die Änderungsgruppen durch, bis Sie die Gruppe Textdateien erreichen.
Beide in der Textdatei enthaltenen Instanzen von employee sind aufgeführt. In dieser exemplarischen Vorgehensweise haben Sie die Klassen definiert, die diesen neuen Typ von Umgestaltung ermöglichen. Aktivieren Sie die Kontrollkästchen neben den einzelnen Änderungen.
Klicken Sie auf Übernehmen.
Der Tabellenname wird in der Schemaansicht und im Inhalt der Datei SampleText1.txt in Person aktualisiert.
Nächste Schritte
Sie können weitere Umgestaltungsziele, aber auch neue Umgestaltungstypen erstellen, um den Aufwand für wiederkehrende Änderungen am Datenbankprojekt zu reduzieren.
Siehe auch
Aufgaben
Konzepte
Erstellen benutzerdefinierter Typen oder Regeln für die Datenbankumgestaltung