Compartir a través de


Tutorial: Extender la refactorización de cambio de nombre de base de datos para que funcione en archivos de texto

En este tema paso a paso creará, instalará, registrará y probará un nuevo contribuidor para la refactorización de cambio de nombre. Este destino de refactorización extenderá las capacidades de Visual Studio Premium o Visual Studio Ultimate para que la refactorización de base de datos pueda cambiar el nombre de las referencias a objetos de base de datos contenidas en archivos de texto en el proyecto de base de datos.

Cuando se agrega un nuevo colaborador de refactorización a un tipo de refactorización existente, debe utilizar una clase de entrada de colaborador existente.

En este tutorial se muestran las tareas siguientes:

  1. Crear un nuevo ensamblado que contenga las clases para un destino de refactorización personalizado.

  2. Instalar y registrar el ensamblado para que el destino de refactorización esté disponible en Visual Studio Premium o Visual Studio Ultimate.

  3. Crear un proyecto de base de datos simple para probar que el destino de refactorización funciona según lo esperado.

Requisitos previos

Necesita los componentes siguientes para completar este tutorial:

  • Debe tener instalado Visual Studio 2010 Premium o Visual Studio 2010 Ultimate.

  • También debe tener instalado Visual Studio 2010 SDK en su equipo. Para descargar este kit, vea esta página en el sitio web de Microsoft: Visual Studio 2010 SDK.

Crear un ensamblado con un destino de refactorización personalizado

Para crear un destino de refactorización personalizado que permita el funcionamiento de la refactorización de cambio de nombre en archivos de texto, debe implementar una clase para proporcionar un nuevo RefactoringContributor:

  • RenameReferenceTextContributorContributor: Esta clase crea la lista de propuestas de cambios para el símbolo cuyo nombre ha cambiado. Las propuestas de cambios son para cada referencia contenida en un archivo de texto que está en el proyecto de base de datos.

Antes de crear esta clase, creará una biblioteca de clases, agregará las referencias requeridas y agregará código auxiliar que simplifique una parte del código que escribirá más adelante en este tutorial.

Para crear la biblioteca de clases y código auxiliar

  1. Cree un nuevo proyecto de biblioteca de clases de C# y denomínelo RenameTextContributor.csproj.

  2. Agregue referencias a los siguientes ensamblados de .NET:

    • Microsoft.Data.Schema

    • Microsoft.Data.Schema.ScriptDom

    • Microsoft.Data.Schema.ScriptDom.sql

    • Microsoft.Data.Schema.Sql

  3. Agregue referencias a los siguientes ensamblados que encontrará en la carpeta %Archivos de programa%\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. Agregue referencias a los siguientes ensamblados del Kit de desarrollo de software (SDK) de Visual Studio 2010:

    • 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. En el Explorador de soluciones, cambie el nombre de Class1.cs a SampleHelper.cs.

  6. Haga doble clic en SampleHelper.cs para abrirlo en el editor de código.

    Nota

    Esta clase auxiliar es idéntica al texto auxiliar que se utilizó en el tutorial para tipos de refactorización personalizados. Puede copiar el código fuente de ese proyecto en el nuevo proyecto para ahorrar tiempo.

  7. Reemplace el contenido del editor de código por el código siguiente:

    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. En el menú Archivo, haga clic en Save SampleHelper.cs.

  9. Agregue al proyecto una clase denominada RawChangeInfo.

  10. En el editor de código, actualice el código para que coincida con el siguiente:

    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. En el menú Archivo, haga clic en Guardar RawChangeInfo.cs.

    A continuación, definirá la clase RenameReferenceTextContributor.

