none
Infracción de la restricción PRIMARY KEY 'PK_xxxx'. No se puede insertar una clave duplicada en el objeto 'dbo.xxxx'. El valor de clave duplicada es (YY). RRS feed

  • Pregunta

  • BD SQL 2008, lenguaje VB 2012

    Estimados amigos: Por favor ayudenme a solucionar este problema al parecer muy sencillo:

    Tengo una tabla con su clave primaria Id, creo un registro (YY ejemplo)  y actualizo la BD mediante UpdateAll del TableAdapterManager, a continuacion elimino el registro YY y vuelvo a crear el registro (YY), vuelvo a actualizar y me da el error inicado. Se que es por el orden (UpdateOrder property) pero en un mantenimiento de una tabla puede presentarse esta operacion y no encuentro como controlarla.

    Gacias de antemano.


    saludos.

    lunes, 29 de octubre de 2012 14:16

Respuestas

  • "Jaime65" escribió:

    > Es exactamente lo que tu describes. me comprendiste muy
    > bien lo que estoy haciendo.

    Pues si es así, no dudes en marcar la respuesta como satisfactoria. ¿OK?

    > Lo ultimo que te expongo es el motivo por el que he buscado controlar
    > este error que se presenta unicamente al reingresar una clave ya
    > borrada sin haber enviado la informacion a la BD.

    Insisto que ese error se produce porque tú estás queriendo que se produzca, ya que lo estás provocando.

    El usuario puede hacer en el control DataGridView (o en su objeto DataTable subyacente), lo que le venga en gana: insertar, eliminar, modificar, volver a eliminar lo que antes insertó, etc. Pero para que surtan efecto en la propia tabla de la base de datos, tienes que enviárselas mediante el método UpdateAll del objeto TableAdapterManager que tengas habilitado al efecto.

    Cuando llegen los datos a la base de datos y hayan algunos que sean incompatibles con los existentes actualmente, es cuando obtendrás la oportuna excepción en tiempo de ejecución, y es misión tuya detectar esa excepción y actuar en consecuencia.

    Para comenzar, te aconsejaría que encerraras la llamada al método UpdateAll entre un bloque Try ... End Try:

      Private Sub PruebaBindingNavigatorSaveItem_Click(sender As Object, e As EventArgs) _
         Handles PruebaBindingNavigatorSaveItem.Click
    
            Try
                Me.Validate()
                Me.Tabla1BindingSource.EndEdit()
                Me.TableAdapterManager.UpdateAll(Me.PruebaDataSet)
    
            Catch ex As SqlException
                ' Errores de SQL Server
                Dim msg As String = _
                    String.Format("Error nº {0}: {1}", ex.Errors(0), ex.Message)
    
                MessageBox.Show(msg)
    
            Catch ex As Exception
                ' Errores generales
                MessageBox.Show(ex.Message)
    
            End Try
    
      End Sub

    Dependiendo del error que se produzca, tendrás que actuar en consecuencia.

    Ahora bien, si a pesar de que se produzca el error, no deseas que éste le aparezca al usuario, lo que tienes que hacer es establecer el valor True a la propiedad ContinueUpdateOnError del objeto DataAdapter que estás utilizando:

        Private Sub Prueba_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    
            Dim da As SqlDataAdapter = Me.PruebaTableAdapter.Adapter
    
            ' Instalamos el controlador para el evento RowUpdating
            ' del objeto DataAdapter.
            '
            AddHandler da.RowUpdating, AddressOf AdapterOnRowUpdating
    
            ' Impedimos que se produzca una excepción al actualizar
            ' el origen de datos.
            '
            da.ContinueUpdateOnError = True
    
            Me.PruebaTableAdapter.Fill(Me.JMG30VBDataSet.prueba)
    
        End Sub

    De ésta manera no se producirá un error, si no que le aparecerá al usuario un control ErrorProvider en la fila del control DataGridView que no se ha actualizado en el origen de datos, indicándole el motivo de ello.

    Como habrás observado, he añadido un controlador para el evento Rowupdating del objeto DataAdapter, el cual se desencadenará para cada fila o registro ANTES de que se ejecute el comando en el origen de datos:

        Private Sub AdapterOnRowUpdating(sender As Object, e As SqlRowUpdatingEventArgs)
    
            Console.WriteLine("OnRowUpdating")
            Console.WriteLine("  event args: (" & " command=" & e.Command.CommandText & _
               " commandType=" & e.StatementType & " status=" & e.Status & ")")
    
        End Sub

    Esto es por si quieres tener un control mayor a la hora de actualizar la base de datos. Échale un vistazo a las propiedades de la clase SqlRowUpdatingEventArgs (el segundo parámetro del método).

    En fin, mira a ver lo que puedes hacer con lo que te he comentado.


    Enrique Martínez
      [MS MVP - VB]

    Nota informativa: La información contenida en este mensaje, así como el código fuente incluido en el mismo, se proporciona «COMO ESTÁ», sin garantías de ninguna clase, y no otorga derecho alguno. Usted asume cualquier riesgo al poner en práctica, utilizar o ejecutar lo recomendado o sugerido en el presente mensaje.

    Si esta respuesta le ha resultado útil, recuerde marcarla como satisfactoria.

    Si usas Visual Basic .NET y deseas ser productivo y feliz, activa la instrucción Option Strict.

    • Marcado como respuesta Jaime65 viernes, 2 de noviembre de 2012 1:04
    jueves, 1 de noviembre de 2012 16:25
    Moderador
  • "Jaime65" escribió:

    > LA PRUEBA: trabajando en el DataGridView creado automaticamente por
    > el diseñador crear un registro y grabarlo (el codigo del evento LOAD
    > y BindingNavigatorSaveItem_Click estan creados automaticamente) ,
    > eliminar el registro grabado y volverlo a crear, en este momento
    > existe un registro en la BD y otro en el BindingSource, el momento
    > de grabar nos da error al infringir la restrigcion de clave unica. 
    >
    > Se puede controlar este error??

    Hola:

    ¡Claro que se puede controlar! Pero más que controlar sería no provocar el error, que indudablemente lo estás provocando, quizás sin darte cuenta de ello. ;-)

    Cuando elimines el registro grabado tienes que actualizar la base de datos, para que el registro se elimine también de la tabla de la base de datos de SQL Server, Access, Oracle, MySQL, o cualquier base de datos que se esté utilizando. Pero si solamente te limitas a eliminarlo del objeto DataTable, y no lo eliminas físicamente de la tabla, es normal que obtengas el error que describes cuando añades un nuevo registro con el mismo valor del campo ID, porque si estamos hablando de un campo que conforma la clave principal de la tabla, éste no admite valores duplicados como bien me imagino conocerás. Y por lo que describes en tu mensaje, eso es exactamente lo que estás haciendo: queriendo insertar un nuevo registro donde el valor de la clave principal ya existe en la tabla. Vuelvo a insistir que quizás lo estés haciendo inconscientemente.

    Si deseas conocer cómo tienes que proceder, reproduce los siguientes pasos:

    1º) Añadir un nuevo registro al control DataGridView, o más bien, al objeto DataTable subyacente que se encuentra enlazado con el control DataGridView.

    2º) Grabar los cambios efectuados, para que surtan efecto en la tabla de la base de datos.

    3º) Eliminar el registro del control DataGridView.

    4º) Grabar los cambios efectuados, para que surtan efecto en la tabla de la base de datos.

    5º) Añadir un nuevo registro al control DataGridView con el valor del que anteriormente habías eliminado.

    6º) Grabar los cambios efectuados, para que surtan efecto en la tabla de la base de datos.

    Por lo que describes en tu mensaje, creo que te has "saltado" el cuarto paso. ;-)

    Ten en cuenta que tal y como estás trabajando, lo estás haciendo de una manera desconectada del origen de datos. Esto quiero decir que todos los cambios que efectúes en el control DataGridView (o repito, en el objeto DataTable subyacente), no surtirán efecto en la tabla de la base de datos hasta que no envíes esas modificaciones a la base de datos.

    Reproduce los pasos que he descrito anteriormente y después me comentas el resultado. ;-)

    Un saludo


    Enrique Martínez
      [MS MVP - VB]

    Nota informativa: La información contenida en este mensaje, así como el código fuente incluido en el mismo, se proporciona «COMO ESTÁ», sin garantías de ninguna clase, y no otorga derecho alguno. Usted asume cualquier riesgo al poner en práctica, utilizar o ejecutar lo recomendado o sugerido en el presente mensaje.

    Si esta respuesta le ha resultado útil, recuerde marcarla como satisfactoria.

    Si usas Visual Basic .NET y deseas ser productivo y feliz, activa la instrucción Option Strict.





    jueves, 1 de noviembre de 2012 7:36
    Moderador
  • "Jaime65" preguntó:

    > Enrique por favor si puedes hacer algun comentario a esta inquietud:
    >

    ¡Bueno! Esto poco tiene que ver con el asunto de tu mensaje inicial. Lo suyo es que inicies una nueva conversación para los nuevos problems, dudas o inquietudes que te surjan, porque si todo lo vas a añadir a la misma conversación, ésta se puede hacer interminable. ¿De acuerdo?

    > es factible hacer un bucle para evitar escribir el codigo 17 veces
    > y ver un codigo mas corto y simplificado??

    Recorrer un bucle va a ser bastante complicado, porque el código que te ha generado el Asistente para la configuración de origenes de datos, no ha creado una especie de colección para recorrer los objetos TableAdapter existentes en el mismo.

    Si es para simplificar y clarificar un poco el código, puedes recurrir a técnicas de reflexión. Pero llegado a éste punto, ya no sé si es más factible copiar/pegar/modificar el código 17 veces, que escribir un procedimiento para referenciar y modificar los objetos mediante Reflection.

    No obstante, si solamente deseas modificar el valor de las propiedades Connection y ContinueUpdateError, y ejecutar el método Fill del objeto TableAdapter (no el método Fill del objeto SqlDataAdapter existente en aquel), para no repetir 17 veces la operación, bien te puede servir el siguiente procedimiento:

    Imports System.Reflection Private Sub ConfigurarAdaptador(obj As Object, dt As DataTable) Try ' Obtenemos el Type del objeto ' Dim ty As Type = obj.GetType() ' Establecemos el valor de la propiedad Connection. ' Dim pi As PropertyInfo = ty.GetProperty("Connection", _ BindingFlags.Instance Or BindingFlags.NonPublic) pi.SetValue(obj, ConexionActual, Nothing) ' Establecemos el valor de la propiedad ContinueUpdateError ' pi = ty.GetProperty("Adapter", _ BindingFlags.Instance Or BindingFlags.NonPublic) ' Referenciamos el adaptador de datos '

    Dim value As Object = pi.GetValue(obj, Nothing)
    Dim adapter As SqlDataAdapter = DirectCast(value, SqlDataAdapter)
    adapter.ContinueUpdateOnError = True ' Referenciamos el método Fill del objeto TableAdapter pasado. ' Dim mi As MethodInfo = ty.GetMethod("Fill") ' Definimos el valor del único parámetro que espera el método. ' Dim param() As Object = {dt} ' Ejecutamos el método ' mi.Invoke(obj, param) Catch ' Cualquier excepción que se haya producido ' la devolvemos al procedimiento llamador. ' Throw End Try End Sub

    Por supuesto, la veriable objeto llamada ConexionActual deberá estar declarada a nivel del formulario o clase donde insertes el procedimiento ConfigurarAdaptador.

    Asimismo, he supuesto que estás trabajando con los objetos del espacio de nombres System.Data.SqlClient, es decir, con un origen de datos de SQL Server.

    Ahora, en el evento Load del formulario llamarías al procedimiento pasándole cada objeto TableAdapter que desees configurar:

            Try
                ' Para la Tabla Padre 
                ConfigurarAdaptador(Me.PadreTableAdapter, Me.JMGDataSet.Padre)
    
                ' Para la Tabla Hija1 
                ConfigurarAdaptador(Me.Hija1TableAdapter, Me.JMGDataSet.Hija1)
    
            Catch ex As Exception
                ' Se ha producido un error en el método ConfigurarAdaptador.
                '
                MessageBox.Show(ex.Message)
    
            End Try

    Como podrás observar, ahora sería cuestión de escribir solamente 17 líneas, que serían las 17 llamadas al procedimiento ConfigurarAdaptador.

    Por ahora, no esperes "más milagros". ;-)

    Y lo dicho, para nuevos problemas, dudas o inquietudes que no tengan nada que ver con el asunto de tu mensaje inicial, inicia una nueva conversación.


    Enrique Martínez
      [MS MVP - VB]

    Nota informativa: La información contenida en este mensaje, así como el código fuente incluido en el mismo, se proporciona «COMO ESTÁ», sin garantías de ninguna clase, y no otorga derecho alguno. Usted asume cualquier riesgo al poner en práctica, utilizar o ejecutar lo recomendado o sugerido en el presente mensaje.

    Si esta respuesta le ha resultado útil, recuerde marcarla como satisfactoria.

    Si usas Visual Basic .NET y deseas ser productivo y feliz, activa la instrucción Option Strict.

    viernes, 2 de noviembre de 2012 18:14
    Moderador
  • Enrique excelente, por ahi encontre algo al respecto y lo apliqué. ojala sirva a otras personas:

    Public Class Prueba
        Private Sub PadreBindingNavigatorSaveItem_Click(sender As Object, e As EventArgs) Handles PadreBindingNavigatorSaveItem.Click
            Me.Validate()
            Me.PadreBindingSource.EndEdit()
            UpdateDB()
            '    Me.TableAdapterManager.UpdateAll(Me.JMG30VBDataSet)
        End Sub
        Private Sub Prueba_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            '   Me.PadreTableAdapter.Adapter.ContinueUpdateOnError = True
            Me.PadreTableAdapter.Fill(Me.JMG30VBDataSet.Padre)
            Me.HijaTableAdapter.Adapter.ContinueUpdateOnError = True
            Me.HijaTableAdapter.Fill(Me.JMG30VBDataSet.Hija)
        End Sub
        Private Sub UpdateDB()
            Dim deletedChildRecords As JMG30VBDataSet.HijaDataTable =
                CType(JMG30VBDataSet.Hija.GetChanges(Data.DataRowState.Deleted), JMG30VBDataSet.HijaDataTable)
            Dim newChildRecords As JMG30VBDataSet.HijaDataTable =
                CType(JMG30VBDataSet.Hija.GetChanges(Data.DataRowState.Added), JMG30VBDataSet.HijaDataTable)
            Dim modifiedChildRecords As JMG30VBDataSet.HijaDataTable =
                CType(JMG30VBDataSet.Hija.GetChanges(Data.DataRowState.Modified), JMG30VBDataSet.HijaDataTable)
            Try
                If deletedChildRecords IsNot Nothing Then
                    HijaTableAdapter.Update(deletedChildRecords)
                End If
                PadreTableAdapter.Update(JMG30VBDataSet.Padre)
                If newChildRecords IsNot Nothing Then
                    HijaTableAdapter.Update(newChildRecords)
                End If
                If modifiedChildRecords IsNot Nothing Then
                    HijaTableAdapter.Update(modifiedChildRecords)
                End If
                JMG30VBDataSet.AcceptChanges()
            Catch ex As Exception
                MessageBox.Show(ex.ToString)
            Finally
                If deletedChildRecords IsNot Nothing Then
                    deletedChildRecords.Dispose()
                End If
                If newChildRecords IsNot Nothing Then
                    newChildRecords.Dispose()
                End If
                If modifiedChildRecords IsNot Nothing Then
                    modifiedChildRecords.Dispose()
                End If
            End Try
        End Sub
    End Class

    Esta es una manera de crear un orden de actualizacion DeleteInsertUpdate que por ahora me funciona para 2 tablas relacionadas Padre e Hija, voy a mi necesidad que es Padre y 17 Hijas a la vez. Enrique gracias por tu ayuda, eres Maestro.

    Saludos cordiales,

    Jaime.


    saludos.

    • Marcado como respuesta Jaime65 viernes, 2 de noviembre de 2012 0:41
    viernes, 2 de noviembre de 2012 0:41

