jueves, 14 de julio de 2011

Master-Details y Autoincrementales

Ahora si, este tema no puedo decir que no esta documentado, he encontrado tutoriales y papers que describen el problema, eso si, siempre en Ingles y tampoco son tantos los lugares donde uno pueda encontrar una solución a este asunto.
Como vimos en la entrada

http://pablosoligo.blogspot.com/2011/07/dbexpressmaster-detail.html

DBExpress maneja muy bien la idea de maestro detalle, lo maneja como un elemento único simplificando el desarrollo, es mas, solo se encarga de llenar el campo de relación en el detalle y lo llena correctamente con el identificado del maestro.
Rapidamente aparece un problema con los autoincrementales, el valor clave del maestro lo pone la base, cuando dbexpress intenta insertar los registros detalles no sabe la clave del maestro, de hecho el problema aparece antes ¿Que clave lleva el detalle en el ClientDataSet cuando siquiera se la clave que va a tener el maestro?

Primero ataquemos el problema que acabamos de mencionar, voy a cambiar la base de datos de ejemplo vista en las entradas anteriores para indicar que ahora el idPedido es autoincremental, en una oracle sera colocar un trigger, en Interbase un generator o como sea según motor, la idea es que el valor lo pone la base y no yo.






Como ven, el clientDataSet(Maestro) tiene un evento que se ejecuta cuando hay un nuevo registro, tomemos esto para colocar un IdPedido temporal o comodín si así lo quieren llamar. Luego los detalles se generaran con este IdPedido comodín.

procedure TFrmPedido.cdsPedidoNewRecord(DataSet: TDataSet);
begin
  DataSet.FieldByName('IdPedido').Value := -1;
end;




Esto es una simplificación, si yo intentara ingresar mas registros maestros fallaría porque crearía dos registros con IdPedido=-1, solución y truco para esto.


procedure TFrmPedido.cdsPedidoNewRecord(DataSet: TDataSet);
begin
  DataSet.FieldByName('IdPedido').Value := DataSet.FieldByName('IdPedido').Tag - 1;
  DataSet.FieldByName('IdPedido').Tag := DataSet.FieldByName('IdPedido').Value;
end;

Ayudándonos con la propiedad tag y sin declarar mas variables hacemos que nuestro IdPedido comodín se incremente hacia el negativo. Vamos a usar es breve este valor comodín.

Ahora el siguiente problema, si el valor lo IdPedido lo pone la base, entonces no lo pongo yo.
¿Como le digo a DBExpress insertame todos los campos menos este que lo hace la base de datos?
La respuesta es simple, del lado servidor (Suponiendo trabajo con DataSnap) buscamos el DataSet, seleccionamos en campo y en providerflags ponemos el pfInUpdate en false, con esto cuando se genere el insert DBExpress dejara fuera a este campo. La figura muestra como hacerlo.






En esta entrada http://pablosoligo.blogspot.com/2011/07/logica-del-negocio.html  se demuestra que puedo tener injerencia en los datos justo antes de ser insertados efectivamente, ¿Como usar esto a mi favor y poner en orden el tema de las claves en este maestro-detalle?

El SqlServer se encargara de poner el valor de IdPedido al maestro, es mi responsabilidad ponerlos en el detalle. Como vimos en la entrada anterior puedo meterme justo antes de que se inserte el registro e interferir en la operación, veamos como:

procedure TSMPedido.dspPedidoBeforeUpdateRecord(Sender: TObject;
  SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind;
  var Applied: Boolean);
begin
  inherited;
  if (UpdateKind=ukInsert) and (SourceDS=sqlPedido) then begin
    DeltaDs.FieldByName('FechaHora').NewValue := Now();
  end;

  if (UpdateKind=ukInsert) and (SourceDS=sqlItemPedido) and (DeltaDS.FieldByName('IdPedido').NewValue<0) then begin
      sqlTableIdentity.Close;
      sqlTableIdentity.ParamByName('TableName').Value := 'Pedido';
      sqlTableIdentity.Open;
      DeltaDS.FieldByName('IdPedido').NewValue  := sqlTableIdentity.FieldByName('Identidad').Value;
  end;
end;


sqlTablaIdenty es un TSqlDataSet con el siguiente CommandText

SELECT IDENT_CURRENT(:TableName) as Identidad


En sqlServer retorna la identidad para una tabla en una conexión determinada, el lector sabrá como hacerlo en su motor de base de datos favorito, si utilizo un esquema de herencia para la creacion de servermodules mucho mejor porque puede poner este sqldataset en el servermodule padre y tenerlo listo en cualquier server module donde lo necesite usar.
Por lo demas lo que se hace es preguntar si es una inserción, si el dataset origen es el del detalle y si el valor de idPedido del detalle es negativo, pensemos que me puede estar insertando un detalle mas en un pedido previamente creado que ya tiene su IdPedido correctamente generado, ahí no necesito preguntar a la base y asignarlo por la fuerza.





No hay comentarios:

Publicar un comentario