Te paso un ejemplo bien completo de cómo lo hago yo:
** creacion de las tablas en sqlserver
** en la vida real tienen más campos, pero estos son los mínimos que
deberían tener
CREATE TABLE [dbo].[DETALLE](
[FECHA] [date] NOT NULL,
[TIPODOC] [char](3) NOT NULL,
[DOCNUM] [char](8) NOT NULL,
[CODIGO] [char](30) NOT NULL,
[CANTIDAD] [int] NOT NULL,
[PRECIO] [numeric](10, 4) NOT NULL,
[DT] [datetime] NOT NULL,
[IDETALLE] [int] IDENTITY(1,1) NOT NULL,
[IDCABECERA] [int] NOT NULL,
CONSTRAINT [PK_DETALLE] PRIMARY KEY CLUSTERED
(
[IDDETALLE] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY
=
OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [CN_IDDETALLE] UNIQUE NONCLUSTERED
(
[IDDETALLE] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY
=
OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[CABECERA](
[FECHA] [date] NOT NULL,
[CUENTA] [char](4) NOT NULL,
[TIPODOC] [char](3) NOT NULL,
[DOCNUM] [char](8) NOT NULL,
[IVA] [numeric](10, 2) NOT NULL,
[TOTAL] [numeric](10, 2) NOT NULL,
[IDCABECERA] [int] IDENTITY(1,1) NOT NULL,
[DT] [datetime] NOT NULL,
CONSTRAINT [PK_IDCABECERA] PRIMARY KEY CLUSTERED
(
[IDCABECERA] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY
=
OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
** lo siguiente se hace en Visual FoxPro
** generación de un cursor adapter para la tabla CABECERA
Local cCmd,cSch,cUFL,cUNL,lOK
Text to cCmd noshow pretext 15
SELECT
FECHA ,
CUENTA ,
TIPODOC ,
DOCNUM ,
IVA ,
TOTAL ,
IDCABECERA ,
DT
FROM CABECERA WHERE 1=0
EndText
Text to cSch noshow pretext 15
FECHA D,
CUENTA C(4),
TIPODOC C(3),
DOCNUM C(8),
IVA N(10,2),
TOTAL N(10,2),
IDCABECERA I,
DT T
EndText
Text to cUFL noshow pretext 15
FECHA ,
CUENTA ,
TIPODOC ,
DOCNUM ,
IVA ,
TOTAL ,
IDCABECERA ,
DT
EndText
Text to cUNL noshow pretext 15
FECHA CABECERA.FECHA,
CUENTA CABECERA.CUENTA,
TIPODOC CABECERA.TIPODOC,
DOCNUM CABECERA.DOCNUM,
IVA CABECERA.IVA,
TOTAL CABECERA.TOTAL,
IDCABECERA CABECERA.IDCABECERA,
DT CABECERA.DT
EndText
cUFL = Chrtran( cUFL, Chr(13) + Chr(10), " " )
cUNL = Chrtran( cUNL, Chr(13) + Chr(10), " " )
cSch = Chrtran( cSch, Chr(13) + Chr(10), " " )
cCmd = Chrtran( cCmd, Chr(13) + Chr(10), " " )
Use in Select("")
lOK = .t.
If !PemStatus(thisform,'oCA',5)
thisform.AddProperty('oCA')
EndIf
Try
Thisform.oCA = Createobject("CursorAdapter")
lOK = .t.
Catch to oErrores
MessageBox("No se pudo generar el objeto CursorAdapter"+;
Chr(13)+oErrores.message,16,"Atención",2000)
lOK = .f.
Finally
EndTry
If lOK = .t.
try
With thisform.oCA
.DataSourceType = "ODBC"
.DataSource = thisform.nHandle
.alias = "curCabecera"
.tables = "cabecera"
.BufferModeOverride = 5
.keyfieldlist = "idcabecera"
.sendupdates = .T.
.usetransactions = .f. && si están en falso, no usa
transacciones y funciona entonces ponerlas en manual en SQL Server
.selectcmd = cCmd
.updatablefieldlist = cUFL
.updatenamelist = cUNL
.cursorschema = cSch
.cursorfill()
EndWith
Select curCabecera
Catch to oErrores
MessageBox("No se pudo generar el Cursor
"+Chr(13)+oErrores.message;
,16,"Atención",2000)
lOK = .f.
endtry
endif
Return lOK
**(hacer otro cursor adapter para la tabla DETALLE y obtener el cursor
curDetalle)
** la sentencia where 1=0 genera un cursor vacío. Esto es para que
llenes el cursor adapter con los datos necesarios antes de
** grabar. Estos datos salen de textboxes, grillas, cálculos, etc.
** rutina de grabación
Local lOK,nPK,cCmd,nResults,lBatchMode
SQLExec(thisform.nHandle,'BEGIN TRANSACTION')
SQLSetProp(thisform.nHandle, "TRANSACTIONS", 2 ) && pone las
transacciones en manuales
Select curCabecera
Scatter name oCab blank fields except idcabecera
With oCab
.fecha = Date()
.cuenta = '1234'
.tipodoc = 'FRA'
.docnum = '12345678'
.iva = 210.00
.total = 1210.00
.dt = Datetime()
EndWith
** grabación de la cabecera y obtención de la clave primaria generada
** el campo idcabecera es autoincremental
Insert into curCabecera from name oCab
lOK = TableUpdate(.t.,.t.,'curCabecera')
If lOK = .t.
nPK = 0
nResults = 0
cCmd = [select @@IDENTITY as pk ]
lBatchMode = SQLGetProp(thisform.nHandle,"BatchMode" )
SQLSetProp(thisform.nHandle,"BatchMode" ,.f.)
SQLExec(thisform.nHandle,cCmd,'curPK')
Do while nResults <> 2
nResults = SQLMoreResults(thisform.nHandle)
enddo
SQLSetProp(thisform.nHandle,"BatchMode" ,lBatchMode)
nPK = curPK.pk
Use in Select('curPK')
Select curFactura && cursor auxiliar usado en el form para
insertar las lineas de detalle de la factura
&& tiene la misma estructura
que la tabla detalle del sql server
Scan all
Scatter name oDet fields except iddetalle
With oDet
.fecha = Date()
.tipodoc = 'FRA'
.docnum = '12345678'
.idcabecera = nPK && aqui pasa a ser la clave
foránea
.dt = Datetime()
*** los campos codigo, cantidad y precio ya están en el
objeto, obtenidos con SCATTER NAME
endwith
Insert into curDetalle from name oDet
EndScan
lOK = TableUpdate(.t.,.t.,'curDetalle')
endif
If lOK = .t.
SQLCommit(thisform.nHandle)
Else
SQLRollback(thisform.nHandle)
endif
SQLSetProp(thisform.nHandle,'Transactions',1 ) && transacciones
automáticas
A los cursor adapters les ponés la propiedad usetransactions en falso,
para que no maneje las transacciones directamente. Acordate que en SQL
Server, las transacciones son automáticas a menos que las pongas en
manual. Esto hace que cada vez que insertás,borrás o modificás un
registro, se abra y cierre una transacción automáticamente. No te
sirve
para el caso de una factura, porque si te da error alguna de las
tablas,
no podés cancelar una transacción completa. En cambio, si las
transacciones las ponés en manual, solamente se completa la
transacción
cuando le mandás un SQLCommit (o SQLRollback si falla)
Tenés que poner las transacciones del SQLServer en manual antes de
comenzar a grabar y luego de cerrar la transacción las ponés
nuevamente
en automáticas,para que el SQL Server siga funcionando normalmente
Fijate en estos artículos
http://www.universalthread.com/ViewPageArticle.aspx?ID=667
http://www.universalthread.com/ViewPageArticle.aspx?ID=817
Rafael Copquin