Partager via


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

Cette procédure pas à pas décrit comment créer, installer, enregistrer et tester un nouveau collaborateur pour la refactorisation de changement de nom. Cette cible de refactorisation a pour effet d'étendre les fonctionnalités de Visual Studio Premium ou Visual Studio Ultimate pour permettre à votre refactorisation de base de données de renommer des références aux objets de base de données contenus dans des fichiers texte au sein de votre projet de base de données.

Lorsque vous appliquez un nouveau collaborateur de refactorisation à un type de refactorisation existant, il doit utiliser une classe d'entrée de collaborateur existante.

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

  1. Définition d'un nouvel assembly contenant les classes pour une cible de refactorisation personnalisée.

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

  3. Création d'un projet de base de données simple pour tester le fonctionnement de la cible de refactorisation.

Composants requis

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

Création d'un assembly avec une cible de refactorisation personnalisée

Pour créer une cible de refactorisation personnalisée permettant d'appliquer la refactorisation de changement de nom aux fichiers texte, vous devez implémenter une classe afin de fournir un nouveau RefactoringContributor :

  • RenameReferenceTextContributorContributor — Cette classe génère la liste de propositions de modifications pour le symbole renommé. Les propositions de modifications concernent chaque référence citée dans un fichier texte du projet de base de données.