Para definir la clase RenameReferenceTextContributor

  1. Agregue una clase denominada RenameReferenceTextContributor al proyecto.

  2. En el editor de código, actualice las instrucciones using para que coincidan con las siguientes:

    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. Cambie el espacio de nombres a MySamples.Refactoring:

    namespace MySamples.Refactoring
    
  4. Actualice la definición de clase para que coincida con la siguiente:

        [DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))]
        internal class RenameReferenceTextContributor : RefactoringContributor<RenameReferenceContributorInput>
        {
        }
    

    Especifique el atributo para declarar que este colaborador es compatible con los proveedores de esquema de base de datos derivados de SqlDatabaseSchemaProvider. La clase debe heredar de RefactoringContributor para RenameReferenceContributorInput.

  5. Defina constantes adicionales y variables miembro privadas:

            #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
    

    Las constantes proporcionan información que aparecerá en la ventana de vista previa. Los miembros adicionales se utilizan para realizar el seguimiento del grupo de visualización previa y la lista de los archivos de texto que se van a cambiar.

  6. Agregue el constructor de clase:

            #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
    

    El constructor inicializa el elemento de modelo, creando un nuevo grupo de visualización previa e inicializando sus propiedades. También puede registrar iconos aquí, con el fin de que se muestren en la ventana de vista previa para extensiones de nombre de archivo concretas. Dado que no hay resaltado de sintaxis para archivos de texto, no tiene que registrar un servicio de lenguaje para los archivos de texto.

  7. Reemplace la propiedad PreviewGroup para devolver el grupo que se creó cuando se creó este colaborador:

            /// <summary>
            /// Preview group for text files
            /// </summary>
            public override RefactoringPreviewGroup PreviewGroup
            {
                get
                {
                    return _textPreviewGroup;
                }
                set
                {
                    _textPreviewGroup = value;
                }
            }
    
  8. Reemplace el método ContributeChanges(Boolean) para devolver una lista de propuestas de cambios:

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

    Este método llama al método GetAllChangesForAllTextFiles para realizar la mayor parte del trabajo.

  9. Agregue el método GetChangesForAllTextFiles para realizar iteraciones en la lista de los archivos de texto contenidos en el proyecto, obtener los cambios para cada archivo y agregar esos cambios en una lista de propuestas de cambios:

            /// <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. Implemente el método GetChangesForOneTextFileMethod para devolver una lista de las propuestas de cambios contenidas en un solo archivo de texto:

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

    Dado que el destino de la refactorización no es un script Transact-SQL ni un objeto de esquema, este método no usa tipos ni métodos de Microsoft.Data.Schema.ScriptDom o Microsoft.Data.Schema.Sql.SchemaModel, sino que proporciona una búsqueda y reemplaza la implementación de archivos de texto, porque los símbolos de los archivos de texto no tienen información de esquema disponible.

  11. En el menú Archivo, haga clic en Save RenameTextContributor.cs.

    A continuación, configurará y compilará el ensamblado.

Para firmar y compilar el ensamblado

  1. En el menú Proyecto, haga clic en Propiedades de RenameTextContributor.

  2. Haga clic en la ficha Firma.

  3. Haga clic en Firmar el ensamblado.

  4. En Elija un archivo de clave de nombre seguro, haga clic en <Nuevo>.

  5. En el cuadro de diálogo Crear clave de nombre seguro, en Nombre del archivo de clave, escriba MyRefKey.

  6. (opcional) Puede especificar una contraseña para el archivo de clave de nombre seguro.

  7. Haga clic en Aceptar.

  8. En el menú Archivo, haga clic en Guardar todo.

  9. En el menú Generar, haga clic en Generar solución.

    A continuación, debe instalar y registrar el ensamblado para que aparezca como una condición de prueba disponible.

Para instalar el ensamblado RenameTextContributor

  1. Cree una carpeta denominada MyExtensions en la carpeta %Archivos de programa%\Microsoft Visual Studio 10.0\VSTSDB\Extensions.

  2. Copie el ensamblado firmado (RenameTextContributor.dll) a la carpeta %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions.

    Nota

    Se recomienda no copiar directamente los archivos XML a la carpeta %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions. Si usa una subcarpeta en su lugar, evitará cambios accidentales en los demás archivos proporcionados con Visual Studio.

    A continuación, debe registrar el ensamblado, un tipo de extensión de características, para que aparezca en Visual Studio.

Para registrar el ensamblado RenameTextContributor

  1. En el menú Ver, haga clic en Otras ventanas y, a continuación en Ventana Comandos para abrir la ventana Comandos.

  2. En la ventana Comando, escriba el código siguiente. En FilePath, sustituya la ruta de acceso y el nombre de archivo por la ruta y el nombre de su archivo .dll compilado. Debe escribir la ruta de acceso y el nombre de archivo entre comillas.

    Nota

    De forma predeterminada, la ruta de acceso del archivo .dll compilado es rutaDeAccesoDeSolución\bin\Debug o rutaDeAccesoDeSolución\bin\Release.

    ? System.Reflection.Assembly.LoadFrom("FilePath").FullName
    
    ? System.Reflection.Assembly.LoadFrom(@"FilePath").FullName
    
  3. Presione Entrar.

  4. Copie la línea resultante al Portapapeles. Debe ser similar a la siguiente:

    "RenameTextContributor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nnnnnnnnnnnnnnnn"
    
  5. Abra un editor de texto sin formato, como el Bloc de notas.

  6. Proporcione la siguiente información, especificando el nombre del ensamblado, el símbolo de clave pública y el tipo de extensión:

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

    Registre la clase que hereda de RefactoringContributor.

  7. Guarde el archivo como RenameTextContributor.extensions.xml en la carpeta %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions.

  8. Cierre Visual Studio.

    A continuación, creará un proyecto de base de datos muy simple para probar el nuevo tipo de refactorización.

