Partager via


Procédure pas à pas : création d'un nouveau type de refactorisation de base de données pour modifier la casse

Cette procédure pas à pas décrit comment créer, installer, enregistrer et tester un nouveau type de refactorisation de base de données. Cette opération de refactorisation convertira le premier caractère du nom de l'objet spécifié en majuscule, et il mettra à jour toutes les références à ce nom mis à jour.

Cette procédure pas à pas décrit les tâches suivantes :

  1. Créer un nouvel assembly contenant les classes pour un type de refactorisation personnalisé.

  2. Installer et enregistrer l'assembly pour que le type de refactorisation soit disponible dans Visual Studio Premium ou Visual Studio Ultimate.

  3. Créer un projet de base de données simple pour tester si le type de refactorisation fonctionne comme prévu.

Composants requis

Pour exécuter cette procédure pas à pas, vous devez disposer des composants suivants :

Création d'un assembly avec un type de refactorisation personnalisé

Pour créer un type de refactorisation personnalisé qui peut convertir la première lettre d'un nom d'objet en majuscule puis mettre à jour toutes les références à cet objet, vous devez implémenter six classes :

  • CasingRefactoringCommand : cette classe fournit le nom de commande pour le menu de refactorisation, spécifie sur quels éléments de modèle votre type de refactorisation est disponible, et appelle votre opération de refactorisation lorsque l'utilisateur clique sur la commande.

  • CasingRefactoringOperation : cette classe précise le mode d'interaction entre votre opération de refactorisation et la fenêtre d'aperçu, spécifie les propriétés décrivant l'opération et crée la classe CasingContributorInput.

  • CasingContributorInput : cette classe stocke les données d'entrée à la classe CasingSymbolContributor.

  • CasingSymbolContributor : cette classe génère la liste de propositions de modifications pour le symbole renommé, et il crée également la classe CasingReferenceContributorInput pour gérer la mise à jour des références à l'objet dont le nom est modifié.

  • CasingReferenceContributorInput : cette classe stocke les données d'entrée à la classe CasingReferenceContributor.

  • CasingReferenceContributor : cette classe génère la liste de propositions de modifications associées à la mise à jour de références au symbole renommé.

Avant de créer ces classes, vous allez créer une bibliothèque de classes, ajouter les références requises et prévoir le code de la fonction d'assistance pour simplifier certaines lignes de code que vous écrirez par la suite.

Pour créer la bibliothèque de classes et le code de la fonction d'assistance

  1. Créez un projet de bibliothèque de classes C# et nommez-le CasingRefactoringType.csproj.

  2. Ajoutez des références aux bibliothèques de classes suivantes :

    • 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. Ajoutez des références aux assemblys suivants du Kit de développement Visual Studio 2010 SDK :

    • Microsoft.VisualStudio.OLE.Interop.dll

    • Microsoft.VisualStudio.Shell.10.0.dll

    • Microsoft.VisualStudio.Shell.Interop.dll

    • Microsoft.VisualStudio.Shell.Interop.8.0.dll

    • Microsoft.VisualStudio.Shell.Interop.9.0.dll

    • Microsoft.VisualStudio.Shell.Interop.10.0.dll

    • Microsoft.VisualStudio.TextManager.Interop.dll

  4. Dans l'Explorateur de solutions, remplacez le nom Class1.cs par SampleHelper.cs.

  5. Double-cliquez sur SampleHelper.cs pour l'ouvrir dans l'éditeur de code.

  6. Remplacez le contenu de l'éditeur de code par le code suivant :

    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. Dans le menu Fichier, cliquez sur Enregistrer SampleHelper.cs.

  8. Ajoutez une nouvelle classe intitulée RawChangeInfo à votre projet.

  9. Dans l'éditeur de code, mettez à jour le code comme suit :

    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. Dans le menu Fichier, cliquez sur Enregistrer RawChangeInfo.cs.

    Il convient maintenant de définir la classe CasingRefactoringCommand.

