none
Error en grabación de datos en sql server express

    Pregunta

  • Hola a todos:

    Tengo un problema de 2 caras, os explico. La sentencia siguiente funciona perfectamente en Access y SqlCompact, el problema está cuando quiero grabar los datos del parámetro en sql server, me indica que no se puede convertir el valor nvarchar en numérico. Hasta ahí no habría problema, le pondría un Cdec antes del valor y solucionado, pero que es lo que pasa, pues si lo pongo así cuando trabajo con Access el valor me lo graba mal y ya no me sirve ya que también en un datagridview lo muestra mal lógicamente.

    Por ejemplo el valor que lleva la propiedad es: 0,67545 y en access tal como está lo mostraría correctamente y en sqlCompact también, ahora si pongo .Add(Configuracion.CreateParameter(cmd, String.Format("@ejer{0}", i), Cdec(UmbralRentabilidad(i).ToString()))), el valor en Access me lo pone como 67545 y eso es lo que me muestra en a grilla.

    Y ahí está el problema que la grabación de datos parece ser diferente en Access y en Sql Server.

    Os pongo el código entero.

     Public Shared Sub Formula_UmbralRentabilidad()
            Try
               Dim valor As Integer = AccesoLogica.ObtenerPeriodos()
    
                'Cálculo del Umbral de Rentabilidad
                For i = 0 To valor
                    If DenominadorUmbralRentab(i) = 0 Then
                        UmbralRentabilidad(i) = 0
                    Else
    
                        UmbralRentabilidad(i) = Decimal.Round(CostesFijos(i) / DenominadorUmbralRentab(i), 2)
                    End If
                Next i
    
                ' Declaramos una variable Connection
                Using cnn As DbConnection = da.CreateConnection()
    
                    ' Creamos el Commando
                    Dim cmd As DbCommand = cnn.CreateCommand
    
                    cmd.CommandText = "UPDATE Varios SET Ejer_01 = @ejer0, Ejer_02 = @ejer1, Ejer_03 = @ejer2, Ejer_04 = @ejer3, Ejer_05 = @ejer4, Ejer_06 = @ejer5, Ejer_07 = @ejer6, Ejer_08 = @ejer7, " &
                                  "Ejer_09 = @ejer8, Ejer_10 = @ejer9, Ejer_11 = @ejer10, Ejer_12 = @ejer11 " &
                                  "WHERE Cod_Empresa = @empresa AND Orden = '445'"
    
    
                  For i = 0 To 11
                        .Add(Configuracion.CreateParameter(cmd, String.Format("@ejer{0}", i), UmbralRentabilidad(i).ToString()))
                  Next
                        .Add(Configuracion.CreateParameter(cmd, "@empresa", VarGlobal.StrCodEmpresa))
                    End With
                    MsgBox("Valor umbral: " & UmbralRentabilidad(0))
                    ' Asignamos la conexión al comando
                    cmd.Connection = cnn
                    cnn.Open()
                    cmd.ExecuteNonQuery()
                End Using
    
            Catch ex As Exception
                MessageBox.Show("Bloqueo en carga de Fórmula: '3230'" & vbCrLf & ex.Message & vbCrLf &
                   "Acepte para continuar.", "PROGRAMA", MessageBoxButtons.OK, MessageBoxIcon.Error)
            End Try
        End Sub

    Esta hecha para Access y SqlCompact y va perfecta, es en SQL Server que no funciona, por ello, desearía saber que formato puedo aplicar para que funcione en las 3 bases de datos o que sea común a ellas.

    Un cordial saludo.

    Gemma


    lunes, 10 de octubre de 2016 15:48

