Freigeben über


Exemplarische Vorgehensweise: Erstellen eines neuen Typs der Datenbankumgestaltung zum Ändern der Schreibweise

In diesem Thema erstellen, installieren, registrieren und testen Sie schrittweise einen neuen Typ der Datenbankumgestaltung. In diesem Umgestaltungsvorgang wird das erste Zeichen des Namens des angegebenen Objekts in einen Großbuchstaben konvertiert, und alle Verweise auf den aktualisierten Namen werden ebenfalls aktualisiert.

In dieser exemplarischen Vorgehensweise werden die folgenden Aufgaben veranschaulicht:

  1. Erstellen Sie eine neue Assembly mit den Klassen für einen benutzerdefinierten Umgestaltungstyp.

  2. Installieren und registrieren Sie die Assembly, sodass der Umgestaltungstyp in Visual Studio Premium oder Visual Studio Ultimate verfügbar ist.

  3. Erstellen Sie ein einfaches Datenbankprojekt, um zu testen, ob der Umgestaltungstyp wie erwartet verwendet werden kann.

Vorbereitungsmaßnahmen

Zum Durchführen dieser exemplarischen Vorgehensweise benötigen Sie die folgenden Komponenten:

  • Sie müssen Visual Studio 2010 Premium oder Visual Studio 2010 Ultimate installiert haben.

  • Ferner müssen Sie das Visual Studio 2010 Software Development Kit (SDK) auf dem Computer installiert haben. Sie können dieses Kit auf der Microsoft-Website auf der Seite Visual Studio 2010 SDK herunterladen.

Erstellen einer Assembly mit einem benutzerdefinierten Umgestaltungstyp

Um einen benutzerdefinierten Umgestaltungstyp zu erstellen, der den ersten Buchstaben eines Objektnamens in die Großschreibung konvertieren kann und anschließend alle Verweise auf das Objekt aktualisiert, müssen Sie sechs Klassen implementieren:

  • CasingRefactoringCommand: Diese Klasse stellt den Befehlsnamen für das Umgestaltungsmenü bereit, gibt an, für welche Modellelemente der Umgestaltungstyp verfügbar ist und ruft den Umgestaltungsvorgang auf, wenn der Benutzer auf den Befehl klickt.

  • CasingRefactoringOperation: Diese Klasse gibt an, wie der Umgestaltungsvorgang mit dem Vorschaufenster interagiert, gibt Eigenschaften für die Beschreibung des Vorgangs an und erstellt die CasingContributorInput-Klasse.

  • CasingContributorInput: Diese Klasse speichert Eingabedaten für die CasingSymbolContributor-Klasse.

  • CasingSymbolContributor: Diese Klasse erstellt die Liste der Änderungsvorschläge für das umbenannte Symbol sowie die CasingReferenceContributorInput-Klasse zum Aktualisieren der Verweise auf das Objekt, dessen Name geändert wurde.

  • CasingReferenceContributorInput: Diese Klasse speichert Eingabedaten für die CasingReferenceContributor-Klasse.

  • CasingReferenceContributor: Diese Klasse erstellt die Liste der Änderungsvorschläge, die dem Aktualisieren von Verweisen auf das umbenannte Symbol zugeordnet sind.

Bevor Sie diese Klassen 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

  1. Erstellen Sie ein neues C#-Klassenbibliotheksprojekt, und benennen Sie dieses mit CasingRefactoringType.csproj.

  2. Fügen Sie Verweise für die folgenden Klassenbibliotheken hinzu:

    • 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

  3. 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.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

  4. Benennen Sie im Projektmappen-Explorer die Datei Class1.cs in SampleHelper.cs um.

  5. Doppelklicken Sie auf SampleHelper.cs, um die Datei im Code-Editor zu öffnen.

  6. 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. 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;
            }
        }
    }
    
  7. Klicken Sie im Menü Datei auf SampleHelper.cs speichern.

  8. Fügen Sie dem Projekt die Klasse RawChangeInfo hinzu.

  9. 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;
                }
            }
        }
    }
    
  10. Klicken Sie im Menü Datei auf RawChangeInfo.cs speichern.

    Definieren Sie danach die CasingRefactoringCommand-Klasse.

