Edición masiva en GeneXus: Cerrar las pestañas References antes de empezar


Si tu Knowledge Base contiene miles de objetos, cada Save puede convertirse en una pausa innecesaria: el IDE recalcula de inmediato la información mostrada en las pestañas References y Referenced by. Cuando estás haciendo cambios en serie (ajustes de propiedades, limpieza de variables sin uso, marcar objetos como Not Generated, importar objetos, etc.) ese refresco continuo añade segundos —o minutos— a tu tarea.

¿Qué está pasando en segundo plano?

Las pestañas de referencia se alimentan de un grafo que describe todas las dependencias entre objetos. Cada guardado desencadena:

  1. Análisis sintáctico del objeto modificado.
  2. Re-cálculo de las aristas que cambian en el grafo.
  3. Render del resultado en cada pestaña abierta.

En KB grandes el paso 2 puede tomar varios segundos. Repetido cientos de veces se vuelve un cuello de botella innecesario.

La práctica recomendada

1. Cierra todas las pestañas References/Referenced by antes de iniciar los cambios.

Usa el botón × de cada pestaña de referencias o usa la opcion "Close All But this" , que cierra todas los tabs, menos el activo.  

2. Realiza toda la tanda de ediciones.

3. Guarda y confirma.

4. Cuando termines, vuelve a abrir las pestañas. El refresco se hará solo una vez, con todas las modificaciones ya consolidadas.

Ventajas inmediatas

  • Menos tiempo de espera: la percepción de “lag” desaparece.
  • Menor carga de CPU y disco: ideal en máquinas virtuales o KB con repositorio remoto.
  • Flujo de trabajo más fluido: te concentras en los cambios y no en la interfaz. 

Conclusión

El truco es simple: mantén cerradas las pestañas de referencias mientras haces ediciones masivas. Ganarás agilidad sin perder la información; siempre podrás consultarla cuando realmente la necesites y con un solo refresco.

Extra: SOLO PARA NERDS

Solo para los que les guste optimizar sentencias en la base de datos, en GeneXus 18 Upgrade 12, para calcular las referencias, esta usando esta consulta sobre SQL Server. 


WITH ObjectsInModule AS (
  SELECT
    mcr.ToEntityId    AS ModuleId,
    mcr.FromEntityTypeId AS EntityTypeId,
    mcr.FromEntityId  AS EntityId
  FROM ModelCrossReference mcr WITH (NOLOCK)
  WHERE ModelId = @ModelId
    AND ToEntityTypeId = @ModuleTypeId
    AND LinkType = @ParentLinkType

  UNION ALL

  SELECT
    oim.ModuleId,
    mcr.FromEntityTypeId AS EntityTypeId,
    mcr.FromEntityId     AS EntityId
  FROM ModelCrossReference mcr WITH (NOLOCK)
    INNER JOIN ObjectsInModule oim
      ON oim.EntityTypeId = mcr.ToEntityTypeId
     AND oim.EntityId     = mcr.ToEntityId
  WHERE mcr.ModelId        = @ModelId
    AND mcr.FromEntityTypeId <> @ModuleTypeId
    AND mcr.LinkType         = @ParentLinkType
    AND oim.EntityTypeId     <> @ModuleTypeId
),
ModulesInModule AS (
  SELECT
    EntityId    AS ModuleId,
    CONVERT(VARCHAR(MAX), '') AS ModuleName
  FROM ModelEntityVersion WITH (NOLOCK)
  WHERE ModelId             = @ModelId
    AND EntityTypeId        = @ModuleTypeId
    AND ModelParentEntityTypeId = 0

  UNION ALL

  SELECT
    mev.EntityId     AS ModuleId,
    IIF(
      mim.ModuleName = '',
      mev.ModelEntityVersionName,
      CONCAT(mim.ModuleName, '.', mev.ModelEntityVersionName)
    ) AS ModuleName
  FROM ModelEntityVersion mev WITH (NOLOCK)
    INNER JOIN ModulesInModule mim
      ON mev.ModelParentEntityId = mim.ModuleId
  WHERE mev.ModelId      = @ModelId
    AND mev.EntityTypeId = @ModuleTypeId
)
SELECT
  mcr.EntityTypeId,
  mcr.EntityId,
  COALESCE(mev.ModelEntityVersionName, '') AS EntityVersionName,
  mcr.LinkType,
  mcr.LinkTypeInfo,
  mcr.ReferenceType,
  mcr.HasReferences,
  COALESCE(mim.ModuleName, '') AS ModuleName
