Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Lazarus, FreePascal, Kylix, etc. (https://www.clubdelphi.com/foros/forumdisplay.php?f=14)
-   -   forma correcta de acceder y guardar en firebird con ibx (https://www.clubdelphi.com/foros/showthread.php?t=90667)

anubis 08-08-2016 01:44:47

forma correcta de acceder y guardar en firebird con ibx
 
Hola amigos,

Le ando dando vueltas a si estoy programando correctamente.
Ya hice algunos meses algunas aplicaciones usando lazarus, componentes zeos y firebird. En principio todo funcionaba bien, pero despues de unos meses, me comentan que, en algunas ocasiones, no esta guardando los registros.En otros casos, he usado componentes ibx para lazarus, y hace exactamente lo mismo.
Lo primero que hago, es crear un datamodule y ahi le meto la basededatos, un dataset y un datasource. Los tres ligados. Con eso ya puedo acceder sin problemas.
Código Delphi [-]
 fmodulodatos.ibproductos.SelectSQL.clear;
   fmodulodatos.ibproductos.selectsql.Text:='select * from productos order by nombre_producto';
   fmodulodatos.ibproductos.Open;
   fmodulodatos.ibcomprasdetalle.SelectSQL.clear;
   fmodulodatos.ibcomprasdetalle.SelectSQL.Text:='select * from comprasdetalle where id_compra=:idcompra';
   fmodulodatos.ibcomprasdetalle.ParamByName('idcompra').asinteger:=idcompra;
   fmodulodatos.ibcomprasdetalle.Open;

Tambien, sino quiero alterar el ibdataset de productos por ejemplo, lo que hay generar un ibdataset temporal al que le asigno la tabla productos con otros parametros para visualizar en un dbgrid por ejemplo.

A la hora de guardar, simplemente aplico lo siguiente:

Código Delphi [-]
with fmodulodatos do
   begin
   ibcomprasdetalle.insert;
   ibcomprasdetalle.FieldByName('id_compra').asinteger:=idcompra;
   ibcomprasdetalle.fieldbyname('id_producto').asinteger:=dblookupproducto.KeyValue;
   ibcomprasdetalle.fieldbyname('cantidad').asinteger:=icantidad;
   ibcomprasdetalle.fieldbyname('preciocoste').asfloat:=preciocost;
   ibcomprasdetalle.fieldbyname('lote').asstring:=lote.Text;
   ibcomprasdetalle.fieldbyname('fecha_caducidad').asdatetime:=datefechacaducidad.Date;
   ibcomprasdetalle.Post;
INVENTARIO(DBLOOKUPPRODUCTO.KEYVALUE,icantidad); // es otra procedure donde hago lo mismo con inventarios
   precios(dblookupproducto.KeyValue,preciocost); // otra procedure donde hago lo mismo con precios
   ibtransaction1.CommitRetaining;  
end;

No se que mas hay que hacer para que no de problemas posteriores.
En las tablas tengo asignados los triggers, generators y en cada tabla, si hace falta, añado indices ademas de los que se generan solos por la primary key.

Q estoy haciendo mal.

Casimiro Notevi 08-08-2016 11:52:42

Los registros no se pueden borrar porque pongas componentes en un datamodule o en un form, tendrás que revisar posts, transacciones, etc.
Aparte de eso, en el datamodule principal te aconsejo poner la conexión a la base de datos y la transacción. Luego, puedes tener un datamodule independiente para compras, otro para ventas, otro para 'otras cosas'...
El datasource lo pones en el form donde está el dbgrid que quieras mostrar.
Eso es basicamente.

anubis 08-08-2016 16:12:24

Gracias por contestar Casimiro Notevi,
Si, lo que hago es eso, poner una base de datos, la transaccion, un dataset y un datasource todo en un datamodule.
Por lo que me dices, puedo poner datamodules separados y los datasource acompañando en los forms donde vayan por ejemplo los dbgrid.
De todas formas, ponerlos juntos o separados no debe de influir solo seria por estetica de diseño?.

Entiendo ademas que, segun lo que he descrito en el primer mensaje del post, esta correcta la forma: insertar o edit, asignar y postear, despues tan solo hace un commitretaining. No debiera de haber problemas.

Gracias de nuevo.

Casimiro Notevi 08-08-2016 17:08:49

Cita:

Empezado por anubis (Mensaje 507493)
De todas formas, ponerlos juntos o separados no debe de influir solo seria por estetica de diseño?.

No es por estética, es porque la conexión a la base de datos y transacción sirve para todo el programa y, sin embargo, el datasource de un dbgrid es solamente para ese dbgrid que está en un form. Y es más cómodo asignarlo porque no hay que incluir en el uses el datamodule, ni tampoco hay que escribir más código innecesario: datamodulexx.datasource.....
Cita:

Empezado por anubis (Mensaje 507493)
Entiendo ademas que, segun lo que he descrito en el primer mensaje del post, esta correcta la forma: insertar o edit, asignar y postear, despues tan solo hace un commitretaining. No debiera de haber problemas.

Es muy difícil poder aconsejar porque no podemos ver nada más, ni siquiera sabemos qué parámetros has puesto en las transacciones.

anubis 09-08-2016 01:16:02

Ah perdon,
en transaction tengo:
defaultaction->tacommitretaining

en parametros
read_commited
rec_version
nowait

del resto, no se que mas opciones comentaros. :(

Casimiro Notevi 09-08-2016 10:36:54

En principio, todo bien, tendrás que verificar exactamente qué datos son esos que se pierden y verificar esa parte de tu código.
Porque perderse datos es imposible, salvo que los borren a propósito.

anubis 21-02-2017 16:24:30

Hola.
Perdón que reabra este post después de tanto tiempo de inactividad pero digamos que estuve ausente del tema por algunos problemas.

Retomando lo que preguntaba y, viendo que Casimiro Notevi ya me contestó que, en principio, todo se veía más o menos bien, quería añadir lo siguiente:

Si tengo dos tablas, una de productos y otra de precios.
Cuando doy de alta un producto, lo que hago es lo siguiente:

Bueno, lo primero es tener asignadas las tablas y abiertas.

Código Delphi [-]
with modulodatos do
begin  
   ibproductos.SelectSQL.clear;
   ibproductos.selectsql.Text:='select * from productos';
   ibproductos.Open;

   ibprecios.SelectSQL.clear;
   ibprecios.selectsql.Text:='select * from precios';
   ibprecios.Open;
end;

Código Delphi [-]
with modulodatos do
begin
   ibproductos.insert;
   ibproductos.fieldbyname('codigo_barras').asstring;
   ibproductos.fieldbyname('nombre_producto').asstring;
   ibproductos.post;
   ibtransaction1.CommitRetaining;  

   ibtemporal.selectsql.clear;
   ibtemporal selectsql.text:='select max(id_producto) as ultimo_id from productos'; // para sacar el último producto que acabo de añadir.
   ibtemporal.open;
   
   ultimo_producto:=ibtemporal.fieldbyname('ultimo_id').asstring;
   
   ibprecios.insert;
   ibprecios.fieldbyname('id_producto').asinteger:=ultimo_producto;
   ibprecios.fieldbyname('precio_costo').asfloat:=precio_costo;
   ibprecios.post;
   
   ibtransaction1.commitretaining;

end;

En principio eso funciona sin mayor problema, imagino que no es la mejor manera de programarlo.

Sólo tengo el problema si quiero hacer esto:

Código Delphi [-]
producto_registrado:=ibproducto.fieldbyname('id_producto').asinteger;
nombre_producto:=ibproducto.fieldbyname('nombre_producto').asstring;

Es el último producto que acabo de añadir.
El id_producto aparece en 0 pero el nombre_producto si me lo devuelve.
Si le añado un dbgrid para probar, el id_producto aparece vacío.
Ya he probado a cerrar la tabla y vuelto a abrir, a refrescarla, ... Pero con los mismos resultados.

La idea de todo lo anterior, es poder insertar un producto y tambien su precio en otra tabla.

Perdonen que ande preguntando sobre estas cosas, sé que son cosas muy básicas pero no consigo resolverlas.

Gracias.

AgustinOrtu 21-02-2017 18:56:19

Cuando grabas el precio, en el medio pedís el último ID del producto. Ese paso se hace correctamente? Es decir, se graba el ID correspondiente, o te graba 0? Estás usando un generador para el ID?

anubis 21-02-2017 19:44:27

Gracias por contestar ;).

Si, al pedir el ultimo id si lo saca correctamente, de momento no he tenido problemas con eso.

El problema lo tengo cuando quiero sacar el id directamente de la tabla productos de ese registro. Algo me debe de faltar en la transaccion, la tabla, la base de datos o no se, para que actualice id_producto.

O no se como debiera de hacerse.

ecfisa 22-02-2017 18:34:46

Hola.

De tu código me surge una duda:
Código Delphi [-]
  with modulodatos do
  begin
   ibproductos.insert;
   ibproductos.fieldbyname('codigo_barras').asstring;
   ibproductos.fieldbyname('nombre_producto').asstring;
   ibproductos.post;
   ibtransaction1.CommitRetaining;
¿ No le asignas un valor a la columna ID_PRODUCTOS de la tabla productos antes de guardar ?

Cita:

Empezado por anubis (Mensaje 513558)
...
Si, al pedir el ultimo id si lo saca correctamente, de momento no he tenido problemas con eso.

El problema lo tengo cuando quiero sacar el id directamente de la tabla productos de ese registro. Algo me debe de faltar en la transaccion, la tabla, la base de datos o no se, para que actualice id_producto.

Si al consultar el último ID lo obtenes bién, ¿ Seguro que estas posicionado en el registro correcto cuando consultas de forma directa el valor del campo ?

Saludos :)

anubis 28-02-2017 22:04:52

Gracias por las respuestas,

Uso un generador asi que no asigno ningun id_producto a "mano".
Respecto a si posiciono bien, ya lo he resuelto, tuve que usar un locate para posicionarme.
Y, sobre sacar el id_producto despues de hacer post y commitretaining, no tengo más remedio que cerrar la tabla y volverla a abrir.

Esto es normal?. Cada vez, cerrar y abrir tablas?

AgustinOrtu 28-02-2017 22:25:37

Yo utilizo la clausula RETURNING del INSERT INTO para que me devuelva el valor de Id que el generador le dio al registro que estoy insertado. De esta manera evitas problemas de concurrencia y hasta es mas eficiente, puesto que en una sola operacion insertas y obtenes el id que necesitas

Casimiro Notevi 28-02-2017 22:30:33

Cita:

Empezado por AgustinOrtu (Mensaje 513806)
Yo utilizo la clausula RETURNING del INSERT INTO para que me devuelva el valor de Id que el generador le dio al registro que estoy insertado. De esta manera evitas problemas de concurrencia y hasta es mas eficiente, puesto que en una sola operacion insertas y obtenes el id que necesitas

^\||/^\||/^\||/

ecfisa 01-03-2017 01:12:31

Hola.
Cita:

Empezado por anubis (Mensaje 513805)
Uso un generador asi que no asigno ningun id_producto a "mano".

Entonces, para obtener el ID recién generado, sin alterar su valor:
Código SQL [-]
SELECT GEN_ID(G_TUTABLA, 0) AS NEXT_ID FROM RDB$DATABASE
Y para obtener el próximo ID ( incrementando su valor ):
Código SQL [-]
SELECT GEN_ID(G_TUTABLA, 1) AS NEXT_ID FROM RDB$DATABASE
Cita:

Empezado por anubis (Mensaje 513805)
Y, sobre sacar el id_producto despues de hacer post y commitretaining, no tengo más remedio que cerrar la tabla y volverla a abrir.

No necesariamente. Podes obtener el último ID de forma directa, o haciéndote una función, independientemente de si la tabla está abierta o cerrada:
Código Delphi [-]
function Tmodulodatos.TuTablaGetLastID: Int64;
begin
  IBQuery1.Close;
  IBQuery1.SQL.Text := 'SELECT GEN_ID(G_TUTABLA, 0) AS NEXT_ID FROM RDB$DATABASE';
  IBQuery1.Open;
  Result := IBQuery1.FieldByName('NEXT_ID').AsInteger;
  IBQuery1.Close;
end;
Y claro que también como te indica Agustín, al momento de realizar la inserción.

Saludos :)

