miércoles, 3 de agosto de 2011

DBExpress - Many to Many (Relaciones muchos a muchos) Parte 2

En la ultima entrada vimos como trabajar relaciones muchos a muchos con dbexpress. Analizamos como relacionar los datasets y demás componentes de tal forma que casi sin escribir código logramos que moviéndome sobre un registro me muestre los elementos incluidos en el y los disponibles a ser incluidos. Hasta acá funciono todo sin problemas, ahora llega el momento de meter elementos dentro de los asignados, es decir, tomar elementos disponibles y meterlos en el lote de asignados para dejar luego que dbexpress se encargue de los inserts/deletes relacionados. Usamos un ejemplo de usuarios-permisos por ser sencillo y descriptivo y armamos un form con una grilla superior que muestra los usuarios y dos grillas inferiores mostrando permisos asignados y disponibles.
Pongamos ahora dos botones para que se puedan enviar los permisos de disponibles a asignados y viceversa.


Hagamos luego también una primera aproximación a la solución en términos de código

procedure TFrmUsuario.btnAsignarClick(Sender: TObject);
begin
  cdsPermisosAsignados.Insert;
  cdsPermisosAsignadosIdPermiso.Value := cdsPermisosDisponiblesIdPermiso.Value;
  cdsPermisosAsignados.Post;
  cdsPermisosDisponibles.Delete;

end;

procedure TFrmUsuario.Button1Click(Sender: TObject);
begin
  cdsUsuario.ApplyUpdates(-1);
end;

procedure TFrmUsuario.Button2Click(Sender: TObject);
begin
  cdsUsuario.CancelUpdates;
end;

Esto funciona y funciona bien si hablamos solo de asignación de permisos.
Asigna correctamente los permisos y se encarga de actualizar la nueva realidad en la tabla de relación, pero solo funciona correcta y completamente hacia un lado, si intentara la inversa, quitar un permiso asignado tendría el problema de que no tengo el campo nombre para llenar el clientDataSet del los permisos disponibles. Algunas modificaciones en el dataset que provee los datos podrían ayudar. 
Lo que originalmente fue:

select *
from UsuarioPermiso
Where Login=:Login

Ahora es:

select UsuarioPermiso.*, Permiso.Nombre
from UsuarioPermiso inner join Permiso
On UsuarioPermiso.IdPermiso=Permiso.IdPermiso
Where Login=:Login

Muy importante ver que el campo Nombre no pertenece a la tabla relación y por tanto no debe ser parte de los inserts y delete, mediante los providerflags de este campo desactivar su participacion en pfInUpdate y en pfInWhere,  (En ClientDataSet y DataSet), es un campo invitado en la consulta simplemente para mejorar aspectos de interfaz de usuario.


Ahora la interfaz muestra el nombre de los permisos





Finalmente el código quedaría así:

procedure TFrmUsuario.btnAsignarClick(Sender: TObject);
begin

  cdsPermisosAsignados.Insert;
  cdsPermisosAsignadosIdPermiso.Value := cdsPermisosDisponiblesIdPermiso.Value;
  cdsPermisosAsignadosNombre.Value := cdsPermisosDisponiblesNombre.Value;
  cdsPermisosAsignados.Post;
  cdsPermisosDisponibles.Delete;

end;

procedure TFrmUsuario.btnDesasignarClick(Sender: TObject);
begin
  cdsPermisosDisponibles.Insert;
  cdsPermisosDisponiblesIdPermiso.Value := cdsPermisosAsignadosIdPermiso.Value;
  cdsPermisosDisponiblesNombre.Value := cdsPermisosAsignadosNombre.Value;
  cdsPermisosDisponibles.Post;
  cdsPermisosAsignados.Delete;

end;

procedure TFrmUsuario.Button1Click(Sender: TObject);
begin
  cdsPermisosDisponibles.CancelUpdates;
  cdsUsuario.ApplyUpdates(-1);
   cdsUsuario.Refresh;
end;

procedure TFrmUsuario.Button2Click(Sender: TObject);
begin
  cdsUsuario.CancelUpdates;
end;

procedure TFrmUsuario.cdsUsuarioBeforeScroll(DataSet: TDataSet);
begin
  if cdsUsuario.ChangeCount>0 then begin
    ShowMessage('Hay cambios pendientes en el registro actual, los perdera');
    cdsUsuario.CancelUpdates;
    cdsUsuario.Refresh;
  end;
end;

Observe que antes de aplicar los cambios cancelo los cambios referentes los registros disponibles, los mismos son resultado del calculo de la consulta sobre lo que esta en la base y no deben tener injerencia sobre la misma. Observe también que esta consulta debe actualizarse constantemente, si aplique un cambio a la base necesito actualizar esto, si me intentan cambiar de registro en usuario y no guardaron los cambios debe preguntar que se debe hacer y guardo y refresco o cancelo todo.
El objetivo de la entrada es entender la forma el concepto principal, el lector luego puede mejorarlo, incluso se pueden activar/desactivar los botones de asignación y desasignación segun disponibilidad, ese tipo de detalles no serán contemplados aquí.