Avant de définir cette classe, 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 RenameTextContributor.csproj.

  2. Ajoutez des références aux assemblys .NET suivants :

    • Microsoft.Data.Schema

    • Microsoft.Data.Schema.ScriptDom

    • Microsoft.Data.Schema.ScriptDom.sql

    • Microsoft.Data.Schema.Sql

  3. Ajoutez des références aux assemblys suivants que vous trouverez dans le dossier %Program Files%\Microsoft Visual Studio 10.0\VSTSDB :

    • Microsoft.Data.Schema.Tools.Sql.dll

    • Microsoft.VisualStudio.Data.Schema.Package.dll

    • Microsoft.VisualStudio.Data.Schema.Package.Sql.dll

  4. 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.TextManager.Interop.dll

    • Microsoft.VisualStudio.Shell.Interop.dll

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

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

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

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

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

    Notes

    Cette classe d'assistance est identique au texte d'assistance utilisé dans la procédure pas à pas relative aux types de refactorisation personnalisés. Pour gagner du temps, contentez-vous de copier le code source de ce projet dans le nouveau projet.

  7. 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. For example, if recursion started with the Solution
            /// node, then : Level 0 -- Solution node, Level 1 -- children of Solution, etc.</param>
            /// <param name="hierIsSolution">true if hierarchy is Solution Node. </param>
            /// <param name="visibleNodesOnly">true if only nodes visible in the Solution Explorer should
            /// be traversed. false if all project items should be traversed.</param>
            private static void EnumProjectItems(IVsHierarchy hierarchy,
                                                string fileExtension,
                                                List<string> files,
                                                uint itemid,
                                                int recursionLevel,
                                                bool hierIsSolution,
                                                bool visibleNodesOnly)
            {
                int hr;
                IntPtr nestedHierarchyObj;
                uint nestedItemId;
                Guid hierGuid = typeof(IVsHierarchy).GUID;
    
    
                // Check first if this node has a nested hierarchy. 
                hr = hierarchy.GetNestedHierarchy(itemid, ref hierGuid, out nestedHierarchyObj, out nestedItemId);
                if (VSConstants.S_OK == hr && IntPtr.Zero != nestedHierarchyObj)
                {
                    IVsHierarchy nestedHierarchy = Marshal.GetObjectForIUnknown(nestedHierarchyObj) as IVsHierarchy;
                    Marshal.Release(nestedHierarchyObj);
                    if (nestedHierarchy != null)
                    {
                        EnumProjectItems(nestedHierarchy, fileExtension, files,
                                        nestedItemId,
                                        recursionLevel,
                                        false,
                                        visibleNodesOnly);
                    }
                }
                else
                {
                    // Check if the file extension of this node matches 
                    string fileFullPath;
                    hierarchy.GetCanonicalName(itemid, out fileFullPath);
                    if (CompareExtension(fileFullPath, fileExtension))
                    {
                        // add matched file paths into the list
                        files.Add(fileFullPath);
                    }
    
                    recursionLevel++;
    
                    //Get the first child node of the current hierarchy being walked
                    object pVar;
                    hr = hierarchy.GetProperty(itemid,
                        ((visibleNodesOnly || (hierIsSolution && recursionLevel == 1) ?
                            (int)__VSHPROPID.VSHPROPID_FirstVisibleChild : (int)__VSHPROPID.VSHPROPID_FirstChild)),
                        out pVar);
                    ErrorHandler.ThrowOnFailure(hr);
                    if (VSConstants.S_OK == hr)
                    {
                        // Use Depth first search so at each level we recurse to check if the node has any children
                        // and then look for siblings.
                        uint childId = GetItemId(pVar);
                        while (childId != VSConstants.VSITEMID_NIL)
                        {
                            EnumProjectItems(hierarchy, fileExtension, files, childId, recursionLevel, false, visibleNodesOnly);
                            hr = hierarchy.GetProperty(childId,
                                ((visibleNodesOnly || (hierIsSolution && recursionLevel == 1)) ?
                                    (int)__VSHPROPID.VSHPROPID_NextVisibleSibling : (int)__VSHPROPID.VSHPROPID_NextSibling),
                                out pVar);
                            if (VSConstants.S_OK == hr)
                            {
                                childId = GetItemId(pVar);
                            }
                            else
                            {
                                ErrorHandler.ThrowOnFailure(hr);
                                break;
                            }
                        }
                    }
                }
            }
    
            /// <summary>
            /// Gets the item id.
            /// </summary>
            /// <param name="pvar">VARIANT holding an itemid.</param>
            /// <returns>Item Id of the concerned node</returns>
            private static uint GetItemId(object pvar)
            {
                if (pvar == null) return VSConstants.VSITEMID_NIL;
                if (pvar is int) return (uint)(int)pvar;
                if (pvar is uint) return (uint)pvar;
                if (pvar is short) return (uint)(short)pvar;
                if (pvar is ushort) return (uint)(ushort)pvar;
                if (pvar is long) return (uint)(long)pvar;
                return VSConstants.VSITEMID_NIL;
            }
    
            /// <summary>
            /// Check if the file has the expected extension.
            /// </summary>
            /// <param name="filePath"></param>
            /// <param name="extension"></param>
            /// <returns></returns>
            public static bool CompareExtension(string filePath, string extension)
            {
                bool equals = false;
                if (!string.IsNullOrEmpty(filePath))
                {
                    equals = (string.Compare(System.IO.Path.GetExtension(filePath), extension, StringComparison.OrdinalIgnoreCase) == 0);
                }
                return equals;
            }
    
            /// <summary>
            /// Read file content from a file
            /// </summary>
            /// <param name="filePath"> file path </param>
            /// <returns> file content in a string </returns>
            internal static string ReadFileContent(string filePath)
            {
                //  Ensure that the file exists first.
                if (!File.Exists(filePath))
                {
                    Debug.WriteLine(string.Format("Cannot find the file: '{0}'", filePath));
                    return string.Empty;
                }
    
                string content;
                using (StreamReader reader = new StreamReader(filePath))
                {
                    content = reader.ReadToEnd();
                    reader.Close();
                }
                return content;
            }
    
            /// <summary>
            ///  Check null references and throw
            /// </summary>
            /// <param name="obj"></param>
            /// <param name="?"></param>
            public static void CheckNullArgument(object obj, string objectName)
            {
                if (obj == null)
                {
                    throw new System.ArgumentNullException(objectName);
                }
            }
    
            /// <summary>
            /// Get offset of the fragment from an Identifier if the identifier.value matches the
            /// name we are looking for.
            /// </summary>
            /// <param name="identifier"></param>
            /// <param name="expectedName"></param>
            public static RawChangeInfo AddOffsestFromIdentifier(
               Identifier identifier,
                String expectedName,
                String newName,
                Boolean keepOldQuote)
            {
                RawChangeInfo change = null;
                if (identifier != null && String.Compare(expectedName, identifier.Value, true) == 0)
                {
                    if (keepOldQuote)
                    {
                        QuoteType newQuote = QuoteType.NotQuoted;
                        newName = Identifier.DecodeIdentifier(newName, out newQuote);
                        newName = Identifier.EncodeIdentifier(newName, identifier.QuoteType);
                    }
                    change = new RawChangeInfo(identifier.StartOffset, identifier.FragmentLength, expectedName, newName);
                }
                return change;
            }
    
            public static IList<ChangeProposal> ConvertOffsets(
                string projectFullName,
                string fileFullPath,
                List<RawChangeInfo> changes,
                bool defaultIncluded)
            {
                // Get the file content into IVsTextLines
                IVsTextLines textLines = GetTextLines(fileFullPath);
    
                int changesCount = changes.Count;
                List<ChangeProposal> changeProposals = new List<ChangeProposal>(changesCount);
                for (int changeIndex = 0; changeIndex < changesCount; changeIndex++)
                {
                    int startLine = 0;
                    int startColumn = 0;
                    int endLine = 0;
                    int endColumn = 0;
    
    
                    RawChangeInfo currentChange = changes[changeIndex];
                    int startPosition = currentChange.StartOffset;
                    int endPosition = currentChange.StartOffset + currentChange.Length;
                    int result = textLines.GetLineIndexOfPosition(startPosition, out startLine, out startColumn);
                    if (result == VSConstants.S_OK)
                    {
                        result = textLines.GetLineIndexOfPosition(endPosition, out endLine, out endColumn);
                        if (result == VSConstants.S_OK)
                        {
                            TextChangeProposal changeProposal = new TextChangeProposal(projectFullName, fileFullPath, currentChange.NewText);
                            changeProposal.StartLine = startLine;
                            changeProposal.StartColumn = startColumn;
                            changeProposal.EndLine = endLine;
                            changeProposal.EndColumn = endColumn;
                            changeProposal.Included = defaultIncluded;
                            changeProposals.Add(changeProposal);
                        }
                    }
    
                    if (result != VSConstants.S_OK)
                    {
                        throw new InvalidOperationException("Failed to convert offset");
                    }
                }
                return changeProposals;
            }
    
            /// <summary>
            /// Get IVsTextLines from a file.  If that file is in RDT, get text buffer from it.
            /// If the file is not in RDT, open that file in invisible editor and get text buffer
            /// from it.
            /// If failed to get text buffer, it will return null.        
            /// </summary>
            /// <param name="fullPathFileName">File name with full path.</param>
            /// <returns>Text buffer for that file.</returns>
            private static IVsTextLines GetTextLines(string fullPathFileName)
            {
                System.IServiceProvider serviceProvider = DataPackage.Instance;
                IVsTextLines textLines = null;
                IVsRunningDocumentTable rdt = (IVsRunningDocumentTable)serviceProvider.GetService(typeof(SVsRunningDocumentTable));
    
                if (rdt != null)
                {
                    IVsHierarchy ppHier = null;
                    uint pitemid, pdwCookie;
                    IntPtr ppunkDocData = IntPtr.Zero;
                    try
                    {
                        rdt.FindAndLockDocument((uint)(_VSRDTFLAGS.RDT_NoLock), fullPathFileName, out ppHier, out pitemid, out ppunkDocData, out pdwCookie);
                        if (pdwCookie != 0)
                        {
                            if (ppunkDocData != IntPtr.Zero)
                            {
                                try
                                {
                                    // Get text lines from the doc data
                                    IVsPersistDocData docData = (IVsPersistDocData)Marshal.GetObjectForIUnknown(ppunkDocData);
    
                                    if (docData is IVsTextLines)
                                    {
                                        textLines = (IVsTextLines)docData;
                                    }
                                    else
                                    {
                                        textLines = null;
                                    }
                                }
                                catch (ArgumentException)
                                {
                                    // Do nothing here, it will return null stream at the end.
                                }
                            }
                        }
                        else
                        {
                            // The file is not in RDT, open it in invisible editor and get the text lines from it.
                            IVsInvisibleEditor invisibleEditor = null;
                            TryGetTextLinesAndInvisibleEditor(fullPathFileName, out invisibleEditor, out textLines);
                        }
                    }
                    finally
                    {
                        if (ppunkDocData != IntPtr.Zero)
                            Marshal.Release(ppunkDocData);
                    }
                }
                return textLines;
            }
    
            /// <summary>
            /// Open the file in invisible editor in the running
            /// documents table (RDT), and get text buffer from that editor.
            /// </summary>
            /// <param name="fullPathFileName">File name with full path.</param>
            /// <param name="spEditor">The result invisible editor.</param>
            /// <param name="textLines">The result text buffer.</param>
            /// <returns>True, if the file is opened correctly in invisible editor.</returns>
            private static bool TryGetTextLinesAndInvisibleEditor(string fullPathFileName, out IVsInvisibleEditor spEditor, out IVsTextLines textLines)
            {
                System.IServiceProvider serviceProvider = DataPackage.Instance;
                spEditor = null;
                textLines = null;
    
                // Need to open this file.  Use the invisible editor manager to do so.
                IVsInvisibleEditorManager spIEM;
                IntPtr ppDocData = IntPtr.Zero;
                bool result;
    
                Guid IID_IVsTextLines = typeof(IVsTextLines).GUID;
    
                try
                {
                    spIEM = (IVsInvisibleEditorManager)serviceProvider.GetService(typeof(IVsInvisibleEditorManager));
                    spIEM.RegisterInvisibleEditor(fullPathFileName, null, (uint)_EDITORREGFLAGS.RIEF_ENABLECACHING, null, out spEditor);
                    if (spEditor != null)
                    {
                        int hr = spEditor.GetDocData(0, ref IID_IVsTextLines, out ppDocData);
                        if (hr == VSConstants.S_OK && ppDocData != IntPtr.Zero)
                        {
                            textLines = Marshal.GetTypedObjectForIUnknown(ppDocData, typeof(IVsTextLines)) as IVsTextLines;
                            result = true;
                        }
                        else
                        {
                            result = false;
                        }
                    }
                    else
                    {
                        result = false;
                    }
                }
                finally
                {
                    if (ppDocData != IntPtr.Zero)
                        Marshal.Release(ppDocData);
                }
                return result;
            }
        }
    }
    
  8. Dans le menu Fichier, cliquez sur Enregistrer SampleHelper.cs.

  9. Ajoutez une nouvelle classe intitulée RawChangeInfo au projet.

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

    Il convient maintenant de définir la classe RenameReferenceTextContributor.