Todas las respuestas

  • Hola:

    Según entiendo lo que tienes que intentar hacer es ver si existe el registro en la base de datos. Tendrás que ponerle alguna restricción que te indique que si no existe lo grabe y si existe que indique que ya existe.

    Prueba esto a ver si te lo pasa bien, te he puesto la entrada en un inputbox. Si no te gusta la puedes adpatar a tus necesidades. Me imagino que debes tener un datagridview donde ves las columnas y tus códigos de cliente por ejemplo, si es así, te tiene que funcionar.

    Private Sub btnBUSCAR_Click(sender As System.Object, e As System.EventArgs) Handles btnBUSCAR.Click
            Dim Criterio As String
            Criterio = InputBox("¿Qué código de cliente quiere buscar?")
    
            If Criterio = "" Then Return
           
            DataGridView1.CurrentCell = Nothing
    
            For Each i As DataGridViewRow In DataGridView1.Rows
                If Convert.ToString(i.Cells("Código").Value).IndexOf(Criterio, StringComparison.CurrentCultureIgnoreCase) > -1 Then
                    DataGridView1.CurrentCell = i.Cells("Código")
                    MessageBox.Show("El código de cliente " & CStr(i.Cells("Código").Value) & " ya existe.", "BÚSQUEDA DE CLIENTES", MessageBoxButtons.OK, MessageBoxIcon.Information)
                    Exit For
                End If
            Next
    
            If DataGridView1.CurrentCell Is Nothing Then
                MessageBox.Show("No se encuenta el código de cliente.", "BÚSQUEDA DE EMPRESAS", MessageBoxButtons.OK, MessageBoxIcon.Information)
            End If
    
        End Sub

    Un cordial saludo.

    Gemma

    lunes, 29 de octubre de 2012 14:47
  • Gemma gracias por tu respuesta.

    No es el caso, el registro no existe porque lo borro pero permanece hasta que no realice la actualizacion con el UpdateAll del TableAdapterManager y por ese motivo me permite volverlo a ingresar siendo el campo una Clave Primaria. El problema se presenta al actualizar nuevamente. Hay 2 registro iguales: el borrado que no ha sido eliminado aun por  UpdateOrder y el nuevo qye sera el primero en insertarse por el mismo UpdateOrder.

    Ojala se me entiendan el caso.

    saludos.

    Jaime.


    saludos.

    lunes, 29 de octubre de 2012 15:24
  • Hola:

    No hemos visto tu código, pero en principio no entiendo por qué repites y actualizas antes de borrar definitivamente el registro. Como primer paso haría el borrado, la actualización y posteriormente ya volverás a poner lo que quieras, es decir, los pasos serían: Borrar, actualizar, insertar. Yo lo haría así, quizás algún miembro del foro te de alguna idea mejor.

    Un saludo.

    Gemmna.

    lunes, 29 de octubre de 2012 16:04
  • Gracias Gemma.

    mi codigo es extenso y no refleja el problemas por eso lo resumi. Mira en SQL te creas una tablas con un campo Id y le asignas Clave primaria obiamente no acepta NULL y es unica. En VB creas un DataSet que incluye esta tabla. Se crear un formulario y se arrastra la tabla al formulario y listo, puedes comprobar el problema que te expongo.  El unico codigo es el que te da el generador:

    Public Class Prueba
        Private Sub PruebaBindingNavigatorSaveItem_Click(sender As Object, e As EventArgs) Handles PruebaBindingNavigatorSaveItem.Click
            Me.Validate()
            Me.PruebaBindingSource.EndEdit()
            Me.TableAdapterManager.UpdateAll(Me.JMG30VBDataSet)
        End Sub
        Private Sub Prueba_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            
            Me.PruebaTableAdapter.Fill(Me.JMG30VBDataSet.prueba)
        End Sub
    End Class

    En el DataGridView creado al arrastrar la tabla al formulario puedes ingresar un registro y grabarlo, luego lo borras y vuelves a ingresar el mismo registro y al grabar te da el error.

    Cuando un usuario de mi sistema esta haciendo altas y bajas en una tabla se supone que puede borra o ingresar cuantas veces lo requiera el mismo dato, esto lo puedes probar en el ejercicio que te propongo. Si uso el TableAdapter.Update(Dataset) de la tabla directamente no me da error ya que la actualizacion no guarda el orden InsertUpdateDelete del TableAdapterManager, Se puede correjir esto?

    Saludos cordiales,

    Jaime.


    saludos.

    lunes, 29 de octubre de 2012 16:59
  • Hola Jaime:

    Vamos a esperar que alguien con mas conocimientos que yo te responda la pregunta, porque yo nunca he usado el explorador de servidores, lo tengo todo controlado por código por el seguimiento que le puedo hacer.

    Siento no poderte ayudar más, pero antes de meter la pata, prefiero que otra persona con más conocimiento te indique la solución.

    Un cordial saludo.

    Gemma.

    lunes, 29 de octubre de 2012 18:03
  • Gemma gracias por tu respuesta.

    No es el caso, el registro no existe porque lo borro pero permanece hasta que no realice la actualizacion con el UpdateAll del TableAdapterManager y por ese motivo me permite volverlo a ingresar siendo el campo una Clave Primaria. El problema se presenta al actualizar nuevamente. Hay 2 registro iguales: el borrado que no ha sido eliminado aun por  UpdateOrder y el nuevo qye sera el primero en insertarse por el mismo UpdateOrder.

    Ojala se me entiendan el caso.

    saludos.

    Jaime.


    saludos.


    esque en tu tabla deves de tener activado la identity que significa que no puedes insertar una clave que ya existia, deves insertarla con un id nuevo como si fuera un registro nuevo.
    lunes, 29 de octubre de 2012 22:50
  • Gracias DanDan Glitch. Entendi tu idea, estoy usando mi clave principal como dato que el usuario ingresa, voy a cambiar la estructura del sistema para usar clave con identity.

    muchas gracias.

    saludos.


    saludos.

    martes, 30 de octubre de 2012 2:43
  • Amigos buen dia.

    No encuentro la manera de solucionar este inconveniente. Lo pueden probar creando una tabla en SQL que tenga un campo ID y hacerlo Clave Principal.  Desde el asistente en VB 2012 se crea el DataSet con la Tabla creada anteriormente. En un Formulario se arrastra la Tabla desde el Dataset y el Diseñador crea automaticamente los componentes para esta Tabla en nuestro formulario.

    LA PRUEBA: trabajando en el DataGridView creado automaticamente por el diseñador crear un registro y grabarlo (el codigo del evento LOAD y BindingNavigatorSaveItem_Click estan creados automaticamente) , eliminar el registro grabado y volverlo a crear, en este momento existe un registro en la BD y otro en el BindingSource, el momento de grabar nos da error al infringir la restrigcion de clave unica.  

    Se puede controlar este error??

    Saludos

    Jaime.


    saludos.

    jueves, 1 de noviembre de 2012 0:29
  • "Jaime65" escribió:

    > LA PRUEBA: trabajando en el DataGridView creado automaticamente por
    > el diseñador crear un registro y grabarlo (el codigo del evento LOAD
    > y BindingNavigatorSaveItem_Click estan creados automaticamente) ,
    > eliminar el registro grabado y volverlo a crear, en este momento
    > existe un registro en la BD y otro en el BindingSource, el momento
    > de grabar nos da error al infringir la restrigcion de clave unica. 
    >
    > Se puede controlar este error??

    Hola:

    ¡Claro que se puede controlar! Pero más que controlar sería no provocar el error, que indudablemente lo estás provocando, quizás sin darte cuenta de ello. ;-)

    Cuando elimines el registro grabado tienes que actualizar la base de datos, para que el registro se elimine también de la tabla de la base de datos de SQL Server, Access, Oracle, MySQL, o cualquier base de datos que se esté utilizando. Pero si solamente te limitas a eliminarlo del objeto DataTable, y no lo eliminas físicamente de la tabla, es normal que obtengas el error que describes cuando añades un nuevo registro con el mismo valor del campo ID, porque si estamos hablando de un campo que conforma la clave principal de la tabla, éste no admite valores duplicados como bien me imagino conocerás. Y por lo que describes en tu mensaje, eso es exactamente lo que estás haciendo: queriendo insertar un nuevo registro donde el valor de la clave principal ya existe en la tabla. Vuelvo a insistir que quizás lo estés haciendo inconscientemente.

    Si deseas conocer cómo tienes que proceder, reproduce los siguientes pasos:

    1º) Añadir un nuevo registro al control DataGridView, o más bien, al objeto DataTable subyacente que se encuentra enlazado con el control DataGridView.

    2º) Grabar los cambios efectuados, para que surtan efecto en la tabla de la base de datos.

    3º) Eliminar el registro del control DataGridView.

    4º) Grabar los cambios efectuados, para que surtan efecto en la tabla de la base de datos.

    5º) Añadir un nuevo registro al control DataGridView con el valor del que anteriormente habías eliminado.

    6º) Grabar los cambios efectuados, para que surtan efecto en la tabla de la base de datos.

    Por lo que describes en tu mensaje, creo que te has "saltado" el cuarto paso. ;-)

    Ten en cuenta que tal y como estás trabajando, lo estás haciendo de una manera desconectada del origen de datos. Esto quiero decir que todos los cambios que efectúes en el control DataGridView (o repito, en el objeto DataTable subyacente), no surtirán efecto en la tabla de la base de datos hasta que no envíes esas modificaciones a la base de datos.

    Reproduce los pasos que he descrito anteriormente y después me comentas el resultado. ;-)

    Un saludo


    Enrique Martínez
      [MS MVP - VB]

    Nota informativa: La información contenida en este mensaje, así como el código fuente incluido en el mismo, se proporciona «COMO ESTÁ», sin garantías de ninguna clase, y no otorga derecho alguno. Usted asume cualquier riesgo al poner en práctica, utilizar o ejecutar lo recomendado o sugerido en el presente mensaje.

    Si esta respuesta le ha resultado útil, recuerde marcarla como satisfactoria.

    Si usas Visual Basic .NET y deseas ser productivo y feliz, activa la instrucción Option Strict.





    jueves, 1 de noviembre de 2012 7:36
    Moderador
  • Enrique buenos dias.

    Es exactamente lo que tu describes. me comprendiste muy bien lo que estoy haciendo. Te comento lo que puede suceder: El usuario de mi sistema entra en el formulario para hacer consultas, altas y bajas en una tabla principal que esta relacionada con 17 tablas secundarias por lo que el TableAdapterManager me ayuda muchisimo realmente me simplifica la vida. El usuario puede realizar las operaciones necesarias para dejar la informacion como el la necesite, esto implica cambiar, ingresar y borrar registros y algunas veces volver a ingresar un registro ya borrado. En vista que esta trabajando desconectado del origen de datos estos solo se actualizan el momento que envio la informacion a la BD. No me gustaria actualizar cada transaccion porque esto quitaria algo de personalidad al sistema ya que si el usuario ingreso o elimino informacion por error esta la puede recuperar al estado de su ultima grabacion en la BD. Lo ultimo que te expongo es el motivo por el que he buscado controlar este error que se presenta unicamente al reingresar una clave ya borrada sin haber enviado la informacion a la BD.

    Cualquier sugerencia por favor hazmelo saber.

    Saludos cordiales,

    Jaime.


    saludos.

    jueves, 1 de noviembre de 2012 13:37
  • "Jaime65" escribió:

    > Es exactamente lo que tu describes. me comprendiste muy
    > bien lo que estoy haciendo.

    Pues si es así, no dudes en marcar la respuesta como satisfactoria. ¿OK?

    > Lo ultimo que te expongo es el motivo por el que he buscado controlar
    > este error que se presenta unicamente al reingresar una clave ya
    > borrada sin haber enviado la informacion a la BD.

    Insisto que ese error se produce porque tú estás queriendo que se produzca, ya que lo estás provocando.

    El usuario puede hacer en el control DataGridView (o en su objeto DataTable subyacente), lo que le venga en gana: insertar, eliminar, modificar, volver a eliminar lo que antes insertó, etc. Pero para que surtan efecto en la propia tabla de la base de datos, tienes que enviárselas mediante el método UpdateAll del objeto TableAdapterManager que tengas habilitado al efecto.

    Cuando llegen los datos a la base de datos y hayan algunos que sean incompatibles con los existentes actualmente, es cuando obtendrás la oportuna excepción en tiempo de ejecución, y es misión tuya detectar esa excepción y actuar en consecuencia.

    Para comenzar, te aconsejaría que encerraras la llamada al método UpdateAll entre un bloque Try ... End Try:

      Private Sub PruebaBindingNavigatorSaveItem_Click(sender As Object, e As EventArgs) _
         Handles PruebaBindingNavigatorSaveItem.Click
    
            Try
                Me.Validate()
                Me.Tabla1BindingSource.EndEdit()
                Me.TableAdapterManager.UpdateAll(Me.PruebaDataSet)
    
            Catch ex As SqlException
                ' Errores de SQL Server
                Dim msg As String = _
                    String.Format("Error nº {0}: {1}", ex.Errors(0), ex.Message)
    
                MessageBox.Show(msg)
    
            Catch ex As Exception
                ' Errores generales
                MessageBox.Show(ex.Message)
    
            End Try
    
      End Sub

    Dependiendo del error que se produzca, tendrás que actuar en consecuencia.

    Ahora bien, si a pesar de que se produzca el error, no deseas que éste le aparezca al usuario, lo que tienes que hacer es establecer el valor True a la propiedad ContinueUpdateOnError del objeto DataAdapter que estás utilizando:

        Private Sub Prueba_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    
            Dim da As SqlDataAdapter = Me.PruebaTableAdapter.Adapter
    
            ' Instalamos el controlador para el evento RowUpdating
            ' del objeto DataAdapter.
            '
            AddHandler da.RowUpdating, AddressOf AdapterOnRowUpdating
    
            ' Impedimos que se produzca una excepción al actualizar
            ' el origen de datos.
            '
            da.ContinueUpdateOnError = True
    
            Me.PruebaTableAdapter.Fill(Me.JMG30VBDataSet.prueba)
    
        End Sub

    De ésta manera no se producirá un error, si no que le aparecerá al usuario un control ErrorProvider en la fila del control DataGridView que no se ha actualizado en el origen de datos, indicándole el motivo de ello.

    Como habrás observado, he añadido un controlador para el evento Rowupdating del objeto DataAdapter, el cual se desencadenará para cada fila o registro ANTES de que se ejecute el comando en el origen de datos:

        Private Sub AdapterOnRowUpdating(sender As Object, e As SqlRowUpdatingEventArgs)
    
            Console.WriteLine("OnRowUpdating")
            Console.WriteLine("  event args: (" & " command=" & e.Command.CommandText & _
               " commandType=" & e.StatementType & " status=" & e.Status & ")")
    
        End Sub

    Esto es por si quieres tener un control mayor a la hora de actualizar la base de datos. Échale un vistazo a las propiedades de la clase SqlRowUpdatingEventArgs (el segundo parámetro del método).

    En fin, mira a ver lo que puedes hacer con lo que te he comentado.


    Enrique Martínez
      [MS MVP - VB]

    Nota informativa: La información contenida en este mensaje, así como el código fuente incluido en el mismo, se proporciona «COMO ESTÁ», sin garantías de ninguna clase, y no otorga derecho alguno. Usted asume cualquier riesgo al poner en práctica, utilizar o ejecutar lo recomendado o sugerido en el presente mensaje.

    Si esta respuesta le ha resultado útil, recuerde marcarla como satisfactoria.

    Si usas Visual Basic .NET y deseas ser productivo y feliz, activa la instrucción Option Strict.

    • Marcado como respuesta Jaime65 viernes, 2 de noviembre de 2012 1:04
    jueves, 1 de noviembre de 2012 16:25
    Moderador
  • Enrique excelente, por ahi encontre algo al respecto y lo apliqué. ojala sirva a otras personas:

    Public Class Prueba
        Private Sub PadreBindingNavigatorSaveItem_Click(sender As Object, e As EventArgs) Handles PadreBindingNavigatorSaveItem.Click
            Me.Validate()
            Me.PadreBindingSource.EndEdit()
            UpdateDB()
            '    Me.TableAdapterManager.UpdateAll(Me.JMG30VBDataSet)
        End Sub
        Private Sub Prueba_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            '   Me.PadreTableAdapter.Adapter.ContinueUpdateOnError = True
            Me.PadreTableAdapter.Fill(Me.JMG30VBDataSet.Padre)
            Me.HijaTableAdapter.Adapter.ContinueUpdateOnError = True
            Me.HijaTableAdapter.Fill(Me.JMG30VBDataSet.Hija)
        End Sub
        Private Sub UpdateDB()
            Dim deletedChildRecords As JMG30VBDataSet.HijaDataTable =
                CType(JMG30VBDataSet.Hija.GetChanges(Data.DataRowState.Deleted), JMG30VBDataSet.HijaDataTable)
            Dim newChildRecords As JMG30VBDataSet.HijaDataTable =
                CType(JMG30VBDataSet.Hija.GetChanges(Data.DataRowState.Added), JMG30VBDataSet.HijaDataTable)
            Dim modifiedChildRecords As JMG30VBDataSet.HijaDataTable =
                CType(JMG30VBDataSet.Hija.GetChanges(Data.DataRowState.Modified), JMG30VBDataSet.HijaDataTable)
            Try
                If deletedChildRecords IsNot Nothing Then
                    HijaTableAdapter.Update(deletedChildRecords)
                End If
                PadreTableAdapter.Update(JMG30VBDataSet.Padre)
                If newChildRecords IsNot Nothing Then
                    HijaTableAdapter.Update(newChildRecords)
                End If
                If modifiedChildRecords IsNot Nothing Then
                    HijaTableAdapter.Update(modifiedChildRecords)
                End If
                JMG30VBDataSet.AcceptChanges()
            Catch ex As Exception
                MessageBox.Show(ex.ToString)
            Finally
                If deletedChildRecords IsNot Nothing Then
                    deletedChildRecords.Dispose()
                End If
                If newChildRecords IsNot Nothing Then
                    newChildRecords.Dispose()
                End If
                If modifiedChildRecords IsNot Nothing Then
                    modifiedChildRecords.Dispose()
                End If
            End Try
        End Sub
    End Class

    Esta es una manera de crear un orden de actualizacion DeleteInsertUpdate que por ahora me funciona para 2 tablas relacionadas Padre e Hija, voy a mi necesidad que es Padre y 17 Hijas a la vez. Enrique gracias por tu ayuda, eres Maestro.

    Saludos cordiales,

    Jaime.


    saludos.

    • Marcado como respuesta Jaime65 viernes, 2 de noviembre de 2012 0:41
    viernes, 2 de noviembre de 2012 0:41
  • Enrique por favor si puedes hacer algun comentario a esta inquietud:

    En el evento LOAD del formulario tengo entre otras cosas esto:(ejemplo)

        '' Para la Tabla Padre
        Me.PadreTableAdapter.Connection = ConexionActual
        Me.PadreTableAdapter.Adapter.ContinueUpdateError = True
        Me.PadreTableAdapter.Fill(Me.JMGDataSet.Padre)

        '' Para la Tabla Hija1
        Me.Hija1TableAdapter.Connection = ConexionActual
        Me.Hija1TableAdapter.Adapter.ContinueUpdateError = True
        Me.Hija1TableAdapter.Fill(Me.JMGDataSet.Hija1)

    y asi se repite para 17 tablas mas. Si ves en cada bloque se repite el nombre de la tabla para cada comando, es factible hacer un bucle para evitar escribir el codigo 17 veces y ver un codigo mas corto y simplificado??

    Saludos cordiales,
    Jaime.

     


    saludos.

    viernes, 2 de noviembre de 2012 9:14
  • "Jaime65" preguntó:

    > Enrique por favor si puedes hacer algun comentario a esta inquietud:
    >

    ¡Bueno! Esto poco tiene que ver con el asunto de tu mensaje inicial. Lo suyo es que inicies una nueva conversación para los nuevos problems, dudas o inquietudes que te surjan, porque si todo lo vas a añadir a la misma conversación, ésta se puede hacer interminable. ¿De acuerdo?

    > es factible hacer un bucle para evitar escribir el codigo 17 veces
    > y ver un codigo mas corto y simplificado??

    Recorrer un bucle va a ser bastante complicado, porque el código que te ha generado el Asistente para la configuración de origenes de datos, no ha creado una especie de colección para recorrer los objetos TableAdapter existentes en el mismo.

    Si es para simplificar y clarificar un poco el código, puedes recurrir a técnicas de reflexión. Pero llegado a éste punto, ya no sé si es más factible copiar/pegar/modificar el código 17 veces, que escribir un procedimiento para referenciar y modificar los objetos mediante Reflection.

    No obstante, si solamente deseas modificar el valor de las propiedades Connection y ContinueUpdateError, y ejecutar el método Fill del objeto TableAdapter (no el método Fill del objeto SqlDataAdapter existente en aquel), para no repetir 17 veces la operación, bien te puede servir el siguiente procedimiento:

    Imports System.Reflection Private Sub ConfigurarAdaptador(obj As Object, dt As DataTable) Try ' Obtenemos el Type del objeto ' Dim ty As Type = obj.GetType() ' Establecemos el valor de la propiedad Connection. ' Dim pi As PropertyInfo = ty.GetProperty("Connection", _ BindingFlags.Instance Or BindingFlags.NonPublic) pi.SetValue(obj, ConexionActual, Nothing) ' Establecemos el valor de la propiedad ContinueUpdateError ' pi = ty.GetProperty("Adapter", _ BindingFlags.Instance Or BindingFlags.NonPublic) ' Referenciamos el adaptador de datos '

    Dim value As Object = pi.GetValue(obj, Nothing)
    Dim adapter As SqlDataAdapter = DirectCast(value, SqlDataAdapter)
    adapter.ContinueUpdateOnError = True ' Referenciamos el método Fill del objeto TableAdapter pasado. ' Dim mi As MethodInfo = ty.GetMethod("Fill") ' Definimos el valor del único parámetro que espera el método. ' Dim param() As Object = {dt} ' Ejecutamos el método ' mi.Invoke(obj, param) Catch ' Cualquier excepción que se haya producido ' la devolvemos al procedimiento llamador. ' Throw End Try End Sub

    Por supuesto, la veriable objeto llamada ConexionActual deberá estar declarada a nivel del formulario o clase donde insertes el procedimiento ConfigurarAdaptador.

    Asimismo, he supuesto que estás trabajando con los objetos del espacio de nombres System.Data.SqlClient, es decir, con un origen de datos de SQL Server.

    Ahora, en el evento Load del formulario llamarías al procedimiento pasándole cada objeto TableAdapter que desees configurar:

            Try
                ' Para la Tabla Padre 
                ConfigurarAdaptador(Me.PadreTableAdapter, Me.JMGDataSet.Padre)
    
                ' Para la Tabla Hija1 
                ConfigurarAdaptador(Me.Hija1TableAdapter, Me.JMGDataSet.Hija1)
    
            Catch ex As Exception
                ' Se ha producido un error en el método ConfigurarAdaptador.
                '
                MessageBox.Show(ex.Message)
    
            End Try

    Como podrás observar, ahora sería cuestión de escribir solamente 17 líneas, que serían las 17 llamadas al procedimiento ConfigurarAdaptador.

    Por ahora, no esperes "más milagros". ;-)

    Y lo dicho, para nuevos problemas, dudas o inquietudes que no tengan nada que ver con el asunto de tu mensaje inicial, inicia una nueva conversación.


    Enrique Martínez
      [MS MVP - VB]

    Nota informativa: La información contenida en este mensaje, así como el código fuente incluido en el mismo, se proporciona «COMO ESTÁ», sin garantías de ninguna clase, y no otorga derecho alguno. Usted asume cualquier riesgo al poner en práctica, utilizar o ejecutar lo recomendado o sugerido en el presente mensaje.

    Si esta respuesta le ha resultado útil, recuerde marcarla como satisfactoria.

    Si usas Visual Basic .NET y deseas ser productivo y feliz, activa la instrucción Option Strict.

    viernes, 2 de noviembre de 2012 18:14
    Moderador
  • Enrique, exelente. Lo adapte y funciona bien pero no voy a ocupar esta funcion porque me quedo con el copy paste de las 17 tablas. :)

    Dim Ta() As SqlDataAdapter Ta(0) = PadreTableAdapter Ta(1) = Hija1TableAdapter ' y asi todas la hijas For i = 0 To Ta.Count Ta(i).connection = ConexionActual ta(i).Adapter.ContinueUpdateOnError = True Next()

    algo asi era mi idea pero me imagino que asi no funciona. Solo lo pongo como para ilustrarte mi pensamiento

    Gracias por tu ayuda, ojala estemos mas tiempo en contacto.

    Saludos cordiales,

    Jaime.



    saludos.

    sábado, 3 de noviembre de 2012 16:07
  • "Jaime65" escribió:

    > Enrique, exelente. Lo adapte y funciona bien ...

    ¿Esperabas que NO funcionase? Si no funciona yo soy el primero que no publico el código. :-D

    > ... pero no voy a ocupar esta funcion porque me quedo
    > con el copy paste de las 17 tablas. :)

    Pues ¡tú mismo! Pero ya que deseabas simplificar el código y tienes un procedimiento que hace perfectamente su trabajo, ¿qué malo tiene llamar al procedimiento ConfigurarAdaptador por cada objeto TableAdapter que desees configurar?

    >
    > For i = 0 To Ta.Count
    >   Ta(i).connection = ConexionActual
    >   ta(i).Adapter.ContinueUpdateOnError = True
    > Next()
    >
    > algo asi era mi idea pero me imagino que asi no funciona.

    Si tu idea la entendí perfectamente, pero como te indiqué, no existe ningún tipo de colección para conocer los tipos de objetos TableAdapter que ha creado el Asistente para la configuración de origenes de datos.

    Como mucho podrás conocer el número de objetos TableAdapter que actualmente están instanciados, es decir, que su valor no es Nothing, consultando el valor de la propiedad TableAdapterInstanceCount del objeto TableAdapterManager:

      For i = 0 To TableAdapterManager.TableAdapterInstanceCount -1
    
      Next
    

    Pero ahora, ¿cómo accedes a los elementos de una colección inexistente? ;-)


    Enrique Martínez
      [MS MVP - VB]

    Nota informativa: La información contenida en este mensaje, así como el código fuente incluido en el mismo, se proporciona «COMO ESTÁ», sin garantías de ninguna clase, y no otorga derecho alguno. Usted asume cualquier riesgo al poner en práctica, utilizar o ejecutar lo recomendado o sugerido en el presente mensaje.

    Si esta respuesta le ha resultado útil, recuerde marcarla como satisfactoria.

    Si usas Visual Basic .NET y deseas ser productivo y feliz, activa la instrucción Option Strict.

    sábado, 3 de noviembre de 2012 16:30
    Moderador
  • Enrique, gracias tus comentarios, como siempre muy valiosos. Estopy creando un sistema de control para mi Empresa que me parece muy interesante, algun rato te lo envio.

    Concluimos el hilo.

    Saludos cordiales,

    Jaime.


    saludos.

    sábado, 3 de noviembre de 2012 18:33