Pour définir la classe CasingRefactoringCommand

  1. Ajoutez une classe intitulée CasingRefactorCommand à votre projet.

  2. Dans l'éditeur de code, mettez à jour les instructions using comme suit :

    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. Remplacez l'espace de noms par MySamples.Refactoring :

    namespace MySamples.Refactoring
    
  4. Mettez à jour la définition de classe comme suit :

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

    L'attribut est utilisé pour spécifier les fournisseurs de schémas de base de données avec lesquels ce type de refactorisation est compatible. Pour cet exemple, votre nouveau type de refactorisation fonctionnera avec tout fournisseur dérivé de SqlDatabaseSchemaProvider. Votre classe hérite RefactoringSchemaViewNodeCommand. Hériter de cette classe de base indique que votre type de refactorisation est disponible sur les nœuds spécifiés dans la Vue Schéma. D'autres types de refactorisation peuvent être définis pour fonctionner sur les nœuds de fichiers et les nœuds de projets.

  5. Ensuite, ajoutez la méthode override suivante à votre classe :

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

    Cette méthode fournit le comportement lorsque l'utilisateur applique votre commande de refactorisation dans la Vue Schéma.

  6. Ensuite, ajoutez la méthode override suivante à votre classe :

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

    Cette méthode détermine sur quels nœuds dans la Vue Schéma votre commande de refactorisation est disponible.

  7. Enfin, ajoutez la méthode override suivante à votre classe :

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

    Cette méthode fournit le nom convivial pour la commande de refactorisation qui apparaît dans le menu de refactorisation.

  8. Dans le menu Fichier, cliquez sur Enregistrer CasingRefactoringCommand.cs.

    Il convient maintenant de définir la classe CasingRefactoringOperation.

Pour définir la classe CasingRefactoringOperation

  1. Ajoutez une nouvelle classe intitulée CasingRefactoringOperation à votre projet.

  2. Dans l'éditeur de code, mettez à jour les instructions using comme suit :

    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. Remplacez l'espace de noms par MySamples.Refactoring :

    namespace MySamples.Refactoring
    
  4. Mettez à jour la définition de classe comme suit :

        internal class CasingRefactorOperation : RefactoringOperation
        {
    }
    

    Votre classe doit hériter de RefactoringOperation.

  5. Ajoutez les déclarations de constantes et de variables membres suivantes à votre classe :

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

    Les constantes privées fournissent les informations à propos de cette opération qui seront affichées dans la fenêtre d'aperçu.

  6. Ajoutez le constructeur pour votre classe :

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

    Le constructeur initialise le nom d'opération et l'élément de modèle, si ceux-ci ont été spécifiés.

  7. Substituez la propriété PreviewWindowInfo pour obtenir les valeurs qui s'afficheront dans la fenêtre d'aperçu lorsque l'utilisateur applique votre type de refactorisation :

            /// <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. Fournissez des définitions de propriétés supplémentaires :

            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. Enfin, vous pouvez substituer la méthode OnGetContributorInput :

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

    Cette méthode crée le ContributorInput passé aux collaborateurs de refactorisation pour ce type de refactorisation.

  10. Dans le menu Fichier, cliquez sur Enregistrer CasingRefactoringOperation.cs.

    Il convient maintenant de définir la classe CasingContributorInput.

Pour définir la classe CasingContributorInput

  1. Ajoutez une classe intitulée CasingContributorInput à votre projet.

  2. Dans l'éditeur de code, mettez à jour les instructions using comme suit :

    using System;
    using Microsoft.Data.Schema.Sql.SchemaModel;
    using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
    
  3. Remplacez l'espace de noms par MySamples.Refactoring :

    namespace MySamples.Refactoring
    
  4. Mettez à jour la définition de classe comme suit :

        internal class CasingContributorInput: ContributorInput
        {
        }
    

    Votre classe doit hériter de ContributorInput.

  5. Définissez une variable membre privé supplémentaire :

            private ISqlModelElement _modelElement;
    

    Ce membre est utilisé pour effectuer le suivi de l'élément de modèle sur lequel vous fonctionnez.

  6. Ajoutez le constructeur de classe :

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

    Le constructeur initialise l'élément de modèle.

  7. Ajoutez une propriété publique en lecture seule pour l'élément de modèle :

            /// <summary>
            /// Selected model element
            /// </summary>
            public ISqlModelElement ModelElement
            {
                get 
    
                {
                    return _modelElement;
                }
            }
    
  8. La méthode Remplacer Equals fournit une comparaison qui détermine si deux objets CasingContributorInput sont les mêmes :

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

    Pour ce collaborateur, les entrées seront considérées de la même façon si elles s'appliquent au même élément de modèle.

  9. Substituez la méthode GetHashCode :

            /// <summary>
            /// Override GetHashCode
            /// </summary>
            /// <returns></returns>
            public override int GetHashCode()
            {
                Int32 hash = _modelElement.GetHashCode();
                return hash;
            }
    
  10. Dans le menu Fichier, cliquez sur Enregistrer CasingContributorInput.cs.

    Il convient maintenant de définir la classe CasingSymbolContributor.