FROM (
  SELECT
    mcr1.ToEntityTypeId AS EntityTypeId,
    mcr1.ToEntityId     AS EntityId,
    mcr1.LinkType,
    mcr1.LinkTypeInfo,
    mcr1.ReferenceType,
    CONVERT(BIT, (
      SELECT TOP(1) 1
      FROM ModelCrossReference mcr2 WITH (NOLOCK)
      WHERE mcr2.ModelId          = @ModelId
        AND mcr2.FromEntityTypeId = mcr1.ToEntityTypeId
        AND mcr2.FromEntityId     = mcr1.ToEntityId
        AND mcr2.LinkType         = @ObjectLinkType
        AND mcr2.ToEntityTypeId IN (
          SELECT EntityTypeId
          FROM EntityType
          WHERE EntityTypeIsModelable = 0
            OR EntityTypeNamespace    <> ''
        )
    )) AS HasReferences
  FROM ModelCrossReference mcr1 WITH (NOLOCK)
  WHERE mcr1.ModelId          = @ModelId
    AND mcr1.FromEntityTypeId = @EntityTypeId
    AND mcr1.FromEntityId     = @EntityId
    AND mcr1.LinkType         = @ObjectLinkType
    AND mcr1.ToEntityTypeId IN (
      SELECT EntityTypeId
      FROM EntityType
      WHERE EntityTypeIsModelable = 0
        OR EntityTypeNamespace    <> ''
    )
) AS mcr
LEFT JOIN (
  SELECT EntityTypeId, EntityId, ModelEntityVersionName
  FROM ModelEntityVersion WITH (NOLOCK)
  WHERE ModelId = @ModelId
) AS mev
  ON mev.EntityTypeId = mcr.EntityTypeId
 AND mev.EntityId     = mcr.EntityId
LEFT JOIN ObjectsInModule oim
  ON oim.EntityTypeId = mcr.EntityTypeId
 AND oim.EntityId     = mcr.EntityId
LEFT JOIN ModulesInModule mim
  ON oim.ModuleId     = mim.ModuleId;

En la KB en la que estaba trabajando, es bastante ineficiente, pues demora hasta 18 segundos en la base, y eso lo repite tantas veces como tabs de referencias abiertos tengas, multiplicado por la cantidad de objetos que modifiques en forma batch. 

Use un modelo de inteligencia artificial para optimizarlo, y paso de un costo de 158 a un costo de 5 con la siguiente sentencia equivalente. 

-- 1) Lista de tipos permitidos
DECLARE @AllowedTypes TABLE (EntityTypeId INT PRIMARY KEY);
INSERT INTO @AllowedTypes
SELECT EntityTypeId
FROM EntityType
WHERE EntityTypeIsModelable = 0
   OR EntityTypeNamespace <> '';

