El poder de las expresiones regulares

Creo no conocer a ningún programador que no odie enfrentarse a las expresiones regulares, lo cierto es que tienen su complejidad, aunque si te habitúas a utilizarlas en más de una ocasión te pueden sacar de un apuro.

En nuestra aplicación detectamos un error procedente de un control muy utilizado en nuestros formularios, el caso es que eliminamos una propiedad pública que utilizábamos para su configuración, como era de esperar el “find usages”, nos decía que esta era utilizada por infinidad de formularios de la aplicación, aparecían unas 2000 referencias, después de eliminar varias recorde que en cierta ocasión había podido solucionar un problema similar utilizando expresiones regulares.

Tenia que eliminar de los formularios todas las líneas similares a

   1: ((System.ComponentModel.ISupportInitialize)(this.bPorcentaje7.Properties)).BeginInit();
   2: this.bPorcentaje7.Properties.MaxLength = 6;
   3: ((System.ComponentModel.ISupportInitialize)(this.bPorcentaje7.Properties)).EndInit();

Utilizando la herramienta find and replace del visual studio configure las siguientes expresiones:

image

   1: ^(.*){ComponentModel.ISupportInitialize}(.*){Porcentaje}(.*){Properties}(.*)
   2:  
   3: ^(.*){porcentaje}(.*){Properties}(.*){ MaxLength }(.*)

La primera expresión regular eliminaría dos líneas, la primera y la tercera correspondientes a BeginInit y EndInit y la segunda eliminaría la línea del maxlenght.

Su tradución seria así: Busca desde el principio de la línea una cadena que comience con cualquier carácter y que seguidamente tenga la cadena “ComponentModel.ISupportInitialize”, después cualquier cadena, seguido de la palabra “porcentaje” seguida de cualquier cadena, seguida de la palabra “properties” y que finalice con cualquier cadena.

Una vez escrito, solo bastaba reemplazar todas las cadenas, y voila, el trabajo de varias horas hecho en tan solo unos segundos.

Otro ejemplo interesante para sustituir un bloque determinado es este:

(.*)public void Copy(.*n)+?.*(})

Esta busca un metodo publico llamado copy y borra todo el contenido entre los dos corchetes, de esta forma podríamos eliminar un método completo.

La documentación de Visual Studio se encuentra disponible en http://msdn.microsoft.com/en-us/library/az24scfc(VS.80).aspx

Existen además varios generadores de expresiones regulares en la web y programas para ayudarnos a escribirlas.

Entity FrameWork – Metadata

Como sabéis la metadata se define como la información que se conoce sobre los propios datos, cuando hablamos de entity framework, esta contendrá información sobre las entidades, campos y relaciones. En este ejemplo estaba buscando la posibilidad de conocer el maxLength de un campo de una entidad determinada, de esta forma podria configurar algunos de mis controles en tiempo de ejecución.

Cuando configuramos un grid necesitamos configurar el maxLength de cada campo para impedir un desbordamiento en la actualización, para evitar establecer el valor de forma manual en cada columna, pues si en algún momento cambio este valor, estaría obligado a variar esta información en todos los controles de mi aplicación donde utilice este campo. La metadata puede proporcionar esta información para configurar los controles en tiempo de ejecución evitando cambiar estos datos si en el algún momento altero la estructura de mis tablas. De la misma forma podemos configurar la precisión y la escala en valores decimales, nombre de los campos para el databinding, aceptación de valores nulos, configuración de campos de solo lectura, etc.

La automatización de estos procesos a traves de los metadatos evitara numerosos errores y nos permitirá realizar cambios de una forma más eficaz.

En otras ocasiones esta información nos permitira generar sentencias Sql en tiempo de ejecución, generalizar consultas y funciones en nuestros proyectos.

En Entity framework la metadata esta almacenada en el archivo edmx en formato xml. En este ejemplo el archivo xml contiene información diversa en cada uno de los campos, maxlenght en los casos que utilizemos cadenas, precision y escala en los valores decimales.

   1: <Property Name="Empresa" Type="nvarchar" MaxLength="50" />
   2: <Property Name="Transporte_factor" Type="decimal" Nullable="false" Precision="6" Scale="2" />        

He desarrollado este pequeño programa que nos permitirá conocer el maxLength de cualquier campo de una entidad,

/// <summary>
/// Retorna el maxlenght de un campo de una entidad y un contexto determinados
/// </summary>
/// <param name="context">Contexto del que se quieren recuperar las entidades</param>
/// <param name="entity">Nombre de la entidad</param>
/// <param name="field">Nombre del campo</param>
/// <returns>Retorna la longitud de la entidad, en caso de no encontrarlo retorna -1</returns>
public static int GetMaxlenght(ObjectContext context, EntityObject entity, string field)
{
    if (context != null && entity != null && !string.IsNullOrEmpty(field))
    {
        EntityContainer container;
        if (context.MetadataWorkspace.TryGetEntityContainer(context.DefaultContainerName, DataSpace.CSpace, out container))
        {
            EntitySet entitySet;
            if (container.TryGetEntitySetByName(entity.ToString().Split('.')[1], false, out entitySet))
            {
                const string mlenght = "MaxLength";
                if (entitySet.ElementType.Members.Contains(field) && entitySet.ElementType.Members[field].TypeUsage.Facets.Contains(mlenght))
                {
                    object smaxlenght = entitySet.ElementType.Members[field].TypeUsage.Facets[mlenght].Value;
 
                    if (smaxlenght != null)
                    {
                        int maxlenght;
                        if (int.TryParse(smaxlenght.ToString(), out maxlenght))
                        {
                            return maxlenght;
                        }
                    }
                }
            }
        }
    }
    return -1;
}

El ejemplo de uso seria algo así:

   1: using (NorthwindEFEntities context = new NorthwindEFEntities())
   2: {
   3:     Employees employees = new Employees();
   4:     EdmTools.GetMaxlenght(context, employees, "FirstName");
   5: }

Si quisieramos conocer otro factor, tan solo tendriamos que alterar el valor de la constante definida en:

const string mlenght = "MaxLength"

Aunque para el ejemplo anterior quizás lo más lógico sería realizar un método extensor para acceder a este tipo de información para poder hacer algo asi:

int maxlenght = employess.GetMetadata(employess.FirstName, Attributes.Maxlenght);

Cuando lo desarrolle pondré este ejemplo.

Como veis en el depurador las facetas pueden proporcionarnos más información, default value, nullable, etc.

image

El siguiente ejemplo está basado en el mismo proceso y devuelve información a través de un array con el contenido del maxLength de todas las columnas:

   1: /// <summary>
   2: /// Retorna una colección de campos valor con el maxlenght de cada uno de los campos de una entidad
   3: /// </summary>
   4: /// <param name="context">Contexto del que se quieren recuperar las entidades</param>
   5: /// <param name="entity">Entidad</param>
   6: /// <returns>Retorna un diccionario campo valor con la longitud de cada uno de los campos, en caso de no encontrarlo retorna -1</returns>
   7: public static Dictionary<EdmMember, int> GetMaxlenght(ObjectContext context, EntityObject entity)
   8: {
   9:     Dictionary<EdmMember, int> edmFieldsMaxlenght = new Dictionary<EdmMember, int>();
  10:     if (context != null && entity != null)
  11:     {
  12:         EntityContainer container;
  13:         if (context.MetadataWorkspace.TryGetEntityContainer(context.DefaultContainerName, DataSpace.CSpace, out container))
  14:         {
  15:             EntitySet entitySet;
  16:             if (container.TryGetEntitySetByName(entity.ToString().Split('.')[1], false, out entitySet))
  17:             {
  18:                 foreach (EdmMember member in entitySet.ElementType.Members)
  19:                 {
  20:                     const string mlenght = "MaxLength";
  21:  
  22:                     if (entitySet.ElementType.Members.Contains(member.Name) && entitySet.ElementType.Members[member.Name].TypeUsage.Facets.Contains(mlenght))
  23:                     {
  24:                         object smaxlenght = entitySet.ElementType.Members[member.Name].TypeUsage.Facets[mlenght].Value;
  25:  
  26:                         if (smaxlenght != null)
  27:                         {
  28:                             int maxlenght;
  29:                             if (int.TryParse(smaxlenght.ToString(), out maxlenght))
  30:                             {
  31:                                 edmFieldsMaxlenght.Add(member, maxlenght);
  32:                             }
  33:                         }
  34:                     }
  35:                 }
  36:             }
  37:         }
  38:     }
  39:     return edmFieldsMaxlenght;
  40: }
Buscando información encontré este blog http://www.scip.be/index.php?Page=ArticlesNET24, en el que proponen otro ejemplo muy interesante. Solo que aquí no puede acceder al maxlengh, es curioso, únicamente a los valores por defecto, si es nullable, etc.

1:
var query = from meta in context.MetadataWorkspace.GetItems(DataSpace.CSpace)
   2:                 .Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType)
   3:             let m = (meta as EntityType)
   4:             let properties = m.Properties
   5:             select new
   6:             {
   7:  
   8:                 EntityName = m.Name,
   9:                 MembersCount = m.Members.Count,
  10:                 KeyMembersCount = m.KeyMembers.Count,
  11:                 PropertyNames = from p in properties
  12:                                 select new
  13:                                 {
  14:                                     p.Name,
  15:                                     p.Nullable,
  16:                                     p.DefaultValue,
  17:                 Documentation,
  18:                                     Type = p.TypeUsage.EdmType.Name
  19:                                 }
  20:             };

No he probado a leer los metadatos de una entidad heredada, aunque en este caso podriamos averiguar su base y realizar la consulta. Utilizando como base el ejemplo anterior podemos facilmente ver todas la entidades que están derivadas.

   1: public static IEnumerable GetEntityNamesBaseNames(ObjectContext context)
   2: {
   3:     var query = from meta in context.MetadataWorkspace.GetItems(DataSpace.CSpace)
   4:                     .Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType)
   5:                 let m = (meta as EntityType)
   6:                 where m.BaseType != null
   7:                 select new
   8:                 {
   9:                     m.Name,
  10:                     BaseTypeName = m.BaseType != null ? m.BaseType.Name : null,
  11:                 };
  12:     return query;
  13: }

Espero que os sirva…