Pour définir la classe CasingSymbolContributor

  1. Ajoutez une classe intitulée CasingSymbolContributor à votre projet.

  2. Dans l'éditeur de code, mettez à jour les instructions using comme suit :

    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. Remplacez l'espace de noms par MySamples.Refactoring :

    namespace MySamples.Refactoring
    
  4. Mettez à jour la définition de classe comme suit :

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

    Spécifiez l'attribut permettant de déclarer que ce collaborateur est compatible avec tous les fournisseurs de schémas de base de données dérivés de SqlDatabaseSchemaProvider. Votre classe doit hériter de RefactoringContributor pour votre classe CasingContributorInput.

  5. Définissez des constantes et des variables membres privés supplémentaires :

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

    Les constantes fournissent des informations qui s'afficheront dans la fenêtre d'aperçu. Le membre supplémentaire est utilisé pour effectuer le suivi du groupe de visualisation.

  6. Ajoutez le constructeur de classe :

            #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
    

    Le constructeur initialise l'élément de modèle en créant un groupe d'aperçu et en initialisant ses propriétés. Vous pourriez enregistrer également des icônes à afficher dans la fenêtre d'aperçu pour les extensions de noms de fichiers spécifiques, et vous pouvez enregistrer un service de langage utilisé pour fournir la couleur de syntaxe pour les fichiers qui ont une extension spécifiée.

  7. Substituez la propriété PreviewGroup pour renvoyer le groupe généré au moment de la création de ce collaborateur :

            #region overrides
            /// <summary>
            /// Preview group for schema object files
            /// </summary>
            public override RefactoringPreviewGroup PreviewGroup
            {
                get
                {
                    return _previewGroup;
                }
                set
                {
                    _previewGroup = value;
                }
            }
    
  8. Substituez la méthode ContributeChanges(Boolean) pour renvoyer une liste de propositions de modifications :

            /// <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. Ensuite, créez un autre type de ContributorInput :

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

    Ce type supplémentaire de ContributorInput est utilisé pour gérer toutes les références à l'élément dont le symbole a été mis à jour. Cette méthode sera appelée par la méthode suivante.

  10. Ajoutez une méthode pour générer une liste de modifications pour le script qui contient la définition pour le symbole qui est refactorisé :

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

    Cette méthode appelle la méthode AnalyzeScript pour traiter les éléments de script.

  11. Ajoutez la méthode AnalyzeScript :

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

    Cette méthode acquiert le script source pour l'élément qui est refactorisé. Elle extrait ensuite le fragment SQL pour ce script source. Ensuite, elle crée une nouvelle liste de modifications, détermine l'identificateur pour l'élément (selon le type de fragment) et ajoute la nouvelle modification à la liste de modifications.

  12. Dans le menu Fichier, cliquez sur Enregistrer CasingSymbolContributor.cs.

    Il convient maintenant de définir la classe CasingReferenceContributorInput.