anubis 01-03-2017 04:44:27

Gracias.

Pues si, son opciones a tener en cuenta y las voy a probar ;).

De todas formas, el problema que tengo es que, despues de insertar un nuevo registro, si pido ese registro el, id_producto por ejemplo, me devuelve 0 y en el dbgrid me sale en blanco.
Lo soluciono cerrando la tabla y abriendola de nuevo.
Ya probe con refresh y tampoco me lo resuelve.

ecfisa 01-03-2017 11:14:02

Hola.
Cita:

Empezado por anubis (Mensaje 513818)
...
De todas formas, el problema que tengo es que, despues de insertar un nuevo registro, si pido ese registro el, id_producto por ejemplo, me devuelve 0 y en el dbgrid me sale en blanco.
Lo soluciono cerrando la tabla y abriendola de nuevo.
Ya probe con refresh y tampoco me lo resuelve.

Es que cuando Firebird incrementa el generador desde un trigger (vg. BEFORE INSERT) el DataSet no se entera del nuevo valor asignado hasta que lo cierres y abras nuevamente.

Una cosa que podes hacer es usar la propiedad GeneratorFielddel TIBDataSet, ejemplo:
Código Delphi [-]
procedure TForm1.FormCreate(Sender: TObject);
var
  DS: TIBDataSet;