So definieren Sie die CasingRefactoringCommand-Klasse

  1. Fügen Sie dem Projekt die Klasse CasingRefactorCommand hinzu.

  2. Aktualisieren Sie die using-Anweisungen im Code-Editor wie folgt:

    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;
    
  3. Ändern Sie den Namespace in MySamples.Refactoring:

    namespace MySamples.Refactoring
    
  4. Aktualisieren Sie die Klassendefinition wie folgt:

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

    Das Attribut wird verwendet, um die Datenbankschema-Anbieter anzugeben, mit denen dieser Umgestaltungstyp kompatibel ist. In diesem Beispiel funktioniert der neue Umgestaltungstyp mit einem beliebigem Anbieter, der von SqlDatabaseSchemaProvider abgeleitet wird. Die Klasse erbt RefactoringSchemaViewNodeCommand. Durch Erben von dieser Basisklasse wird angegeben, dass der Umgestaltungstyp in angegebenen Knoten in der Schemaansicht verfügbar ist. Sie können auch andere Umgestaltungstypen für Datei- und Projektknoten definieren.

  5. Fügen Sie der Klasse nun die folgende Überschreibungsmethode hinzu:

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

    Diese Methode stellt das Verhalten beim Anwenden des Umgestaltungsbefehls in der Schemaansicht durch den Benutzer bereit.

  6. Fügen Sie der Klasse nun die folgende Überschreibungsmethode hinzu:

            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;
                }
            }
    

    Diese Methode bestimmt die Knoten in der Schemaansicht, in denen der Umgestaltungsbefehl verfügbar ist.

  7. Abschließend fügen Sie der Klasse die folgende Überschreibungsmethode hinzu:

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

    Diese Methode stellt den Anzeigenamen für den Umgestaltungsbefehl bereit, der im Umgestaltungsmenü angezeigt wird.

  8. Klicken Sie im Menü Datei auf CasingRefactoringCommand.cs speichern.

    Definieren Sie danach die CasingRefactoringOperation-Klasse.

So definieren Sie die CasingRefactoringOperation-Klasse

  1. Fügen Sie dem Projekt eine neue Klassendatei mit dem Namen CasingRefactoringOperation hinzu.

  2. Aktualisieren Sie die using-Anweisungen im Code-Editor wie folgt:

    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;
    
  3. Ändern Sie den Namespace in MySamples.Refactoring:

    namespace MySamples.Refactoring
    
  4. Aktualisieren Sie die Klassendefinition wie folgt:

        internal class CasingRefactorOperation : RefactoringOperation
        {
    }
    

    Die Klasse muss von RefactoringOperation erben.

  5. Fügen Sie der Klasse die folgende Deklaration einer Konstante und einer Membervariablen hinzu:

            #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;
    

    Die privaten Konstanten stellen Informationen zu dieser Operation bereit, die im Vorschaufenster angezeigt wird.

  6. Fügen Sie den Konstruktor für die Klasse hinzu:

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

    Der Konstruktor initialisiert den Vorgangsnamen und das Modellelement, sofern diese angegeben wurden.

  7. Überschreiben Sie die PreviewWindowInfo-Eigenschaft, um die Werte abzurufen, die im Vorschaufenster angezeigt werden, wenn Benutzer den Umgestaltungstyp übernehmen:

            /// <summary>
            /// Preview dialog information for this RenameRefactorOperation.
            /// </summary>
            protected override PreviewWindowInfo PreviewWindowInfo
            {
                get
                {
                    if (_previewWindowInfo == null)
                    {
                        _previewWindowInfo = new PreviewWindowInfo();
                        _previewWindowInfo.ConfirmButtonText = ConfirmButtonText;
                        _previewWindowInfo.Description = OperationDescription;
                        _previewWindowInfo.HelpContext = String.Empty;
                        _previewWindowInfo.TextViewDescription = OperationTextViewDescription;
                        _previewWindowInfo.Title = string.Format(CultureInfo.CurrentCulture,PreviewDialogTitle, CasingRefactorOperationName);
                    }
                    return _previewWindowInfo;
                }
            }
    
  8. Stellen Sie zusätzliche Eigenschaftendefinitionen bereit:

            protected override string OperationName
            {
                get
                {
                    return _operationName;
                }
            }
    
            /// <summary>
            /// Undo Description used in undo stack
            /// </summary>
            protected override string UndoDescription
            {
                get
                {
                    return string.Format(CultureInfo.CurrentCulture,
                        CasingUndoDescription, SampleHelper.GetModelElementName(this.ModelElement));
                }
            }
    
            /// <summary>
            /// SchemaIdentifier of currently selected schema object
            /// </summary>
            public ISqlModelElement ModelElement
            {
                get
                {
                    return _modelElement;
                }
                set
                {
                    _modelElement = value;
                }
            }
    
  9. Überschreiben Sie abschließend die OnGetContributorInput-Methode:

            /// <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;
            }
    

    Diese Methode erstellt die ContributorInput, die an die Umgestaltungscontributors für diesen Umgestaltungstyp übergeben wird.

  10. Klicken Sie im Menü Datei auf CasingRefactoringOperation.cs speichern.

    Definieren Sie danach die CasingContributorInput-Klasse.