Pour définir la classe CasingReferenceContributorInput

  1. Ajoutez une classe intitulée CasingReferenceContributorInput à votre projet.

  2. Dans l'éditeur de code, mettez à jour les instructions using comme suit :

    using System;
    using Microsoft.Data.Schema.Sql.SchemaModel;
    using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
    
  3. Remplacez l'espace de noms par MySamples.Refactoring :

    namespace MySamples.Refactoring
    
  4. Mettez à jour la définition de classe comme suit :

        internal class CasingReferenceContributorInput: ContributorInput
        {        
        }        
    

    Votre classe doit hériter de ContributorInput.

  5. Définissez des variables membres privés supplémentaires :

            private ISqlModelElement _modelElement;
            private RefactoringPreviewGroup _previewGroup;
    

    Ces membres sont utilisés pour effectuer le suivi de l'élément de modèle sur lequel vous fonctionnez et le groupe de visualisation auquel les modifications appartiennent.

  6. Ajoutez le constructeur de classe :

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

    Le constructeur initialise l'élément de modèle.

  7. Ajoutez une propriété publique en lecture seule pour l'élément de modèle :

            /// <summary>
            /// Selected model element
            /// </summary>
            public ISqlModelElement ModelElement
            {
                get
                {
                    return _modelElement;
                }
            }
    
  8. Définissez un groupe de visualisation supplémentaire pour les modifications identifiées par ce collaborateur :

            /// <summary>
            /// Preview group that change proposals belong to
            /// </summary>
            public RefactoringPreviewGroup SchemaObjectsPreviewGroup
            {
                get
                {
                    return _previewGroup;
                }
                set 
                { 
                    _previewGroup = value; 
                }
            }
    
  9. La méthode Remplacer Equals fournit une comparaison qui détermine si deux objets CasingReferenceContributorInput sont les mêmes :

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

    Pour ce collaborateur, les entrées seront considérées de la même façon si elles s'appliquent au même élément de modèle.

  10. Substituez la méthode GetHashCode :

            /// <summary>
            /// Override GetHashCode
            /// </summary>
            /// <returns></returns>
            public override int GetHashCode()
            {
                Int32 hash = _modelElement.GetHashCode();
                return hash;
            }
    
  11. Dans le menu Fichier, cliquez sur Enregistrer CasingReferenceContributorInput.cs.

    Il convient maintenant de définir la classe CasingReferenceContributor.

Pour définir la classe CasingReferenceContributor

  1. Ajoutez une classe intitulée CasingReferenceContributor à votre projet.

  2. Dans l'éditeur de code, mettez à jour les instructions using comme suit :

    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. Remplacez l'espace de noms par MySamples.Refactoring :

    namespace MySamples.Refactoring
    
  4. Mettez à jour la définition de classe comme suit :

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

    Spécifiez l'attribut permettant de déclarer que ce collaborateur est compatible avec tous les fournisseurs de schémas de base de données dérivés de SqlDatabaseSchemaProvider. Votre classe doit hériter de RefactoringContributor pour votre classe CasingReferenceContributorInput.

  5. Définissez des constantes et des variables membres privés supplémentaires :

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

    Les constantes fournissent des informations qui s'afficheront dans la fenêtre d'aperçu. Le membre supplémentaire est utilisé pour effectuer le suivi du groupe de visualisation.

  6. Ajoutez le constructeur de classe :

            public CasingReferenceContributor()
            {
            }
    
  7. Substituez la propriété PreviewGroup pour renvoyer le groupe généré au moment de la création de ce collaborateur :

            #region overrides
            /// <summary>
            /// Preview group for text files
            /// </summary>
            public override RefactoringPreviewGroup PreviewGroup
            {
                get
                {
                    return _previewGroup;
                }
                set
                {
                    _previewGroup = value;
                }
            }
            #endregion
    
  8. Substituez la méthode ContributeChanges(Boolean) pour renvoyer une liste de propositions de modifications :

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

    Le ContributeChangesMethod appelle la méthode GetChangesFromReferencedSymbolScripts.

  9. Implémentez la méthode GetChangesFromReferencedSymbolScripts pour retourner une liste de propositions de modifications dans les scripts qui contiennent des références au symbole qui est mis à jour :

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

    Cette méthode extrait une liste de toutes les dépendances du symbole mis à jour. Il appelle alors la méthode AnalyzeRelationshipEntrySource pour chaque référence afin d'identifier toutes modifications supplémentaires qui sont obligatoires.

  10. Ajoutez la méthode AnalyzeRelationshipEntrySource :

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

    Cette méthode extrait une liste des modifications qui doivent être apportées aux fragments de script qui dépendent du symbole mis à jour.

  11. Ajoutez la méthode GetDependentEntries :

            /// <summary>
            ///  Get all relating relationship entries for the model element and its composing and hierarchical children
            /// </summary>
            internal static List<System.Tuple<ISourceInformation, IModelRelationshipEntry>> GetDependentEntries(
                SqlSchemaModel dataSchemaModel,
                ISqlModelElement modelElement,
                bool ignoreComposedRelationship,
                bool includeChildDependencies)
            {
                SampleHelper.CheckNullArgument(dataSchemaModel, "dataSchemaModel");
                SampleHelper.CheckNullArgument(modelElement, "modelElement");
    
                var dependencies = new List<System.Tuple<ISourceInformation, IModelRelationshipEntry>>();
    
                List<IModelRelationshipEntry> relatingRelationships = new List<IModelRelationshipEntry>();
                GetDependentEntries(modelElement,
                                    dataSchemaModel,
                                    new Dictionary<IModelElement, Object>(),
                                    relatingRelationships,
                                    includeChildDependencies);
    
                foreach (IModelRelationshipEntry entry in relatingRelationships)
                {
                    ModelRelationshipType relationshipType = entry.RelationshipClass.ModelRelationshipType;
                    if (!ignoreComposedRelationship ||
                        (relationshipType != ModelRelationshipType.Composing))
                    {
                        ISqlModelElement relatingElement = entry.FromElement as ISqlModelElement;
                        Debug.Assert(relatingElement != null, "Relating element got from ModelStore is null.");
    
                        foreach (var si in relatingElement.GetRelationshipEntrySources(entry))
                        {
                            dependencies.Add(new System.Tuple<ISourceInformation, IModelRelationshipEntry>(si, entry));
                        }
                    }
                }
                return dependencies;
            }
    
            private static void GetDependentEntries(
                IModelElement modelElement,
                DataSchemaModel dataSchemaModel,
                Dictionary<IModelElement, Object> visitElement,
                List<IModelRelationshipEntry> relationshipEntries,
                Boolean includeChildDependencies)
            {
                if (modelElement != null &&
                    !visitElement.ContainsKey(modelElement))
                {
                    visitElement[modelElement] = null;
    
                    IList<IModelRelationshipEntry> relatingRelationships = modelElement.GetReferencingRelationshipEntries();
                    relationshipEntries.AddRange(relatingRelationships);
    
                    if (includeChildDependencies)
                    {
                        // First loop through all composed children of this element, and get their relationship entries as well
                        foreach (IModelRelationshipEntry entry in modelElement.GetReferencedRelationshipEntries())
                        {
                            if (entry.RelationshipClass.ModelRelationshipType == ModelRelationshipType.Composing)
                            {
                                GetDependentEntries(entry.Element, dataSchemaModel, visitElement, relationshipEntries, includeChildDependencies);
                            }
                        }
    
                        // Then loop through all hierarchical children of this element, add their dependents to the list.
                        foreach (IModelRelationshipEntry entry in relatingRelationships)
                        {
                            if (entry.RelationshipClass.ModelRelationshipType == ModelRelationshipType.Hierarchical)
                            {
                                GetDependentEntries(entry.FromElement, dataSchemaModel, visitElement, relationshipEntries, includeChildDependencies);
                            }
                        }
                    }
                }
            }
    

    Dans le menu Fichier, cliquez sur Enregistrer CasingReferenceContributor.cs.

    L'étape suivante consiste à configurer et à générer l'assembly.