Respuestas

  • "gemma_campillo" escribió:

    > Por ejemplo el valor que lleva la propiedad es: 0,67545 y en access tal como
    > está lo mostraría correctamente y en sqlCompact también, ahora si pongo
    > .Add(Configuracion.CreateParameter(cmd, String.Format("@ejer{0}", i),
    > Cdec(UmbralRentabilidad(i).ToString()))), el valor en Access me lo pone como
    > 67545 y eso es lo que me muestra en a grilla.
    >
    > Incluso estoy probando una función pero en access sigue poniéndome mal los datos.
    >
    >  Public Shared Function ConversorDatos(valor As Decimal, decimales As Integer) As Decimal
    >        Dim resultadoSqlExpress As Decimal
    >        Dim resultadoAccess As Decimal
    >
    >        If Configuracion.strNombreBaseDeDatos = "PerseoSqlEx" Then    'Sql Express
    >            resultadoSqlExpress = Decimal.Round(valor, decimales)
    >            Return Convert.ToDecimal(resultadoSqlExpress)
    >        Else
    >            resultadoAccess = Decimal.Round(valor, decimales)         'Access
    >            Return resultadoAccess
    >
    >        End If
    >
    >  End Function

    En Access vas a tener que especificar un valor alfanumérico formateado utilizando la referencia cultural del subproceso actual, ya que Access tiene en cuenta el carácter separador de decimales existentes en la configuración regional del sistema operativo, aparte que la función de conversión CDec que puedes estar utilizando para convertir los valores alfanuméricos en Decimal, también tiene en cuenta dicha configuración regional.

    Si te parece bien, modifica tu función ConversorDatos por ésta otra, la cual devuelve un valor Object, que en el caso de Access encerrará un valor String y en el resto de casos un valor Decimal, de ahí que la función necesariamente tenga que devolver un valor System.Object:

    Imports System.Globalization
    
        Public Shared Function ConversorDatos(valor As Decimal, decimales As Integer) As Object
    
            valor = Decimal.Round(valor, decimales)
    
            If (Configuracion.strNombreBaseDeDatos = "NombreBaseAccess") Then
                ' Para Access devolver el número como cadena alfanumérica formateada
                ' con la referencia cultural del subproceso actual.
                Return valor.ToString(CultureInfo.CurrentCulture)
    
            Else
                ' Para las restantes bases de datos, devolver el valor decimal.
                Return valor
    
            End If
    
        End Function

    Y al método lo llamarás cuando vayas a crear el parámetro:

       
    .Add(Configuracion.CreateParameter(cmd, String.Format("@ejer{0}", i), ConversorDatos(DenominadorUmbralRentab(i), 2)))

    Escribe aquí el nombre de la base de datos correspondiente a Access:

        If (Configuracion.strNombreBaseDeDatos = "NombreBaseAccess") Then

    Dejando el apartado Else para SQL Server y SQL Server Compact.


    Enrique Martínez Montejo
    [MS MVP - Visual Studio y Tecnologías de Desarrollo]

    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, se inteligente y activa la instrucción
    Option Strict.




    martes, 11 de octubre de 2016 17:29
    Moderador
  • Es un problema de configuración de números: coma decimal y punto de miles, o punto decimal y coma de miles. En SQL Server siempre es obligatorio usar PUNTO decimal (no se puede usar la coma), y no se pueden poner separadores de miles. En otros entornos, como Access, puede ser que dependa de la configuración regional de tu panel de control, por lo que aunque a ti te "funcione perfectamente", no puedes confiar en que funcionará en cualquier equipo en el que luego lo ejecutes.

    Acostúmbrate, por tanto, a no convertir los datos numéricos en strings al pasárselos a la sentencia. Mete siempre el valor binario en el parámetro (y lógicamente define el parámetro con el tipo correcto, tal coo Decimal, en lugar de Varchar), y deja que los drivers de la base de datos lo conviertan internamente al formato adecuado (con coma o punto), y así te funcionará en todos los entornos.

    • Marcado como respuesta gemma_campillo lunes, 10 de octubre de 2016 17:26
    lunes, 10 de octubre de 2016 17:08
  • "gemma_campillo" escribió:

    > Me despistó mucho cuando la intenté hacer yo, el tema de la Cultura, ni pensé
    > en ello y esa ha sido la clave para que funcione perfectamente.

    A decir verdad, tampoco es sumamente necesario especificarle una cultura concreta al método ToString de la estructura System.Decimal, porque éste método lo que hace es utilizar un objeto NumberFormatInfo basado en la referencia cultural del subproceso actual (NumberFormatInfo.CurrentInfo), por lo que un usuario con una cultura español de España obtendrá la coma decimal, y otro con una cultura de inglés de Estados Unidos obtendrá el punto decimal como carácter separador de decimales.

    Así que si la variable es System.Decimal, puedes convertirla a String respetando la referencia cultural del subproceso actual de alguna de las siguientes maneras:

        Dim valor As Decimal = 2030.23D
    
        Dim cadena1 As String = CStr(valor)
        Dim cadena2 As String = valor.ToString()
        Dim cadena3 As String = valor.ToString(CultureInfo.CurrentCulture)
        Dim cadena4 As String = valor.ToString(NumberFormatInfo.CurrentInfo)

    En los cuatro casos el valor alfanumérico para una cultura de español de España será "2030,23", con la coma decimal.

    Por tanto tienes donde elegir, ya que la clásica función de conversión CStr de Visual Basic también tiene en cuenta la referencia cultural del subproceso actual.

        Dim cadena3 As String = valor.ToString(CultureInfo.CurrentCulture)
        Dim cadena4 As String = valor.ToString(NumberFormatInfo.CurrentInfo)

    Está sobrecarga del método ToString de la estructura System.Decimal, está pensada mayormente para especificar una cultura concreta que se diferencia de la cultura actual del subproceso actual. Imagina que en tu aplicación estás trabajando con la cultura que tenga establecida el usuario de tu aplicación en su sistema operativo, y quieres formatear, sí o sí, el número decimal utilizando una cultura de inglés de Estados Unidos:

        Dim cadena4 As String = valor.ToString(New CultureInfo("en-US"))
        MessageBox.Show(cadena4)

    En éste caso obtendrás el valor "2030.23", con el punto decimal como carácter separador de decimales, que es el carácter que normalmente suelen utilizar los usuarios de Estados Unidos.


    Enrique Martínez Montejo
    [MS MVP - Visual Studio y Tecnologías de Desarrollo]

    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, se inteligente y activa la instrucción
    Option Strict.


    miércoles, 12 de octubre de 2016 11:36
    Moderador