So definieren Sie die CasingContributorInput-Klasse

  1. Fügen Sie dem Projekt die Klasse CasingContributorInput hinzu.

  2. Aktualisieren Sie die using-Anweisungen im Code-Editor wie folgt:

    using System;
    using Microsoft.Data.Schema.Sql.SchemaModel;
    using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
    
  3. Ändern Sie den Namespace in MySamples.Refactoring:

    namespace MySamples.Refactoring
    
  4. Aktualisieren Sie die Klassendefinition wie folgt:

        internal class CasingContributorInput: ContributorInput
        {
        }
    

    Die Klasse muss von ContributorInput erben.

  5. Definieren Sie eine zusätzliche private Membervariable:

            private ISqlModelElement _modelElement;
    

    Mit diesem Member wird das Modellelement nachverfolgt, das Sie verwenden.

  6. Fügen Sie den Klassenkonstruktor hinzu:

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

    Der Konstruktor initialisiert das Modellelement.

  7. Fügen Sie eine schreibgeschützte öffentliche Eigenschaft für das Modellelement hinzu:

            /// <summary>
            /// Selected model element
            /// </summary>
            public ISqlModelElement ModelElement
            {
                get 
    
                {
                    return _modelElement;
                }
            }
    
  8. Die Override Equals-Methode stellt einen Vergleich bereit, der überprüft, ob zwei CasingContributorInput-Objekte identisch sind:

            /// <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);
            }
    

    Bei diesem Contributor werden die Eingaben als identisch angesehen, wenn sie sich auf das gleiche Modellelement beziehen.

  9. Überschreiben Sie die GetHashCode-Methode:

            /// <summary>
            /// Override GetHashCode
            /// </summary>
            /// <returns></returns>
            public override int GetHashCode()
            {
                Int32 hash = _modelElement.GetHashCode();
                return hash;
            }
    
  10. Klicken Sie im Menü Datei auf CasingContributorInput.cs speichern.

    Definieren Sie danach die CasingSymbolContributor-Klasse.