Pour signer et générer l'assembly

  1. Dans le menu Projet, cliquez sur Propriétés de CasingRefactoringType.

  2. Cliquez sur l'onglet Signature.

  3. Cliquez sur Signer l'assembly.

  4. Dans la zone Choisir un fichier de clé de nom fort, cliquez sur <Nouveau>.

  5. Dans la boîte de dialogue Créer une clé de nom fort, en guise de nom du fichier de clé, tapez MaCléRéf.

  6. (Facultatif) Vous pouvez spécifier un mot de passe pour votre fichier de clé de nom fort.

  7. Cliquez sur OK.

  8. Dans le menu Fichier, cliquez sur Enregistrer tout.

  9. Dans le menu Générer, cliquez sur Générer la solution.

    Vous devez, à présent, installer et enregistrer l'assembly pour qu'il apparaisse comme une condition de test disponible.

Installation et enregistrement de l'assembly

Pour installer l'assembly CasingRefactoringType

  1. Créez un dossier nommé MesExtensions dans le dossier %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions.

  2. Copiez votre assembly signé (CasingRefactoringType.dll) dans le dossier %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MesExtensions.

    Notes

    Évitez de copier directement vos fichiers XML dans le dossier %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions. L'intérêt d'utiliser un sous-dossier est d'empêcher toute modification accidentelle apportée aux autres fichiers fournis avec Visual Studio.

    Veuillez maintenant enregistrer votre assembly, un type d'extension de fonctionnalité, pour le faire apparaître dans Visual Studio.