Probar el nuevo colaborador de refactorización

Para crear un proyecto de base de datos

  1. En el menú Archivo, elija Nuevo y, a continuación, haga clic en Proyecto.

  2. En Plantillas instaladas, expanda el nodo Base de datos y, a continuación, haga clic en el nodo SQL Server.

  3. En la lista de plantillas, haga clic en Proyecto de base de datos de SQL Server 2008.

  4. Haga clic en Aceptar para aceptar el nombre de proyecto predeterminado y crear el proyecto.

    Se crea un proyecto de base de datos vacío.

Para agregar una tabla con una clave principal

  1. En el menú Ver, haga clic en Vista de esquema.

  2. En Vista de esquema, expanda el nodo Esquemas, expanda el nodo dbo, haga clic con el botón secundario en el nodo Tablas, elija Agregar y haga clic en Tabla.

  3. En el cuadro de diálogo Agregar nuevo elemento, en Nombre, escriba employee.

    Nota

    Usará intencionadamente una letra minúscula para comenzar el nombre de tabla.

  4. Haga clic en Aceptar.

  5. Expanda el nodo Tablas, haga clic con el botón secundario en el nodo employee, elija Agregar y haga clic en Clave principal.

  6. En el cuadro de diálogo Agregar nuevo elemento, en Nombre, escriba PK_Employee_column_1.

  7. Haga clic en Aceptar.

    A continuación, agregará un archivo de texto al proyecto de base de datos que contiene una referencia a la tabla employee.

Para agregar un archivo de texto que contiene el nombre de tabla

  1. En el Explorador de soluciones, haga clic con el botón secundario en el nodo de proyecto de base de datos, seleccione Agregar y, a continuación, haga clic en Nuevo elemento.

  2. En el cuadro de diálogo Agregar nuevo elemento, en la lista Categorías, haga clic en Plantillas de Visual Studio.

  3. En la lista Plantillas, haga clic en Archivo de texto.

  4. En Nombre, escriba SampleText1.txt.

  5. En el editor de código, agregue el texto siguiente:

    This is documentation for the employee table.
    Any changes made to the employee table name should also be reflected in this text file.
    
  6. En el menú Archivo, haga clic en Save SampleText1.txt.

    A continuación, utilizará el nuevo tipo de refactorización para cambiar el nombre de tabla y todas las referencias a él.

Para utilizar el nuevo colaborador de refactorización para actualizar el nombre de tabla

  1. En Vista de esquema, haga clic con el botón secundario en el nodo de tabla employee, elija Refactorizar y haga clic en Cambiar nombre.

  2. En el cuadro de diálogo Cambiar nombre, en Nuevo nombre, escriba [Person].

  3. En el cuadro de diálogo Obtener vista previa de cambios, desplácese por los grupos de cambios hasta ver el grupo Archivos de texto.

    Se mostrará una lista de ambas instancias de employee en el archivo de texto. Definió las clases que habilitan este nuevo tipo de refactorización en este tutorial. Active la casilla que se encuentra junto a cada cambio.

  4. Haga clic en Aplicar.

    El nombre de tabla se actualizará a Person, tanto en Vista de esquema como en el contenido del archivo SampleText1.txt.

Pasos siguientes

Puede crear destinos de refactorización adicionales, o nuevos tipos de refactorización, para reducir el esfuerzo asociado a la realización de cambios repetitivos en el proyecto de base de datos.

Vea también

Tareas

Tutorial: Crear un nuevo tipo de refactorización de base de datos para cambiar las mayúsculas y minúsculas

Conceptos

Crear tipos de refactorización de base de datos personalizados o destinos

Refactorizar código de base de datos y datos