So definieren Sie die CasingSymbolContributor-Klasse

  1. Fügen Sie dem Projekt die CasingSymbolContributor-Klasse hinzu.

  2. Aktualisieren Sie die using-Anweisungen im Code-Editor wie folgt:

    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;
    
  3. Ändern Sie den Namespace in MySamples.Refactoring:

    namespace MySamples.Refactoring
    
  4. Aktualisieren Sie die Klassendefinition wie folgt:

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

    Geben Sie das Attribut an, um zu deklarieren, dass dieser Contributor mit allen von SqlDatabaseSchemaProvider abgeleiteten Datenbankschema-Anbietern kompatibel ist. Die Klasse muss für die CasingContributorInput-Klasse von RefactoringContributor erben.

  5. Definieren Sie zusätzliche Konstanten und private Membervariablen:

            #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;
    

    Die Konstanten stellen Informationen bereit, die im Vorschaufenster angezeigt werden. Der zusätzliche Member wird verwendet, um die Vorschaugruppe nachzuverfolgen.

  6. Fügen Sie den Klassenkonstruktor hinzu:

            #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
    

    Der Konstruktor initialisiert das Modellelement und erstellt dabei eine neue Vorschaugruppe und initialisiert deren Eigenschaften. Sie können hier auch Symbole registrieren, die im Vorschaufenster für bestimmte Dateinamenerweiterungen angezeigt werden sollen, und Sie können einen Sprachdienst registrieren, der verwendet wird, um Syntaxfarbe für Dateien bereitzustellen, die eine angegebene Erweiterung haben.

  7. Überschreiben Sie die PreviewGroup-Eigenschaft, um die Gruppe zurückzugeben, die beim Erstellen dieses Contributors erstellt wurde:

            #region overrides
            /// <summary>
            /// Preview group for schema object files
            /// </summary>
            public override RefactoringPreviewGroup PreviewGroup
            {
                get
                {
                    return _previewGroup;
                }
                set
                {
                    _previewGroup = value;
                }
            }
    
  8. Ü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(CasingContributorInput input)
            {
                CasingContributorInput casingInput = input as CasingContributorInput;
                if (casingInput == null)
                {
                    throw new ArgumentNullException("input");
                }
    
                string projectFullName;
                casingInput.RefactoringOperation.CurrentProjectHierarchy.GetCanonicalName(VSConstants.VSITEMID_ROOT, out projectFullName);
    
                Tuple<IList<ChangeProposal>, IList<ContributorInput>> changes = GetChangesFromCurrentSymbolScript(
                        projectFullName,
                        casingInput,
                        casingInput.ModelElement,
                        true);
    
                return changes;
            }
    
            #endregion
    
  9. Erstellen Sie anschließend einen weiteren ContributorInput-Typ:

            /// <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;
            }
    

    Dieser zusätzliche ContributorInput-Typ wird verwendet, um alle Verweise auf das Element zu behandeln, dessen Symbol aktualisiert wurde. Diese Methode wird von der nächsten Methode aufgerufen.

  10. Fügen Sie eine Methode hinzu, um eine Liste der Änderungen für das Skript zu erstellen, das die Definition für das Symbol enthält, das umgestaltet wird:

            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);
            }
    

    Diese Methode ruft die AnalyzeScript-Methode auf, um die Skriptelemente zu verarbeiten.

  11. Fügen Sie die AnalyzeScript-Methode hinzu.

            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;
            }
    

    Diese Methode ruft das Quellskript für das Element ab, das umgestaltet wird. Anschließend ruft die Methode das SQL-Fragment für das Quellskript ab. Die Methode erstellt dann eine neue Änderungsliste, bestimmt den Bezeichner für das Element (anhand des Fragmenttyps) und fügt der Änderungsliste die neue Änderung hinzu.

  12. Klicken Sie im Menü Datei auf CasingSymbolContributor.cs speichern.

    Definieren Sie danach die CasingReferenceContributorInput-Klasse.

So definieren Sie die CasingReferenceContributorInput-Klasse

  1. Fügen Sie dem Projekt die Klasse CasingReferenceContributorInput hinzu.

  2. Aktualisieren Sie die using-Anweisungen im Code-Editor wie folgt:

    using System;
    using Microsoft.Data.Schema.Sql.SchemaModel;
    using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
    
  3. Ändern Sie den Namespace in MySamples.Refactoring:

    namespace MySamples.Refactoring
    
  4. Aktualisieren Sie die Klassendefinition wie folgt:

        internal class CasingReferenceContributorInput: ContributorInput
        {        
        }        
    

    Die Klasse muss von ContributorInput erben.

  5. Definieren Sie zusätzliche private Membervariablen:

            private ISqlModelElement _modelElement;
            private RefactoringPreviewGroup _previewGroup;
    

    Mit diesen Membern werden das verwendete Modellelement und die Vorschaugruppe für Änderungen nachverfolgt.

  6. Fügen Sie den Klassenkonstruktor hinzu:

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

    Der Konstruktor initialisiert das Modellelement.

  7. Fügen Sie eine schreibgeschützte öffentliche Eigenschaft für das Modellelement hinzu:

            /// <summary>
            /// Selected model element
            /// </summary>
            public ISqlModelElement ModelElement
            {
                get
                {
                    return _modelElement;
                }
            }
    
  8. Definieren Sie eine zusätzliche Vorschaugruppe für Änderungen, die von diesem Contributor identifiziert werden:

            /// <summary>
            /// Preview group that change proposals belong to
            /// </summary>
            public RefactoringPreviewGroup SchemaObjectsPreviewGroup
            {
                get
                {
                    return _previewGroup;
                }
                set 
                { 
                    _previewGroup = value; 
                }
            }
    
  9. Die Methode zum Überschreiben von Equals stellt einen Vergleich bereit, der überprüft, ob zwei CasingReferenceContributorInput-Objekte identisch sind:

            /// <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);
            }
    

    Bei diesem Contributor werden die Eingaben als identisch angesehen, wenn sie sich auf das gleiche Modellelement beziehen.

  10. Überschreiben Sie die GetHashCode-Methode:

            /// <summary>
            /// Override GetHashCode
            /// </summary>
            /// <returns></returns>
            public override int GetHashCode()
            {
                Int32 hash = _modelElement.GetHashCode();
                return hash;
            }
    
  11. Klicken Sie im Menü Datei auf CasingReferenceContributorInput.cs speichern.

    Definieren Sie danach die CasingReferenceContributor-Klasse.