Pour enregistrer l'assembly CasingRefactoringType

  1. Dans le menu Affichage, cliquez sur Autres fenêtres, puis cliquez sur Fenêtre Commande pour ouvrir la fenêtre Commande.

  2. Dans la fenêtre Commande, tapez le code suivant. Pour FilePath, substituez le chemin d'accès et le nom de votre fichier .dll compilé. Placez le chemin d'accès et le nom de fichier entre guillemets.

    Notes

    Par défaut, le chemin d'accès de votre fichier .dll compilé est CheminVotreSolution\bin\Debug ou CheminVotreSolution\bin\Release.

    ? System.Reflection.Assembly.LoadFrom("FilePath").FullName
    
    ? System.Reflection.Assembly.LoadFrom(@"FilePath").FullName
    
  3. Appuyez sur Entrée.

  4. Copiez la ligne résultante dans le Presse-papiers. Cette ligne doit se présenter comme suit :

    "GeneratorAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nnnnnnnnnnnnnnnn"
    
  5. Ouvrez un éditeur de texte brut, tel que le Bloc-notes.

    Important

    Sur Windows Vista et Microsoft Windows Server 2008, ouvrez l'éditeur en tant qu'administrateur afin de pouvoir enregistrer le fichier dans votre dossier Program Files.

  6. Fournissez les informations suivantes, en spécifiant vos propres nom d'assembly, jeton de clé publique et type d'extension :

    <?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>
    

    Vous utilisez ce fichier XML pour enregistrer la classe qui hérite de RefactoringCommand et toutes les classes connexes dérivées de RefactoringContributor.

  7. Enregistrez le fichier sous le nom CasingRefactoringType.extensions.xml dans le dossier %Program Files%\\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MesExtensions.

  8. Fermez Visual Studio.

    Créez ensuite un projet de base de données très simple pour tester votre nouveau type de refactorisation.

Test du nouveau type de refactorisation

Pour créer un projet de base de données

  1. Dans le menu Fichier, pointez sur Nouveau, puis cliquez sur Projet.

  2. Sous Modèles installés, développez le nœud Base de données, puis cliquez sur le nœud SQL Server.

  3. Dans la liste des modèles, cliquez sur Projet de base de données SQL Server 2008.

  4. Cliquez sur OK pour accepter le nom du projet par défaut et créer le projet.

    Le projet de base de données vide est créé.

Pour ajouter une table avec une clé primaire

  1. Dans le menu Affichage, cliquez sur Vue Schéma de base de données.

  2. Dans la vue Schéma, développez le nœud Schémas, puis le nœud dbo, cliquez avec le bouton droit sur le nœud Tables, pointez sur Ajouter et cliquez sur Table.

  3. Dans la boîte de dialogue Ajouter un nouvel élément, en guise de Nom, tapez employee.

    Notes

    Vous utilisez intentionnellement une lettre minuscule comme initiale du nom de la table.

  4. Cliquez sur OK.

  5. Développez le nœud Tables, cliquez avec le bouton droit sur le nœud employee, pointez sur Ajouter et cliquez sur Clé primaire.

  6. Dans la boîte de dialogue Ajouter un nouvel élément, en guise de Nom, tapez Employé_CP_colonne_1.

  7. Cliquez sur OK.

    Pour finir, servez-vous du nouveau type de refactorisation pour modifier le nom de la table et toutes les références qu'elle contient.

Pour utiliser le nouveau type de refactorisation afin de mettre à jour le nom de la table

  1. Dans Vue Schéma, cliquez avec le bouton droit sur le nœud de la table d'employé, pointez sur Refactoriser, puis cliquez sur Mettre la première lettre en majuscule.

    Vous avez défini ce nouveau type de refactorisation dans cette procédure pas à pas.

  2. Dans la boîte de dialogue Aperçu des modifications, examinez les modifications, puis cliquez sur Appliquer.

    Le nom de la table est mis à jour et devient Employee. La référence à cette table dans la clé primaire est également mise à jour.

Étapes suivantes

Vous pouvez créer vos propres types supplémentaires de refactorisation de base de données. Vous pouvez également ajouter davantage de collaborateurs pour permettre à un type existant de refactorisation de base de données de fonctionner sur les types de fichiers ou les objets supplémentaires.

Voir aussi

Tâches

Procédure pas à pas : extension de la refactorisation de changement de nom de base de données en vue d'une exécution sur des fichiers texte

Concepts

Créer des types ou cibles de refactorisation de base de données personnalisés

Refactoriser le code et les données d'une base de données