Añadiendo columnas a una tabla de una base de datos publicada en una réplica de mezcla

 

Escenario: desde BI nos preguntan si podemos añadir un campo nuevo, timestamp, a una tabla de una base de datos que tenemos publicada dentro de una réplica de mezcla porque lo necesitan para el control de sus procesos de traspaso de datos. El campo no es necesario que se replique, por lo tanto no tenemos que añadirlo al artículo correspondiente. Nos encontramos ante un Publicador separado del Distribuidor ambos con Sql Server 2008.

¿Añadir un campo a la tabla? ¡Ningún problema! Nos ponemos mi compañero, José Luis Borch de Sistemas y yo con el tema. Alter table…mmmmmmm, espera, tenemos suscriptores con Sql 2000, no podemos, ya sabes “Compatibility level must be 90 or higher”. No hay problema, en el caso que nos ocupa, una replicación con nivel inferior a 90, podemos acudir al procedimiento (obsoleto pero mantenido por compatibilidad con versiones anteriores) sp_repladdcolumn. Perfecto. Manos a la obra.

La idea es añadir el campo a la tabla pero no al artículo relacionado, ya que el campo no se tendrá que replicar. Buscamos la documentación sobre el procedure y analizamos los parámetros:

sp_repladdcolumn [ @source_object = ] 'source_object', [ @column = ] 'column' ]
    [ , [ @typetext = ] 'typetext' ]
    [ , [ @publication_to_add = ] 'publication_to_add' ]
    [ , [ @from_agent = ] from_agent ]
    [ , [ @schema_change_script = ] 'schema_change_script' ]
    [ , [ @force_invalidate_snapshot = ] force_invalidate_snapshot ]
    [ , [ @force_reinit_subscription = ] force_reinit_subscription ]

Consideramos que con los tres primeros tenemos suficiente, quedando nuestra llamada así:

exec sp_repladdcolumn @source_object = 'FraAlbaranesLn'
, @column = 'Stamp'
, @typetext = 'timestamp NULL'

Pero nos preocupa el tercer parámetro, @Publication_to_add. en la documentación sólo habla de dos opciones. ALL para que el cambio afecte a todas las publicaciones donde se encuentra la tabla o bien determinar una publicación concreta en la que efectuar el cambio. El valor por defecto es ALL. Decidimos dejar el campo con su valor por defecto ya que sólo tenemos una publicación y lanzamos el script en un entorno d pruebas tal como lo he puesto anteriormente.

Al comprobar los resultados vemos que efectivamente el campo se ha creado en la tabla, pero también lo ha añadido al artículo marcándolo para replicación, lo que nos obligaría a reiniciar suscripciones, generación de snapshot, etc. (esto se puede evitar con los dos últimos parámetros, que no se invalide el snapshot actual y que no se fuerce la re-inicialización, pero consideramos que puede quedar algún tipo de inconsistencia en el hecho de haber ampliado artículos sin regenerar absolutamente nada ). Por lo que decidimos que la opción de dejarlo así la descartamos.

Tras darle un par de vueltas, pensamos que si hay una opción ALL, puede que haya una opción none que no esté en la documentación. Por lo que abrimos el procedure en cuestión y…Bingo! Allí está. La opción none. Básicamente, y aunque hay varias opciones jugando con las variables @is_for_merge, @is_for_tran, @not_for_merge y @not_for_tran, en el caso de no determinar la publicación mediante el uso de ese valor, termina haciendo lo que realmente queríamos desde el principio, añadir el campo pero no replicarlo.

-- if tran is enabled, but merge is not, sp_repladdcolumn with none pub list should just add the column
else if @not_for_merge = 1 and @not_for_tran = 0 and (LOWER(@publication_to_add) = 'none' or LOWER(@publication_to_add) = '[none]')
begin 
        exec ('alter table '+ @qual_source_object + ' add ' + @qual_column + ' ' + @typetext )
            if @@ERROR<>0
                goto FAILURE
end

Por lo tanto, nuestra llamada final al procedimiento queda así:

 

   1: exec sp_repladdcolumn @source_object = 'FraAlbaranesLn'
   2: , @column = 'Stamp'
   3: , @typetext = 'timestamp NULL'
   4: , @publication_to_add= 'none'

  Como curiosidad dejaros algunas partes significativas sobre el tema del procedimiento almacenado:

La cabecera del procedure con la definición y el valor que por defecto da al parámetro @publication_to_add