So definieren Sie die CasingReferenceContributor-Klasse

  1. Fügen Sie dem Projekt die Klasse CasingReferenceContributor hinzu.

  2. Aktualisieren Sie die using-Anweisungen im Code-Editor wie folgt:

    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;
    
  3. Ändern Sie den Namespace in MySamples.Refactoring:

    namespace MySamples.Refactoring
    
  4. Aktualisieren Sie die Klassendefinition wie folgt:

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

    Geben Sie das Attribut an, um zu deklarieren, dass dieser Contributor mit allen von SqlDatabaseSchemaProvider abgeleiteten Datenbankschema-Anbietern kompatibel ist. Die Klasse muss für die CasingReferenceContributor-Klasse von RefactoringContributor erben.

  5. Definieren Sie zusätzliche Konstanten und private Membervariablen:

            #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;
    

    Die Konstanten stellen Informationen bereit, die im Vorschaufenster angezeigt werden. Der zusätzliche Member wird verwendet, um die Vorschaugruppe nachzuverfolgen.

  6. Fügen Sie den Klassenkonstruktor hinzu:

            public CasingReferenceContributor()
            {
            }
    
  7. Überschreiben Sie die PreviewGroup-Eigenschaft, um die Gruppe zurückzugeben, die beim Erstellen dieses Contributors erstellt wurde:

            #region overrides
            /// <summary>
            /// Preview group for text files
            /// </summary>
            public override RefactoringPreviewGroup PreviewGroup
            {
                get
                {
                    return _previewGroup;
                }
                set
                {
                    _previewGroup = value;
                }
            }
            #endregion
    
  8. Ü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(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;
            }
    

    Die ContributeChangesMethod ruft die GetChangesFromReferencedSymbolScripts-Methode auf.

  9. Implementieren Sie die GetChangesFromReferencedSymbolScripts-Methode, um eine Liste von Änderungsvorschlägen in Skripts zurückzugeben, die Verweise auf das Symbol enthalten, das aktualisiert wird:

            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);
            }
    

    Diese Methode ruft eine Liste aller Abhängigkeiten für das aktualisierte Symbol ab. Anschließend wird die AnalyzeRelationshipEntrySource-Methode für jeden Verweis aufgerufen, um alle zusätzlichen Änderungen zu identifizieren, die erforderlich sind.

  10. Fügen Sie die AnalyzeRelationshipEntrySource-Methode hinzu:

            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;
            }
    

    Diese Methode ruft eine Liste von Änderungen ab, die an Skriptfragmenten vorgenommen werden müssen, die vom aktualisierten Symbol abhängen.

  11. Fügen Sie die GetDependentEntries-Methode hinzu:

            /// <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);
                            }
                        }
                    }
                }
            }
    

    Klicken Sie im Menü Datei auf CasingReferenceContributor.cs speichern.

    Anschließend konfigurieren Sie und erstellen die Assembly.

So signieren und erstellen Sie die Assembly

  1. Klicken Sie im Menü Projekt auf Eigenschaften von CasingRefactoringType.

  2. Klicken Sie auf die Registerkarte Signierung.

  3. Klicken Sie auf Assembly signieren.

  4. Klicken Sie im Feld Schlüsseldatei mit starkem Namen auswählen auf <Neu>.

  5. Geben Sie im Dialogfeld Schlüssel für einen starken Namen erstellen unter Schlüsseldateiname den Namen MyRefKey ein.

  6. (optional) Sie können für die Schlüsseldatei mit starkem Namen ein Kennwort angeben.

  7. Klicken Sie auf OK.

  8. Klicken Sie im Menü Datei auf Alle speichern.

  9. 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.

Installieren und Registrieren der Assembly