Pour définir la classe RenameReferenceTextContributor

  1. Ajoutez une classe nommée RenameReferenceTextContributor à votre projet.

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

    using System;
    using System.Collections.Generic;
    using Microsoft.Data.Schema.Extensibility;
    using Microsoft.Data.Schema.Sql;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Data.Schema.Package.Refactoring;
    using Microsoft.VisualStudio.Data.Schema.Package.Sql.Refactoring;
    
  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 RenameReferenceTextContributor : RefactoringContributor<RenameReferenceContributorInput>
        {
        }
    

    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 RefactoringContributorpour le RenameReferenceContributorInput.

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

            #region const
            private const string TxtExtension = @".txt";
            private const string PreviewFriendlyName = @"Text Files";
            private const string PreviewDescription = @"Update text symbols in text files in the database project.";
            private const string PreviewWarningMessage = @"Updating text symbols in text files in the database project can cause inconsistency.";
    
            #endregion
    
            #region members
            private RefactoringPreviewGroup _textPreviewGroup;
            private List<String> _changingFiles;
            #endregion
    

    Les constantes fournissent des informations qui s'afficheront dans la fenêtre d'aperçu. Les membres supplémentaires permettent d'assurer le suivi du groupe d'aperçu et de la liste des fichiers texte en cours de modification.

  6. Ajoutez le constructeur de classe :

            #region ctor
            public RenameReferenceTextContributor()
            {
                _textPreviewGroup = new RefactoringPreviewGroup(PreviewFriendlyName);
                _textPreviewGroup.Description = PreviewDescription;
                _textPreviewGroup.WarningMessage = PreviewWarningMessage;
                _textPreviewGroup.EnableChangeGroupUncheck = true;
                _textPreviewGroup.EnableChangeUncheck = true;
                _textPreviewGroup.DefaultChecked = false; 
                _textPreviewGroup.IncludeInCurrentProject = true;
    
                // This sample uses the default icon for the file,
                // but you could provide your own icon here.
                //RefactoringPreviewGroup.RegisterIcon(TxtExtension, "textfile.ico");
            }
            #endregion
    

    Le constructeur initialise l'élément de modèle en créant un groupe d'aperçu et en initialisant ses propriétés. C'est ici également que vous pouvez décider d'enregistrer les icônes à afficher dans la Fenêtre d'aperçu pour des extensions de nom de fichier spécifiques. Étant donné que la mise en surbrillance de la syntaxe n'est pas prévue pour les fichiers texte, vous n'avez pas à enregistrer de service de langage pour les fichiers texte.

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

            /// <summary>
            /// Preview group for text files
            /// </summary>
            public override RefactoringPreviewGroup PreviewGroup
            {
                get
                {
                    return _textPreviewGroup;
                }
                set
                {
                    _textPreviewGroup = 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(RenameReferenceContributorInput input)
            {
                RenameReferenceContributorInput referenceInput = input as RenameReferenceContributorInput;
                if (referenceInput == null)
                {
                    throw new ArgumentNullException("input");
                }
    
                string projectFullName;
                referenceInput.RefactoringOperation.CurrentProjectHierarchy.GetCanonicalName(VSConstants.VSITEMID_ROOT, out projectFullName);
    
                return GetChangesForAllTextFiles(referenceInput, 
                                                projectFullName, 
                                                _textPreviewGroup.DefaultChecked, 
                                                out _changingFiles);
            }
    

    Cette méthode fait appel à la méthode GetAllChangesForAllTextFiles pour effectuer la majeure partie du travail.

  9. Ajoutez la méthode GetChangesForAllTextFiles pour effectuer une itération sur la liste des fichiers texte contenus dans le projet, obtenez les modifications pour chaque fichier et regroupez ces modifications dans une liste de propositions de modifications :

            /// <summary>
            /// Get all changes from all text files.
            /// </summary>
            private static Tuple<IList<ChangeProposal>, IList<ContributorInput>> GetChangesForAllTextFiles(
                RenameReferenceContributorInput input,
                string projectFullName,
                bool defaultChecked,
                out List<String> changingFiles)
            {
                if (input == null)
                {
                    throw new ArgumentNullException("input");
                }
    
                changingFiles = new List<String>();
                List<ChangeProposal> allChanges = new List<ChangeProposal>();
    
                List<string> files = new List<string>();
                files = SampleHelper.GetAllFilesInProject(input.RefactoringOperation.CurrentProjectHierarchy, TxtExtension, false);
    
                // process the text files one by one
                if (files != null && files.Count > 0)
                {
                    int fileCount = files.Count;
    
                    // Get all the changes for all txt files.
                    for (int fileIndex = 0; fileIndex < fileCount; fileIndex++)
                    {
                        IList<ChangeProposal> changes =
                            GetChangesForOneTextFile(
                                        input,
                                        projectFullName,
                                        files[fileIndex],
                                        defaultChecked);
                        if (changes != null && changes.Count > 0)
                        {
                            allChanges.AddRange(changes);
                            changingFiles.Add(files[fileIndex]);
                        }
                    }
                }
                return new Tuple<IList<ChangeProposal>, IList<ContributorInput>>(allChanges, null);
            }
    
  10. Implémentez la méthode GetChangesForOneTextFileMethod pour renvoyer la liste des propositions de modification contenues dans un fichier texte unique :

            /// <summary>
            /// Get all the change proposals from one text file.
            /// </summary>
            private static IList<ChangeProposal> GetChangesForOneTextFile(
                RenameReferenceContributorInput input,
                string projectFullName,
                string fileFullPath,
                bool defaultChecked)
            {
                const string separators = " \t \r \n \\()[]{}|.+-*/~!@#$%^&<>?:;";
    
                string fileContent = SampleHelper.ReadFileContent(fileFullPath);
    
                IList<ChangeProposal> changeProposals= null;
                if (string.IsNullOrEmpty(fileContent))
                {
                    // return empty change list
                    changeProposals = new List<ChangeProposal>();
                }
                else
                {
                    int startIndex = 0;
                    int maxIndex = fileContent.Length - 1;
                    string oldName = input.OldName;
                    int oldNameLength = oldName.Length;
                    List<RawChangeInfo> changes = new List<RawChangeInfo>();
                    while (startIndex < maxIndex)
                    {
                        // Text files do not understand schema object information
                        // We do just case-insensitive string match (without schema info)
                        // Only match whole word
                        int offset = fileContent.IndexOf(oldName, startIndex, StringComparison.OrdinalIgnoreCase);
    
                        // Cannot find match any more, stop the match
                        if (offset < 0)
                        {
                            break;
                        }
    
                        startIndex = offset + oldNameLength;
    
                        // match whole word: check before/after characters are separators
                        if (offset > 0)
                        {
                            char charBeforeMatch = fileContent[offset - 1];
                            // match starts in the middle of a token, discard and move on
                            if (!separators.Contains(charBeforeMatch.ToString()))
                            {
                                continue;
                            }
                        }
                        if (offset + oldNameLength < maxIndex)
                        {
                            char charAfterMatch = fileContent[offset + oldNameLength];
                            // match ends in the middle of a token, discard and move on
                            if (!separators.Contains(charAfterMatch.ToString()))
                            {
                                continue;
                            }
                        }
    
                        RawChangeInfo change = new RawChangeInfo(offset, oldNameLength, input.OldName, input.NewName);
                        changes.Add(change);
                    }
    
                    // convert index-based offsets to ChangeOffsets in ChangeProposals
                    changeProposals = SampleHelper.ConvertOffsets(
                        projectFullName,
                        fileFullPath,
                        changes,
                        defaultChecked);
                }
                return changeProposals;
            }
    

    Étant donné que la cible de refactorisation n'est pas un script Transact-SQL ni un objet de schéma, cette méthode n'a pas recours aux types ou aux méthodes de Microsoft.Data.Schema.ScriptDom ou Microsoft.Data.Schema.Sql.SchemaModel. Cette cible de refactorisation permet d'appliquer une commande de recherche et de remplacement aux fichiers texte, dans la mesure où les symboles dans les fichiers texte ne disposent d'aucune information de schéma.

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

  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.

Pour installer l'assembly RenameTextContributor

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

  2. Copiez votre assembly signé (RenameTextContributor.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 RenameTextContributor

  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 :

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

  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.RenameReferenceTextContributor" 
    assembly="RenameTextContributor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<enter key here>" enabled="true" />
    </extensions>
    

    Enregistrez la classe qui hérite de RefactoringContributor.

  7. Sauvegardez le fichier sous le nom RenameTextContributor.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 collaborateur 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.

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

    Prenez soin ensuite d'ajouter un fichier texte à votre projet de base de données dans lequel figure une référence à la table employee.

Pour ajouter un fichier texte contenant le nom de la table

  1. Dans l'Explorateur de solutions, cliquez avec le bouton droit sur le nœud de projet de base de données, pointez sur Ajouter, puis cliquez sur Nouvel élément.

  2. Dans la liste Catégories de la boîte de dialogue Ajouter un nouvel élément, cliquez sur Modèles Visual Studio.

  3. Dans la liste Modèles, cliquez sur Fichier texte.

  4. En guise de Nom, tapez TexteExemple1.txt.

  5. Dans l'éditeur de code, ajoutez le texte suivant :

    This is documentation for the employee table.
    Any changes made to the employee table name should also be reflected in this text file.
    
  6. Dans le menu Fichier, cliquez sur Enregistrer TexteExemple1.txt.

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

Pour utiliser le nouveau collaborateur de refactorisation pour mettre à jour le nom de la table

  1. Dans la vue Schéma, cliquez avec le bouton droit sur le nœud de la table employee, pointez sur Refactoriser et cliquez sur Renommer.

  2. Dans la boîte de dialogue Renommer, en guise de Nouveau nom, tapez [Personne].

  3. Dans la boîte de dialogue Aperçu des modifications, faites défiler les groupes de modification jusqu'à ce que le groupe Fichiers texte apparaisse.

    Vous pouvez constater que les deux instances de la table employee du fichier texte sont répertoriées. Vous avez défini les classes permettant d'activer ce type nouveau de refactorisation au cours de cette procédure. Activez la case à cocher en regard de chaque modification.

  4. Cliquez sur Appliquer.

    Le nom de la table est remplacé par Personne à la fois dans la vue Schéma et dans le contenu du fichier TexteExemple1.txt.

Étapes suivantes

Vous pouvez créer des cibles de refactorisation supplémentaires ou définir de nouveaux types de refactorisation pour éviter toutes les modifications répétitives dans votre projet de base de données.

Voir aussi

Tâches

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

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