begin
  DS := IBDataSet1;
  DS.GeneratorField.Generator   := 'G_TABLA';
  DS.GeneratorField.Field       := 'ID';
  DS.GeneratorField.IncrementBy := 1;
  DS.GeneratorField.ApplyEvent  := gamOnPost;
end;
(Configuré la propiedad en tiempo de ejecución para clarificar, pero podes hacerlo en tiempo de diseño desde el Inspector Object)


Hay dos cosas que olvidé mencionarte, la primera es que los componentes IBX para Delphi hasta la versión 7.8 inclusive, no soportan la cláusula RETURNING (no sé si es así en versiónes posteriores de IBX).

La segunda y mas importante, es que no uses el modo del mensaje #7
Código SQL [-]
SELECT MAX(ID_PRODUCTO) as ULTIMO_ID FROM PRODUCTO
para obtener el último identificador en un entorno multiusuario, por que fácilmente se podría generar incongruencia en los datos. Lo conveniente es solicitar la generación de un ID
Código SQL [-]
SELECT GEN_ID(G_PRODUCTO, 1) AS TENTATIVE_ID FROM RDB$DATABASE
, almacenarlo y luego utilizarlo (Commit) o bién descartar ese valor (Rollback).

Saludos :)

anubis 01-03-2017 17:05:54

Muchas gracias eficsa,
Lo voy a checar, aunque ya me has aclarado bastante, sobretodo con la duda que llevo mucho tiempo sin resolver, que el dataset no se entera del id generado hasta que no cierre y vuelva a abrir.

Y, respecto al generador, usare tu solución ;), es entendible y ademas, llevo con esto mucho tiempo pero sigo siendo novato, incluso no creo que llegue a esa categoria por el tiempo :(.


La franja horaria es GMT +2. Ahora son las 08:31:58.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi