Colabora .NET |
Inserción Cabecera-Detalle con XML, ejemplo práctico
Fecha: 27/Sep/2006 (19-09-06)
|
IntroducciónEn marzo de 2006 escribi un artículo sobre la posibilidad de insertar cabeceras-detalles utilizando XML en SQL 2000 (http://www.elguille.info/colabora/NET2006/sebaconte_UtilizacionCabeceraDetalle.htm), por ese motivo, creo imprescindible terminar de cerrar la idea con una pequeña aplicación en VB.NET 2005 a fin de poder utilizar el ejemplo. ProblemáticaSimularemos un módulo de carga de pedidos para un cliente. para esto crearemos un formulario simple con los datos de cabecera : Cliente - Nro de Pedido y Fecha, todos estos datos en este caso lo haremos con texto y sin validaciones ya que el objetivo es utilizar el XML para el alta. Y en la parte inferior esta la carga de los items del pedido que se mostrarán a medida que se carguen en un listview, al confirmar la operación se insertará en una sola transacción tanto la cabecera del pedido como su detalle, para luego ser visualizado en un treeview. Primero debemos crear los scripts de las tablas, utilizaremos solo 2, pedido e itempedido. CREATE TABLE [dbo].[Itempedido] ( [IdItempedido] [int] IDENTITY (1, 1) NOT NULL , [IdPedido] [int] NOT NULL , [articulo] [varchar] (255) NULL , [cantidad] [int] NULL ) ON [PRIMARY] GO CREATE TABLE [dbo].[Pedido] ( [IdPedido] [int] IDENTITY (1, 1) NOT NULL , [Cliente] [char] (50) NULL , [NroPedido] [char] (50) NULL , [fecha] [datetime] NULL ) ON [PRIMARY] GO Luego en base a lo explicado en el artículo de marzo, crearemos un stored procedure que reciba como parámetro el XML para dar de alta ítems de pedidos. El XML poseerá el siguiente formato: <?xml version="1.0"?> <ROOT> <Pedido Cliente="Nombre Cliente" Numero="Numero Pedido(numérico)" Fecha="fecha Pedido Formato MM/DD/YYYY"> <ItemPedido Articulo="articulo 1" Cantidad="1"/> <ItemPedido Articulo="articulo 2" Cantidad="2"/> <Pedido> <ROOT>" Los TAGS que poseemos para procesar son los siguientes "<?xml version="1.0"?> : Dentro de este TAG definimos la versión y luego veremos la codificación <ROOT> la cabecera del XML <Pedido> Contiene cada cabecera de los pedidos <ItemPedido> Contiene el detalle de cada pedido
En base al formato del XML crearemos el siguiente stored procedure. CREATE Procedure AltaCabeceraDetalleXML @XML text as DECLARE @hDoc int Begin transaction/*Preparamos el document XML en base al texto XML*/ EXEC sp_xml_preparedocument @hDoc OUTPUT, @XML Declare @idpedido int /*Aqui insertamos en la tabla pedido desde el XML leyendo el TAG pedido*/ Insert into Pedido SELECT * FROM OPENXML(@hDoc, N'ROOT/Pedido'/) with (Cliente char(50) ) /*Aqui rescatamos el id asignado al pedido para utilizarlo en los detalles */ Select @idPedido =@@scope_identity If @@error <>0 Rollback Tran Else /*En esta parte insertamos los detalles desde el XML, e incluimos la variable @idPedido para insertar en el campo impedido*/ Insert into ItemPedido SELECT @idPedido,* FROM OPENXML(@hDoc, N'ROOT/Pedido/ItemPedido'/) with ( Articulo varchar(255) , Cantidad int ) /*Removemos la representacion interna del documento XML*/ EXEC sp_xml_removedocument @hDoc If @@error <>0 Begin Rollback Tran Return 55000 End Else Commit Tran GO Con estos elementos poseemos la estructura necesaria para realizar las inserciones Cabecera Detalle transaccionada, nos resta realizar una aplicación que genere el string XML y lo envíe como parámetro al sp antes creado. Restricciones del XML Los caracteres: >, < , " , ' , & , deben reemplazarse al generar el string XML según la siguiente tabla:
Así si nuestro XML contiene alguno de estos caracteres antes de enviarlo al stored deben ser reemplazados. Luego en el código observaremos como se resuelven los acentos y caracteres mas allá del código 127 que es el default. Debido a esto se puede implementar o un reemplazo de todos los caracteres cuyo código sea mayor a 127 o bien cambiar la codificación de envío del XML en el tag <?xml version="1.0"?> agregando lo siguiente encoding=" iso-8859-1" con lo que el tag quedaría <?xml version="1.0" encoding=" iso-8859-1"?>. La función ArmarXML del código de ejemplo es clara y abarca distintas situaciones. También debemos recordar que el XML es case sensitive es decir diferencia minúsculas de mayúsculas. Aplicación para utilizar Para clarificar el concepto desarrolle la siguiente mini aplicación, en realidad se trata de un form que nos permite ingresar para la cabecera:
Y para el detalle 2 campos (Articulo y cantidad) que alimentarán un listview, luego de completar los datos presionamos Aceptar y realiza la inserción para mostrarla en el treeview. También he agregado 4 radio buttons con el objetivo de mostrar como se comporta el XML dependiendo de la codificación que elijamos y si utilizamos el reemplazo de caracteres especiales o la codificación ISO-8859-1. A continuación les presento todo el código explicando mas que nada la función ArmarXML la cual es llamada por el evento click del botón Aceptar, que en base a la opción de codificación realizará los reemplazos necesarios.
Private Function ArmarXML(ByVal Parametro As Integer) As String Dim PrimerCentro As Boolean Dim strXml As String Dim Contador As Integer Dim items As Integer Dim i As Integer Dim d As Integer Dim Cabecera As String Dim icontador As Integer icontador = 0 Contador = 1 items = 1 strXml = "" Cabecera = "" 'Validaciones 'ESTOS CARACTERES SIEMPRE HAY QUE REEMPLAZARLOS txtcliente.Text = Replace(txtcliente.Text, "'", "'") txtcliente.Text = Replace(txtcliente.Text, Chr(34), """) txtcliente.Text = Replace(txtcliente.Text, ">", ">") txtcliente.Text = Replace(txtcliente.Text, "<", "<") txtcliente.Text = Replace(txtcliente.Text, "&", "&") For i = 0 To ListView1.Items.Count - 1 For d = 0 To 1 ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "'", "'") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, Chr(34), """) ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, ">", ">") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "<", "<") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "&", "&") Next d Next i Select Case Parametro Case 1 ' Texto sin codificacion ISO, ' reemplazamos los caracteres alfabeticos comunes invalidos ' Recorro todos la cabecera para reemplazar los caracteres inválidos" Cabecera = "<?xml version=" & Chr(34) & "1.0" & Chr(34) & "?>" txtcliente.Text = Replace(txtcliente.Text, "ñ", "ñ") txtcliente.Text = Replace(txtcliente.Text, "á", "à") txtcliente.Text = Replace(txtcliente.Text, "é", "é") txtcliente.Text = Replace(txtcliente.Text, "í", "í") txtcliente.Text = Replace(txtcliente.Text, "ó", "ó") txtcliente.Text = Replace(txtcliente.Text, "ú", "ú") txtcliente.Text = Replace(txtcliente.Text, "Á", "Á") txtcliente.Text = Replace(txtcliente.Text, "É", "É") txtcliente.Text = Replace(txtcliente.Text, "Í", "Í") txtcliente.Text = Replace(txtcliente.Text, "Ó", "Ó") txtcliente.Text = Replace(txtcliente.Text, "Ú", "Ú") txtcliente.Text = Replace(txtcliente.Text, "Ñ", "Ñ") txtcliente.Text = Replace(txtcliente.Text, "¿", "¿") ' Recorro todos los items del listview para reemplazar los caracteres inválidos" For i = 0 To ListView1.Items.Count - 1 For d = 0 To 1 ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "ñ", "ñ") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "á", "à") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "é", "é") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "í", "í") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "ó", "ó") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "ú", "ú") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "Á", "Á") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "É", "É") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "Í", "Í") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "Ó", "Ó") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "Ú", "Ú") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "Ñ", "Ñ") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "¿", "¿") ' agregando " encoding="iso-8859-1" al XML ' se resuelve lo de arriba pero quedan caracteres " 'que(pueden) traesr problemas como el de diametro "Ø" ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "Ø", "Ø") Next d Next i Case 2 ' Similar al anterior pero reemplazamos los caracteres > a 127 por su codigo For icontador = 128 To 255 txtcliente.Text = Replace(txtcliente.Text, Chr(icontador), "&#" _ & icontador.ToString & ";") ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, _ Chr(icontador), "&#" & icontador.ToString & ";") Next Cabecera = "<?xml version=" & Chr(34) & "1.0" & Chr(34) & "?>" Case 3' No reemplazamos ningun caracter Cabecera = "<?xml version=" & Chr(34) & "1.0" & Chr(34) _ & " encoding=" & Chr(34) & "iso-8859-1" & Chr(34) & "?>" Case 4' Reemplazamos algun caracter que la codificacion ISO no acepte (me ha pasado) Cabecera = "<?xml version=" & Chr(34) & "1.0" & Chr(34) _ & " encoding=" & Chr(34) & "iso-8859-1" & Chr(34) & "?>" ListView1.Items(i).SubItems(d).Text = _ Replace(ListView1.Items(i).SubItems(d).Text, "Ø", "Ø") txtcliente.Text = Replace(txtcliente.Text, "Ø", "Ø") End Select 'Todos los items del listview tienen reemplazados los caracteres reservados ' y aquellos que XML no entiende 'Procedemos a armar el XML 'Cabecera observerse que para representar las "(comillas) ' utilizamos el codigo ascii Chr(34)" strXml = strXml & "<Pedido Cliente=" & Chr(34) & txtcliente.Text _ & Chr(34) & " Numero=" & Chr(34) _ & txtPedido.Text & Chr(34) & " Fecha=" & Chr(34) & dtpPEdido.Value.Month.ToString & "/" _ & dtpPEdido.Value.Day.ToString & "/" & dtpPEdido.Value.Year.ToString & Chr(34) & ">" 'N detalles For i = 0 To ListView1.Items.Count - 1 strXml = strXml & "<ItemPedido Articulo=" & Chr(34) _ & ListView1.Items(i).SubItems(0).Text & Chr(34) & " Cantidad=" & Chr(34) _ & ListView1.Items(i).SubItems(1).Text & Chr(34) & "/> " items = items + 1 Next ArmarXML = Cabecera & "<ROOT>" _ & strXml _ & " </Pedido></ROOT>" End Function Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnAgregar.Click Dim item As New ListViewItem(txtarticulo.Text, 0) 'Inserto los items en el listview item.SubItems.Add(txtcantidad.Text) ListView1.Items.AddRange(New ListViewItem() {item}) txtcantidad.Clear() txtarticulo.Clear() txtcantidad.Focus() End Sub Private Sub Form1_Activated(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Me.Activated txtcliente.Focus() End Sub Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles MyBase.Load CreateMyListView() InicializarControles() End Sub Private Sub CreateMyListView() ListView1.View = View.Details ListView1.LabelEdit = True ListView1.AllowColumnReorder = True ListView1.CheckBoxes = False ListView1.FullRowSelect = True ListView1.GridLines = True ListView1.Sorting = SortOrder.Ascending ListView1.Scrollable = True ListView1.Columns.Add("Descripcion", 100, HorizontalAlignment.Left) ListView1.Columns.Add("Cantidad", 100, HorizontalAlignment.Left) End Sub Private Sub Button1_Click1(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Button1.Click Dim Conexion As New SqlConnection( _ "user id=sa;Persist Security Info=False;Initial Catalog=nombrebase;" _ & "Data Source=localhost; Connect Timeout=2000; Current Language=ENGLISH") Dim Comando As New SqlCommand Dim oSQLAdapter As New SqlDataAdapter Dim oDS As New DataSet Dim oRow As DataRow Dim iCount As Integer = 0 Dim iPedido As Integer Dim indec As Integer Dim procesepedido As Boolean Dim primer As Boolean Dim armarxml1 As String Try If IsNumeric(txtPedido.Text) Then 'Conexion a la bd Comando.Connection = Conexion If RadioButton1.Checked = True Then armarxml1 = ArmarXML(1) Else If RadioButton2.Checked = True Then armarxml1 = ArmarXML(2) Else If RadioButton3.Checked = True Then armarxml1 = ArmarXML(3) Else If RadioButton4.Checked = True Then armarxml1 = ArmarXML(4) End If End If End If End If 'Insercion de Cabecera Detalle Comando.CommandText = "AltaCabeceraDetalleXML" Comando.CommandType = CommandType.StoredProcedure Comando.Parameters.Add("@XML", SqlDbType.Text) Comando.Parameters("@XML").Value = armarxml1 Conexion.Open() Comando.ExecuteNonQuery() Conexion.Close() 'Carga de treeview TreeView1.Nodes.Clear() TreeView1.BeginUpdate() TreeView1.Nodes.Add("Pedidos") primer = True Comando.CommandType = CommandType.Text Comando.CommandText = _ "SELECT A.IDPEDIDO,A.NROPEDIDO,A.CLIENTE,A.FECHA,B.CANTIDAD" _ & ",B.ARTICULO FROM PEDIDO A JOIN ITEMPEDIDO B ON A.IDPEDIDO=B.IDPEDIDO" oSQLAdapter.SelectCommand = Comando oSQLAdapter.Fill(oDS) indec = 0 For Each oRow In oDS.Tables(0).Rows If primer = True Then iPedido = oDS.Tables(0).Rows(iCount).Item("idpedido") procesepedido = False indec = 0 Else If iPedido = oDS.Tables(0).Rows(iCount).Item("idpedido") Then procesepedido = True Else iPedido = oDS.Tables(0).Rows(iCount).Item("idpedido") indec = indec + 1 procesepedido = False End If End If primer = False If Not procesepedido Then TreeView1.Nodes(0).Nodes.Add( _ oDS.Tables(0).Rows(iCount).Item("nroPedido").ToString.Trim _ + "-" + oDS.Tables(0).Rows(0).Item("fecha").ToString.Trim) TreeView1.Nodes(0).Nodes(indec).Nodes.Add( _ oDS.Tables(0).Rows(iCount).Item("Cliente").ToString) End If 'TreeView1.Nodes(0).Nodes(1).Nodes.Add("Grandchild") TreeView1.Nodes(0).Nodes(indec).Nodes(0).Nodes.Add( _ "Cantidad:" + oDS.Tables(0).Rows(iCount).Item("Cantidad").ToString) TreeView1.Nodes(0).Nodes(indec).Nodes(0).Nodes.Add( _ "Articulo:" + oDS.Tables(0).Rows(iCount).Item("Articulo").ToString) iCount += 1 Next TreeView1.EndUpdate() ListView1.Items.Clear() InicializarControles() Else MsgBox("El pedido debe ser numerico") End If Catch ex As Exception MsgBox("Error . " & ex.Message, _ MsgBoxStyle.Critical + MsgBoxStyle.OkOnly, "Error de base de datos") Exit Sub Finally oDS = Nothing End Try End Sub Conclusiones Este ejemplo es solo la punta del ovillo para realizar las mas complejas transacciones en una solo procedimiento SQL, evitando manejar transacciones por código. El ejemplo esta realizado en VB .NET 2005, en el fichero se encuentra el script
de creación de las tablas y stored procedure. El ejecutable esta dentro
de la carpeta XMLCABDET\bin\Release . |
Código de ejemplo (ZIP): |
Fichero con el código de ejemplo: sebaconte_xml_cabecera_detalle_ejemplo.zip
- (125) KB
|