Hay una forma que nunca terminé utilizando, con los siguientes comandos entre otros:
- TABLEUPDATE
- CURSORGETPROP("UpdatableFieldList",lcALias)+","
- CURSORSETPROP("Buffering", 3, "cLista")
- CURSORSETPROP("SendUpdates", .F.)
Lo que hago generalmente es leer el cursor, habilitando las columnas a editar del grid.
Puedes guardar el grid o parte de el con un SELECT/READWRITE o con APPEND FROM como señalaron, para cuando se manden las actualizaciones, solo modificar las filas cambiadas en el motor.
También se pueden determinar los cambios realizados con GETNEXTMODIFIED, OLDVAL, CURVAL, TABLEUPDATE, TABLEREVERT (esto es muy interesante).
Las filas que hayas modificado, las conviertes en XML
=CURSORTOXML("cUbicaciones", "lcXMLUbicaciones", 2)
y envías esta cadena a un procedimiento almacenado. En el procedimiento almacenado, se recibe este parámetro como tipo xml.
Acá te expongo un ejemplo de UPDATE dentro del SP. Hay que poner cuidado, en que los alias coincidan (el puesto en el CURSORTOXML y el /VFPData/cubicaciones).
Todas las actualizaciones de múltiples filas las manejo así. Paso como parámetro una tabla. NUNCA HE NECESITADO MAS QUE SQLEXEC. No se puede renunciar a los objetos de una base de datos: transacciones (hay también anidadas, hasta cierto punto, etc.), triggers, sp, (bloqueos y optimizaciones, que ni sabemos cómo funcionan), pk, fk, etc.; que nos ofrece el motor que estamos trabajando.
UPDATE pb
SET pb.ubicacion = txml.ubicacion
FROM tbl_producto_bodega pb INNER JOIN
(
SELECT tbl.col.value('@codigo_local', 'char(10)') AS codigo_local
, tbl.col.value('@codigo_bodega', 'char(10)') AS codigo_bodega
, tbl.col.value('@ubicacion', 'varchar(15)') AS ubicacion
FROM @xml_ubicaciones.nodes('/VFPData/cubicaciones') AS tbl(col)
) txml
ON pb.rut_empresa = @rut_empresa
AND pb.codigo_local = txml.codigo_local
AND pb.codigo_bodega = txml.codigo_bodega
AND pb.codigo_producto = @codigo_anterior;