Todas las respuestas

  • Es un problema de configuración de números: coma decimal y punto de miles, o punto decimal y coma de miles. En SQL Server siempre es obligatorio usar PUNTO decimal (no se puede usar la coma), y no se pueden poner separadores de miles. En otros entornos, como Access, puede ser que dependa de la configuración regional de tu panel de control, por lo que aunque a ti te "funcione perfectamente", no puedes confiar en que funcionará en cualquier equipo en el que luego lo ejecutes.

    Acostúmbrate, por tanto, a no convertir los datos numéricos en strings al pasárselos a la sentencia. Mete siempre el valor binario en el parámetro (y lógicamente define el parámetro con el tipo correcto, tal coo Decimal, en lugar de Varchar), y deja que los drivers de la base de datos lo conviertan internamente al formato adecuado (con coma o punto), y así te funcionará en todos los entornos.

    • Marcado como respuesta gemma_campillo lunes, 10 de octubre de 2016 17:26
    lunes, 10 de octubre de 2016 17:08
  • Hola Alberto:

    Me lleva loca este tema.

    Mira por favor, si yo tengo el parámetro así:

    .Add(Configuracion.CreateParameter(cmd, String.Format("@ejer{0}", i), UmbralRentabilidad(i))

    como lo tendría que indicar, es ahí que hago pruebas y pruebas y cuando me funciona uno no me funciona el otro.

    Incluso estoy probando una función pero en access sigue poniéndome mal los datos.

      Public Shared Function ConversorDatos(valor As Decimal, decimales As Integer) As Decimal
            Dim resultadoSqlExpress As Decimal
            Dim resultadoAccess As Decimal
    
            If Configuracion.strNombreBaseDeDatos = "PerseoSqlEx" Then    'Sql Express
                resultadoSqlExpress = Decimal.Round(valor, decimales)
                Return Convert.ToDecimal(resultadoSqlExpress)
            Else
                resultadoAccess = Decimal.Round(valor, decimales)         'Access
                Return resultadoAccess
    
            End If
    
        End Function

    Con lo que el parámetro me quedaría así:

    .Add(Configuracion.CreateParameter(cmd, String.Format("@ejer{0}", i), ConversorDatos(DenominadorUmbralRentab(i), 2)))

    Bueno, si ves como lo puedo solucionar perfecvto,m y si no también.

    Muchas gracias querido Alberto.

    Gemma

    lunes, 10 de octubre de 2016 17:26
  • "gemma_campillo" escribió:

    > Por ejemplo el valor que lleva la propiedad es: 0,67545 y en access tal como
    > está lo mostraría correctamente y en sqlCompact también, ahora si pongo
    > .Add(Configuracion.CreateParameter(cmd, String.Format("@ejer{0}", i),
    > Cdec(UmbralRentabilidad(i).ToString()))), el valor en Access me lo pone como
    > 67545 y eso es lo que me muestra en a grilla.
    >
    > Incluso estoy probando una función pero en access sigue poniéndome mal los datos.
    >
    >  Public Shared Function ConversorDatos(valor As Decimal, decimales As Integer) As Decimal
    >        Dim resultadoSqlExpress As Decimal
    >        Dim resultadoAccess As Decimal
    >
    >        If Configuracion.strNombreBaseDeDatos = "PerseoSqlEx" Then    'Sql Express
    >            resultadoSqlExpress = Decimal.Round(valor, decimales)
    >            Return Convert.ToDecimal(resultadoSqlExpress)
    >        Else
    >            resultadoAccess = Decimal.Round(valor, decimales)         'Access
    >            Return resultadoAccess
    >
    >        End If
    >
    >  End Function

    En Access vas a tener que especificar un valor alfanumérico formateado utilizando la referencia cultural del subproceso actual, ya que Access tiene en cuenta el carácter separador de decimales existentes en la configuración regional del sistema operativo, aparte que la función de conversión CDec que puedes estar utilizando para convertir los valores alfanuméricos en Decimal, también tiene en cuenta dicha configuración regional.

    Si te parece bien, modifica tu función ConversorDatos por ésta otra, la cual devuelve un valor Object, que en el caso de Access encerrará un valor String y en el resto de casos un valor Decimal, de ahí que la función necesariamente tenga que devolver un valor System.Object:

    Imports System.Globalization
    
        Public Shared Function ConversorDatos(valor As Decimal, decimales As Integer) As Object
    
            valor = Decimal.Round(valor, decimales)
    
            If (Configuracion.strNombreBaseDeDatos = "NombreBaseAccess") Then
                ' Para Access devolver el número como cadena alfanumérica formateada
                ' con la referencia cultural del subproceso actual.
                Return valor.ToString(CultureInfo.CurrentCulture)
    
            Else
                ' Para las restantes bases de datos, devolver el valor decimal.
                Return valor
    
            End If
    
        End Function

    Y al método lo llamarás cuando vayas a crear el parámetro:

       
    .Add(Configuracion.CreateParameter(cmd, String.Format("@ejer{0}", i), ConversorDatos(DenominadorUmbralRentab(i), 2)))

    Escribe aquí el nombre de la base de datos correspondiente a Access:

        If (Configuracion.strNombreBaseDeDatos = "NombreBaseAccess") Then

    Dejando el apartado Else para SQL Server y SQL Server Compact.


    Enrique Martínez Montejo
    [MS MVP - Visual Studio y Tecnologías de Desarrollo]

    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, se inteligente y activa la instrucción
    Option Strict.




    martes, 11 de octubre de 2016 17:29
    Moderador
  • si yo tengo el parámetro así:

    .Add(Configuracion.CreateParameter(cmd, String.Format("@ejer{0}", i), UmbralRentabilidad(i))

    Habría que ver qué es lo que hace por dentro ese método "CreateParameter" de tu clase Configuración, no vaya a ser que por dentro esté creando un parámetro de tipo varchar. En ese caso, serían inútiles todos los esfuerzos que estás haciendo dentro de la función UmbralRentabilidad para devolver un valor numérico correcto, ya que se estaría machacando al convertirlo a cadena dentro de CreateParameter.
    martes, 11 de octubre de 2016 18:49
  • Hola Enrique:

    Perfecto, ya estaba haciendo un if en los parámetros para que escogiera una cosa u otra.

    No había forma de poder hacer la función, la he estado intentando hacer y siempre me da error en los resultados.

    Bueno, como siempre se que las cosas que haces funcionan a la primera, está solucionado.

    Muchas gracias como siempre querido amigo.

    Un fuerte abrazo.

    Gemma


    martes, 11 de octubre de 2016 19:05
  • "Alberto Poblacion" escribió:

    > Habría que ver qué es lo que hace por dentro ese método "CreateParameter" de
    > tu clase Configuración, no vaya a ser que por dentro esté creando un parámetro
    > de tipo varchar.

    Hola, Alberto:

    Si nuestra amiga Gemma no ha cambiado la implementación del método CreateParameter que en su día le indiqué, el parámetro creado tendrá el mismo tipo de dato que tenga el valor especificado:

        ''' <summary>
        ''' Devuelve un objeto DbParameter apropiado al tipo del objeto DbCommand especifidado. 
        ''' </summary>
        ''' <param name="cmd">Objeto Command debidamente configurado.</param>
        ''' <param name="name">Nombre del parámetro.</param>
        ''' <param name="value">Valor del parámetro.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Function CreateParameter(cmd As DbCommand, name As String, value As Object) As DbParameter
    
            If (cmd Is Nothing) Then
                Throw New ArgumentException("El valor del comando no es válido.")
            End If
    
            Dim param As DbParameter = cmd.CreateParameter()
            param.ParameterName = name
            param.Value = value
    
            Return param
    
        End Function
    

    Es decir, si le pasa un valor System.Decimal el tipo del parámetro será DbType.Decimal, y si le pasa un valor System.String el tipo será DbType.String.

    Un saludo


    Enrique Martínez Montejo
    [MS MVP - Visual Studio y Tecnologías de Desarrollo]

    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, se inteligente y activa la instrucción
    Option Strict.

    miércoles, 12 de octubre de 2016 4:58
    Moderador
  • Hola a los dos:

    La función CreateParameter es exactamente igual a la que ha indicado Enrique Martínez, siempre ha sido la misma desde que me la creó el para poder trabajar con la factoría de proveedores.

    También quiero indicar que dicha función que ha creado para cualquier base de datos (ConversorDatos), funciona de maravilla, haciendo su trabajo a la perfección- Me despistó mucho cuando la intenté hacer yo, el tema de la Cultura, ni pensé en ello y esa ha sido la clave para que funcione perfectamente.

    Buenos gracias a los dos por vuestra preocupación y espero que quede aclarado el tema de la creación de parámetros.

    Un abrazo a los dos.

    Gemma

    miércoles, 12 de octubre de 2016 9:10
  • "gemma_campillo" escribió:

    > Me despistó mucho cuando la intenté hacer yo, el tema de la Cultura, ni pensé
    > en ello y esa ha sido la clave para que funcione perfectamente.

    A decir verdad, tampoco es sumamente necesario especificarle una cultura concreta al método ToString de la estructura System.Decimal, porque éste método lo que hace es utilizar un objeto NumberFormatInfo basado en la referencia cultural del subproceso actual (NumberFormatInfo.CurrentInfo), por lo que un usuario con una cultura español de España obtendrá la coma decimal, y otro con una cultura de inglés de Estados Unidos obtendrá el punto decimal como carácter separador de decimales.

    Así que si la variable es System.Decimal, puedes convertirla a String respetando la referencia cultural del subproceso actual de alguna de las siguientes maneras:

        Dim valor As Decimal = 2030.23D
    
        Dim cadena1 As String = CStr(valor)
        Dim cadena2 As String = valor.ToString()
        Dim cadena3 As String = valor.ToString(CultureInfo.CurrentCulture)
        Dim cadena4 As String = valor.ToString(NumberFormatInfo.CurrentInfo)

    En los cuatro casos el valor alfanumérico para una cultura de español de España será "2030,23", con la coma decimal.

    Por tanto tienes donde elegir, ya que la clásica función de conversión CStr de Visual Basic también tiene en cuenta la referencia cultural del subproceso actual.

        Dim cadena3 As String = valor.ToString(CultureInfo.CurrentCulture)
        Dim cadena4 As String = valor.ToString(NumberFormatInfo.CurrentInfo)

    Está sobrecarga del método ToString de la estructura System.Decimal, está pensada mayormente para especificar una cultura concreta que se diferencia de la cultura actual del subproceso actual. Imagina que en tu aplicación estás trabajando con la cultura que tenga establecida el usuario de tu aplicación en su sistema operativo, y quieres formatear, sí o sí, el número decimal utilizando una cultura de inglés de Estados Unidos:

        Dim cadena4 As String = valor.ToString(New CultureInfo("en-US"))
        MessageBox.Show(cadena4)

    En éste caso obtendrás el valor "2030.23", con el punto decimal como carácter separador de decimales, que es el carácter que normalmente suelen utilizar los usuarios de Estados Unidos.


    Enrique Martínez Montejo
    [MS MVP - Visual Studio y Tecnologías de Desarrollo]

    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, se inteligente y activa la instrucción
    Option Strict.


    miércoles, 12 de octubre de 2016 11:36
    Moderador
  • Hola maestro:

    Cada vez aprendo cosas nuevas, ya he cambiado en el programa todas las fórmulas y ahora me estoy decantando por quitar las "LIstasOF para los updates", las voy pasando a los procedimientos. Ya que aparte de los errores por desconocimiento que me dan las listas con el conversor, creo que según me dijiste hace días el tiempo es mínimo entre ejecutar un update desde el procedimiento o bien desde la lista y más en este caso que cada vez acude a la función para el tipo de dato. Por otro lado, controlo mejor los datos desde su fórmula (procedimiento) que desde la lista.

    Bueno voy a seguir que hay muchas, nada más dejo la listas para los inserts que no es necesario tocarlos, ya que trabajan sobre strings o sobre ceros.

    Enrique deberías estar descansando y no trabajando para los demás.

    Recibe como siempre un fuerte abrazo.

    Gemma.

    miércoles, 12 de octubre de 2016 13:14