-- 2) Precalcula flags de referencias
;WITH RefFlags AS (
  SELECT DISTINCT
    FromEntityTypeId AS EntityTypeId,
    FromEntityId,
    1 AS HasReferences
  FROM ModelCrossReference WITH (NOLOCK)
  WHERE ModelId      = @ModelId
    AND LinkType     = @ObjectLinkType
    AND ToEntityTypeId IN (SELECT EntityTypeId FROM @AllowedTypes)
),
-- 3) CTE recursiva de módulos (sin cambios sustanciales)
ObjectsInModule AS (
  SELECT mcr.ToEntityId AS ModuleId
       , mcr.FromEntityTypeId AS EntityTypeId
       , mcr.FromEntityId AS EntityId
  FROM ModelCrossReference mcr WITH (NOLOCK)
  WHERE ModelId         = @ModelId
    AND ToEntityTypeId  = @ModuleTypeId
    AND LinkType        = @ParentLinkType

  UNION ALL

  SELECT oim.ModuleId
       , mcr.FromEntityTypeId
       , mcr.FromEntityId
  FROM ModelCrossReference mcr WITH (NOLOCK)
  JOIN ObjectsInModule oim
    ON oim.EntityTypeId = mcr.ToEntityTypeId
   AND oim.EntityId     = mcr.ToEntityId
  WHERE mcr.ModelId         = @ModelId
    AND mcr.FromEntityTypeId<> @ModuleTypeId
    AND mcr.LinkType        = @ParentLinkType
),
ModulesInModule AS (
  SELECT EntityId AS ModuleId
       , CAST('' AS VARCHAR(MAX)) AS ModuleName
  FROM ModelEntityVersion WITH (NOLOCK)
  WHERE ModelId                = @ModelId
    AND EntityTypeId           = @ModuleTypeId
    AND ModelParentEntityTypeId= 0

  UNION ALL

  SELECT mev.EntityId
       , CASE
           WHEN mim.ModuleName = '' THEN mev.ModelEntityVersionName
           ELSE mim.ModuleName + '.' + mev.ModelEntityVersionName
         END
  FROM ModelEntityVersion mev WITH (NOLOCK)
  JOIN ModulesInModule mim
    ON mev.ModelParentEntityId = mim.ModuleId
  WHERE mev.ModelId      = @ModelId
    AND mev.EntityTypeId = @ModuleTypeId
)
SELECT
  mcr.ToEntityTypeId AS EntityTypeId,
  mcr.ToEntityId     AS EntityId,
  COALESCE(mev.ModelEntityVersionName,'') AS EntityVersionName,
  mcr.LinkType,
  mcr.LinkTypeInfo,
  mcr.ReferenceType,
  COALESCE(rf.HasReferences,0)    AS HasReferences,
  COALESCE(mim.ModuleName,'')     AS ModuleName
FROM ModelCrossReference mcr WITH (NOLOCK)
-- 4) Aplica filtros directos y únete a AllowedTypes
JOIN @AllowedTypes at
  ON mcr.ToEntityTypeId = at.EntityTypeId
   AND mcr.ModelId      = @ModelId
   AND mcr.FromEntityTypeId = @EntityTypeId
   AND mcr.FromEntityId     = @EntityId
   AND mcr.LinkType         = @ObjectLinkType

-- 5) Enlaza el flag precomputado
LEFT JOIN RefFlags rf
  ON rf.EntityTypeId = mcr.ToEntityTypeId
 AND rf.FromEntityId = mcr.ToEntityId

-- 6) Trae nombre de versión si existe
LEFT JOIN ModelEntityVersion mev WITH (NOLOCK)
  ON mev.ModelId       = @ModelId
 AND mev.EntityTypeId  = mcr.ToEntityTypeId
 AND mev.EntityId      = mcr.ToEntityId

-- 7) Finalmente enlaza con los módulos
LEFT JOIN ObjectsInModule oim
  ON oim.EntityTypeId  = mcr.ToEntityTypeId
 AND oim.EntityId      = mcr.ToEntityId
LEFT JOIN ModulesInModule mim
  ON mim.ModuleId      = oim.ModuleId
OPTION (RECOMPILE); 

Se lo pase a la gente de Genexus por si les interesa hacer esta optimización o una parecida, pero muchas veces hay restricciones como las versiones de SQL Server soportadas, o  efectos secundarios que pueden acarrear estos cambios que impiden implementarla. 

En resumen, si quieren que GeneXus esta un poquito mas rapido, CIERREN LAS VENTANAS DE REFERENCIAS cuando no las necesiten. 

Conclusión Extra

Cada vez que veo este tipo de cosas, pienso que GeneXus debería utilizar una base de datos orientada a grafos, para almacenar las referencias de los objetos en la base de conocimiento.  

Comentarios

Entradas más populares de este blog

La nefasta influencia del golero de Cacho Bochinche en el fútbol uruguayo

Aplicación monolítica o distribuida?

Migrando de GeneXus 9.0 a GeneXus X.