So installieren Sie die CasingRefactoringType-Assembly

  1. Erstellen Sie den Ordner MyExtensions im Verzeichnis %Programme%\Microsoft Visual Studio 10.0\VSTSDB\Extensions.

  2. Kopieren Sie die signierte Assembly (CasingRefactoringType.dll) in das Verzeichnis %Programme%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions.

    Tipp

    Es empfiehlt sich, 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 CasingRefactoringType-Assembly

  1. Klicken Sie im Menü Ansicht auf Weitere Fenster, und klicken Sie dann auf Befehlsfenster, um das Befehlsfenster zu öffnen.

  2. 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
    
  3. Drücken Sie die EINGABETASTE.

  4. Kopieren Sie die resultierende Zeile in die Zwischenablage. Die Zeile sollte der folgenden entsprechen:

    "GeneratorAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nnnnnnnnnnnnnnnn"
    
  5. Öffnen Sie einen Text-Editor, z. B. Editor.

    Wichtig

    Öffnen Sie in Windows Vista und in Microsoft Windows Server 2008 den Editor als Administrator, damit Sie die Datei im Ordner "Programme" speichern können.

  6. 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.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>
    

    Mit dieser XML-Datei registrieren Sie die Klasse, die von RefactoringCommand erbt, und alle verwandten Klassen, die von RefactoringContributor abgeleitet werden.

  7. Speichern Sie die Datei unter dem Namen "CasingRefactoringType.extensions.xml" im Verzeichnis %Programme%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions.

  8. Schließen Sie Visual Studio.

    Anschließend erstellen Sie ein sehr einfaches Datenbankprojekt, um den neuen Umgestaltungstyp zu testen.

Testen des neuen Umgestaltungstyps

So erstellen Sie ein Datenbankprojekt

  1. Zeigen Sie im Menü Datei auf Neu, und klicken Sie auf Projekt.

  2. Erweitern Sie unter Installierte Vorlagen den Knoten Datenbank, und klicken Sie dann auf den Knoten SQL Server.

  3. Klicken Sie in der Liste der Vorlagen auf SQL Server 2008-Datenbankprojekt.

  4. Klicken Sie auf OK, um den Projektnamen zu übernehmen und das Projekt zu erstellen.

    Das leere Datenbankprojekt wird erstellt.

So fügen Sie eine Tabelle mit einem Primärschlüssel hinzu

  1. Klicken Sie im Menü Ansicht auf Schemaansicht der Datenbank.

  2. 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.

  3. Geben Sie im Dialogfeld Neues Element hinzufügen unter Name den Namen employee ein.

    Tipp

    Sie verwenden am Anfang des Tabellennamens bewusst einen Kleinbuchstaben.

  4. Klicken Sie auf OK.

  5. 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.

  6. Geben Sie im Dialogfeld Neues Element hinzufügen unter Name die Bezeichnung PK_Employee_column_1 ein.

  7. Klicken Sie auf OK.

    Ändern Sie dann mithilfe des neuen Umgestaltungstyps den Tabellennamen und sämtliche Verweise auf diesen.

So aktualisieren Sie den Tabellennamen mit dem neuen Umgestaltungstyp

  1. Klicken Sie in der Schemaansicht mit der rechten Maustaste auf den Tabellenknoten "employee", zeigen Sie auf Umgestalten, und klicken Sie auf Ersten Buchstaben groß schreiben.

    Sie haben den neuen Umgestaltungstyp in dieser exemplarischen Vorgehensweise definiert.

  2. Überprüfen Sie die Änderungen im Dialogfeld Vorschau der Änderungen, und klicken Sie dann auf Übernehmen.

    Der Tabellenname wird aktualisiert und lautet nun "Employee". Der Verweis auf die Tabelle im Primärschlüssel wird ebenfalls aktualisiert.

Nächste Schritte

Sie können zusätzlich eigene Typen für die Datenbankumgestaltung erstellen. Sie können auch weitere Contributors hinzufügen, um einen vorhandenen Typ für die Datenbankumgestaltung zur Verwendung mit weiteren Datei- oder Objekttypen zu aktivieren.

Siehe auch

Aufgaben

Exemplarische Vorgehensweise: Erweitern der Datenbankumbenennung durch Umgestaltung auf die Verarbeitung von Textdateien

Konzepte

Erstellen benutzerdefinierter Typen oder Regeln für die Datenbankumgestaltung

Umgestalten von Datenbankcode und Daten