ALTER procedure [sys].[sp_repladdcolumn](
    @source_object            nvarchar (358),
    @column                    sysname,
    @typetext                nvarchar(3000),
    @publication_to_add        nvarchar(4000) = 'all',
    @from_agent                int = 0, 
    @schema_change_script    nvarchar(4000) = NULL,
    @force_invalidate_snapshot bit = 1,    /* Force invalidate existing snapshot */
    @force_reinit_subscription bit = 0    /* Force reinit subscription */

La explicación que da al parámetro del que dependerá que finalmente se añada al artículo o no

declare @is_for_merge   bit
-- 1 if merge is on AND the table is published in a merge publication AND the publication is in @publivation_to_add list

Por último,una parte de código clave en la que determina si el cambio es para hacer el merge o no.

if exists (select * from sys.objects where name = 'sysmergearticles')
begin
    if exists (select a.objid from sysmergearticles a, sysmergepublications p where a.objid=@objid
                and p.pubid = a.pubid
                and (p.name like @publication_to_add or charindex('[' + p.name + ']', @publication_to_add)>0))
    begin
        select @is_for_merge = 1
    end
end
else
    select @is_for_merge = 0

 

Nada más. Este artículo lo escribo como una pequeña queja sobre el hecho de no completar la documentación en algo tan simple como los valores de un parámetro con la consiguiente pérdida de tiempo y momentos de pánico añadido.

 Saludos

Lectura, escritura y copia de un mensaje en WCF I

La vida es como una sucesión de círculos que se repiten. Hace más de un año y medio, desarrollando nuestra primera aplicación con WCF se nos planteó el problema del que hablo a continuación. Hace unos días, a otro equipo de mi empresa le sucedió lo mismo. Debido a esto, he pensado que estaría bien comentarlo de nuevo.
Comentaros que no he vuelto a revisar las clases implicadas desde entonces, por si actualmente hay alguna otra solución al tema, por lo que se agradece cualquier sugerencia-puntualización que podáis darme.
En este post, plantearé el problema, para en los siguientes, explicar la manera como lo solucionamos en su momento.
Un mensaje de WCF es de un solo uso. ¿Qué quiere decir esto? Que sólo se puede acceder para realizar alguna acción sobre el mismo, ya sea de lectura, escritura o copia, cuando está creado y no se ha realizado anteriormente alguna de estas acciones sobre él.
Si por ejemplo, en algún proceso se intenta obtener el “Body” del mensaje, esto implicará una lectura del mismo, por lo que cualquier otro intento de lectura producirá el error: System.InvalidOperationException, indicándonos que el mensaje ya ha sido leído, Esto sucederá incluso en el caso de que se produzca algún tipo de error durante la lectura como podría ser un error de conversión en el tipo recibido durante el acceso al cuerpo del mensaje, porque como veremos más adelante, lo primero que hace ante una operación sobre el mensaje es comprobar su estado actual y si este es correcto o mejor dicho, adecuado, el siguiente paso es cambiarle al estado que corresponda.

Pueden producir este error varias acciones que llevemos a cabo en el mensaje, por ejemplo:
Dos llamadas consecutivas al método del mensaje, GetBody. volver a pasar el mensaje por la pila de canales para su reenvío a otro servicio mediante un cliente Proxy, si después de acceder al mensaje con GetBody, lo pasamos como parámetro en algún método del servicio; las posibilidades son muchas, en cualquier caso, el hecho común es intentar acceder al mensaje más de una vez.

El motivo de esta implementación del objeto Message, es que pueda admitir stream, por lo que de esta manera se garantiza que sólo se procese una vez.

La propiedad a a través de la cual WCF controla esta situación es State, la cual puede tomar los siguientes valores. Creado, Leido, Escrito, Copiado, y cerrado, que determinará que operaciones se pueden o no se pueden realizar con el mensaje.
La enumeración que nos proporciona los valores de esta propiedad es la siguiente:
Se encuentra en System.ServiceModel.Channels

Public enum MessageState
{
Created,
Read,
Writen,
Copied,
Closed
}
Obviamente la propiedad State hace que sea posible determinar en tiempo de ejecución si el cuerpo de un Message particular ya se ha consumido (cuando ya no está en estado Created) y cómo se consumió (si se leyó, escribió o copió).

Esta propiedad hace posible que en cualquier momento de la ejecución de nuestra aplicación, podamos saber si el mensaje ya ha sido tratado y que se hizo con el mismo, es decir, si se copió, escribió, etc.El único estado correcto que nos permitirá acceder al cuerpo del mensaje, ya sea para leer, escribir o copiar, será el estado Created.