Share via


Discordancias de tipos SQL-CLR (LINQ to SQL)

LINQ to SQL automatiza gran parte de la conversión entre el modelo de objetos y SQL Server.No obstante, algunas situaciones impiden la exactitud de la conversión.Estas discordancias clave entre los tipos de Common Language Runtime (CLR) y los de base de datos de SQL Server se resumen en las siguientes secciones.Puede encontrar más detalles sobre asignaciones de tipos y la conversión de funciones específicas en Correspondencia de tipos SQL-CLR (LINQ to SQL) y Tipos de datos y funciones (LINQ to SQL).

Tipos de datos

La conversión entre CLR y SQL Server se produce cuando se envía una consulta a la base de datos y cuando los resultados se devuelven al modelo de objetos.Por ejemplo, la siguiente consulta de Transact-SQL requiere dos conversiones de valores:

Select DateOfBirth From Customer Where CustomerId = @id   

Antes de que se pueda ejecutar la consulta en SQL Server, se debe especificar el valor del parámetro de Transact-SQL.En este ejemplo, el valor del parámetro id primero se debe convertir de un tipo de elemento System.Int32 de CLR a un tipo de elemento INT de SQL Server para que la base de datos pueda entender el significado del valor.Después, para recuperar los resultados, la columna DateOfBirth de SQL Server se debe convertir de un tipo de elemento DATETIME de SQL Server a un tipo de elemento System.DateTime de CLR para su uso en el modelo de objetos.En este ejemplo, los tipos del modelo de objetos de CLR y la base de datos de SQL Server tienen asignaciones naturales.Pero no siempre es así.

Ausencia de tipos equivalentes

Los tipos siguientes no tienen equivalencias razonables.

  • Discordancias del espacio de nombres System de CLR:

    • Enteros sin signo.Estos tipos suelen asignarse a sus equivalentes con signo de mayor tamaño para evitar el desbordamiento.Los literales se pueden convertir a un tipo numérico con signo del mismo tamaño o de un tamaño inferior, según su valor.

    • Boolean.Estos tipos pueden asignarse a un valor numérico en bits o mayor, o a una cadena.Un literal se puede asignar a una expresión que se evalúe en el mismo valor (por ejemplo, 1=1 en SQL para True en CLS).

    • TimeSpan.Este tipo representa la diferencia entre dos valores DateTime y no se corresponde con timestamp en SQL Server.El elemento Timespan de CLR también se puede asignar al tipo de elemento TIME de SQL Server en algunos casos.La única finalidad del tipo de elemento TIME de SQL Server era representar valores positivos menores que 24 horas.El ámbito del elemento Timespan de CLR es mucho más amplio.

    NotaNota

    Los tipos de .NET Framework que son específicos de SQL Server en el espacio de nombres System.Data.SqlTypes no se incluyen en esta comparación.

  • Discordancias de SQL Server:

    • Tipos de caracteres de longitud fija.Transact-SQL distingue la categoría Unicode y de la que no lo es. Además tiene tres tipos distintos en cada categoría: nchar/char, de longitud fija, nvarchar/varchar, de longitud variable y ntext/text de mayor tamaño.Los tipos de caracteres de longitud fija podrían asignarse al tipo de elemento System.Char de CLR para recuperar los caracteres, pero realmente no se corresponden con el mismo tipo en conversiones y comportamiento.

    • Bit.Aunque el dominio bit tiene el mismo número de valores que Nullable<Boolean>, ambos son tipos diferentes.Bit usa los valores 1 y 0 en lugar de true/false y no se puede utilizar como equivalente de las expresiones booleanas.

    • Timestamp.A diferencia del tipo de elemento Timespan de CLR, el tipo de elemento TIMESTAMP de SQL Server representa un número de 8 bits generado por la base de datos que es único para cada actualización y no se basa en la diferencia entre los valores de DateTime.

    • Money y SmallMoney.Estos tipos pueden asignarse a Decimal, pero básicamente son tipos diferentes y son tratados como tales en las conversiones y por las funciones basadas en servidor.

Asignaciones múltiples

Existen muchos tipos de datos de SQL Server que se pueden asignar a uno o más tipos de datos de CLR.Asimismo, existen muchos tipos de datos de CLR que se pueden asignar a uno o más tipos de SQL Server.Aunque LINQ to SQL pueda admitir una asignación, no significa que dos tipos asignados entre CLR y SQL Server tengan una coincidencia perfecta en precisión, ámbito y semántica.Algunas asignaciones pueden incluir diferencias en alguna o todas estas dimensiones.Puede encontrar más detalles sobre estas posibles diferencias para las diversas posibilidades de asignaciones en Correspondencia de tipos SQL-CLR (LINQ to SQL).

Tipos definidos por el usuario

Los tipos de CLR definidos por el usuario están diseñados para eliminar las lagunas del sistema de tipos.No obstante, revelan problemas interesantes con el control de versiones de los tipos.Un cambio de versión en el cliente podría no corresponderse con un cambio en el tipo almacenado en el servidor de bases de datos.Esto generaría otra discordancia de tipos según la cual no coincidiría la semántica de tipos y es probable que saltase a la vista la diferencia entre las versiones.Esto se complica todavía más cuando las jerarquías de herencia se refactorizan en sucesivas versiones.

Semántica de expresión

Además de la discordancia en pares entre los tipos de CLR y los tipos de base de datos, las expresiones agregan complejidad.Deben considerarse las discordancias en la semántica de operador, semántica de función, conversión de tipos implícita y reglas de prioridad.

Las subsecciones siguientes muestran la discordancia entre expresiones aparentemente similares.Quizás se puedan generar expresiones SQL semánticamente equivalentes a una expresión CLR dada.Sin embargo, no está claro si las diferencias semánticas entre expresiones aparentemente similares son evidentes para un usuario de CLR y, por tanto, si se han previsto o no los cambios necesarios para la equivalencia semántica.Éste es un problema especialmente crítico cuando una expresión se evalúa para un conjunto de valores.La visibilidad de la diferencia podría depender de los datos y ser difícil de identificar durante la codificación y depuración.

Semántica de valores null

Las expresiones SQL proporcionan lógica de tres valores para las expresiones booleanas.El resultado puede ser True, False o Null.En cambio, CLR especifica un resultado booleano de dos valores para las comparaciones que implican valores de tipo NULL.Considere el código siguiente:

Dim i? As Integer = Nothing
Dim j? As Integer = Nothing
If i = j Then
    '  This branch is executed.
End If
Nullable<int> i = null;
Nullable<int> j = null;
if (i == j)
{
    // This branch is executed.
}
-- Assume col1 and col2 are integer columns with null values. 
-- Assume that ANSI null behavior has not been explicitly
--    turned off.
Select …
From …
Where col1 = col2
-- Evaluates to null, not true and the corresponding row is not
--     selected.
-- To obtain matching behavior (i -> col1, j -> col2) change
--     the query to the following:
Select …
From …
Where
    col1 = col2 
or (col1 is null and col2 is null)
-- (Visual Basic 'Nothing'.)

Un problema similar se da cuando se asumen resultados de dos valores.

If (i = j) Or (i <> j) Then ' Redundant condition.
    ' ...
End If
if ((i == j) || (i != j)) // Redundant condition.
{
    // ...
}
-- Assume col1 and col2 are nullable columns.
-- Assume that ANSI null behavior has not been explicitly
--     turned off.
Select …
From …
Where
    col1 = col2      
or col1 != col2
-- Visual Basic: col1 <> col2.

-- Excludes the case where the boolean expression evaluates
--     to null. Therefore the where clause does not always
--     evaluate to true.

En el caso anterior, puede obtener un comportamiento equivalente para generar SQL, pero la conversión podría no reflejar su intención de manera precisa.

LINQ to SQL no impone la semántica de comparación de valores null en C# o nothing en Visual Basic para SQL.Los operadores de comparación se convierten sintácticamente en sus equivalentes SQL.La semántica refleja la semántica de SQL tal como la define la configuración del servidor o de la conexión.Dos valores nulos se consideran distintos según la configuración predeterminada de SQL Server (aunque se puede cambiar la configuración para cambiar la semántica).Sea como fuere, LINQ to SQL no considera la configuración del servidor en la conversión de consultas.

Una comparación con el literal null (nothing) se convierte a la versión de SQL correcta (is null o is not null).

SQL Server define el valor null (nothing) en la intercalación; LINQ to SQL no cambia la intercalación.

Conversión y promoción de tipos

SQL admite un completo conjunto de conversiones implícitas en las expresiones.Las mismas expresiones en C# requerirían una conversión de tipos explícita.Por ejemplo:

  • Los tipos Nvarchar y DateTime se pueden comparar en SQL sin ninguna conversión de tipos explícita; C# requiere la conversión explícita.

  • Decimal se convierte implícitamente a DateTime en SQL.C# no permite una conversión implícita.

De igual forma, la prioridad de los tipos en Transact-SQL difiere de la prioridad de los tipos en C#, ya que el conjunto de tipos subyacente es diferente.De hecho, no hay una relación clara de subconjuntos/supraconjuntos entre las listas de prioridades.Por ejemplo, al comparar nvarchar con varchar, se produce la conversión implícita de la expresión varchar a nvarchar.CLR no proporciona ninguna promoción equivalente.

En casos más sencillos, estas diferencias generan expresiones CLR con conversiones de tipos que van a ser redundantes para la expresión SQL correspondiente.Lo que es más importante, los resultados intermedios de una expresión SQL podrían promoverse implícitamente a un tipo que no tiene ningún homólogo preciso en C#, y lo mismo a la inversa.En general, las pruebas, la depuración y la validación de tales expresiones representan una carga adicional importante para el usuario.

Collation

Transact-SQL admite las intercalaciones explícitas como anotaciones en tipos de cadena de caracteres.Estas intercalaciones determinan la validez de ciertas comparaciones.Por ejemplo, al comparar dos columnas con intercalaciones explícitas diferentes, se genera un error.Si se utiliza el tipo de cadena CTS, más simplificado, no se producen tales errores.Considere el ejemplo siguiente:

create table T2 (
    Col1 nvarchar(10),
    Col2      nvarchar(10) collate Latin_general_ci_as
)
Class C
    Dim s1 As String    ' Map to T2.Col1.
    Dim s2 As String    ' Map to T2.Col2.
    Sub Compare()
        If s1 = s2 Then ' This is correct.
            ' ...
        End If
    End Sub
End Class
class C
{
string s1;       // Map to T2.Col1.
string s2;       // Map to T2.Col2.

    void Compare()
    {
        if (s1 == s2) // This is correct.
        {
            // ...
        }
    }
}
Select …
From …
Where Col1 = Col2
-- Error, collation conflict.

De hecho, la subcláusula de intercalación crea un tipo restringido que no es sustituible.

De forma similar, el criterio de ordenación puede ser bastante diferente entre los sistemas de tipos.Esta diferencia afecta a la ordenación de los resultados.Guid se ordena en los 16 bytes por orden lexicográfico (IComparable()), mientras que T-SQL compara los GUID en el orden siguiente: node(10-15), clock-seq(8-9), time-high(6-7), time-mid(4-5), time-low(0-3).Esta ordenación era la habitual en SQL 7.0, cuando los GUID generados por NT tenían este orden de octetos.Este enfoque garantizaba que los GUID generados en el mismo clúster de nodos se obtenían juntos y ordenados secuencialmente, según la marca de tiempo.También era útil para compilar índices (las inserciones se convertían en anexos en lugar de E/S aleatorias).Posteriormente, el orden se codificó en Windows por cuestiones de privacidad, pero SQL debe mantener la compatibilidad.Una solución alternativa es utilizar SqlGuid en lugar de Guid.

Diferencias entre operadores y funciones

Los operadores y las funciones que son esencialmente comparables presentan una semántica ligeramente diferente.Por ejemplo:

  • C# especifica una semántica de cortocircuito basada en el orden léxico de los operandos para los operadores lógicos && y ||.Por otro lado, SQL está orientado a consultas basadas en conjuntos y, por consiguiente, proporciona más libertad al optimizador para que decida el orden de ejecución.Algunas de las implicaciones se exponen a continuación:

    • Una conversión semánticamente equivalente requeriría la construcción "CASE … WHEN … THEN" en SQL para evitar la reordenación en la ejecución de los operandos.

    • Una conversión libre a AND/OR podría producir errores inesperados si la expresión C# confía en que la evaluación del segundo operando se basa en el resultado de la evaluación del primero.

  • La función Round() tiene una semántica diferente en .NET Framework y en T-SQL.

  • El índice de inicio de las cadenas es 0 en CLR, pero 1 en SQL.Por consiguiente, cualquier función que tenga un índice requiere la conversión del índice.

  • CLR admite el operador de módulo (‘%’) para los números de punto flotante, pero SQL no.

  • El operador Like admite eficazmente las sobrecargas automáticas basadas en las conversiones implícitas.Aunque el operador Like por definición funciona en tipos de cadena de caracteres, la conversión implícita de tipos numéricos o tipos DateTime permite utilizar igualmente tipos que no son de cadena con Like.En CTS, no existe una conversión implícita comparable.Por lo tanto, se requieren sobrecargas adicionales.

    NotaNota

    Este comportamiento del operador Like solo se aplica en C#; la palabra clave de Visual Basic Like se mantiene invariable.

  • El desbordamiento siempre se comprueba en SQL, pero tiene que especificarse explícitamente en C# (no en Visual Basic) para evitar el salto.Dadas las columnas de enteros C1, C2 y C3, si C1+C2 se almacena en C3 (Update T Set C3 = C1 + C2):

    create table T3 (
        Col1      integer,
        Col2      integer
    )
    insert into T3 (col1, col2) values (2147483647, 5)
    -- Valid values: max integer value and 5.
    select * from T3 where col1 + col2 < 0
    -- Produces arithmetic overflow error.
    
' Does not apply.
' Visual Basic overflow in absence of implicit check
' (turn off overflow checks in compiler options)
Dim I As Integer = Int32.MaxValue
Dim j As Integer = 5
If I + j < 0 Then
    ' This code prints the overflow message.
    Console.WriteLine("Overflow!")
End If
// C# overflow in absence of explicit checks.
int i = Int32.MaxValue;
int j = 5;
if (i+j < 0) Console.WriteLine("Overflow!");
// This code prints the overflow message.
  • SQL realiza un redondeo aritmético simétrico mientras que .NET Framework utiliza el redondeo bancario.Para obtener información detallada, vea el artículo 196652 de Microsoft Knowledge Base.

  • De forma predeterminada, para las configuraciones regionales comunes, en las comparaciones de cadenas de caracteres no se hace distinción entre mayúsculas y minúsculas en SQL.En Visual Basic y en C#, se distingue entre mayúsculas y minúsculas.Por ejemplo, s == "Food" (s = "Food" en Visual Basic) y s == "Food" pueden producir resultados diferentes si s es food.

    -- Assume default US-English locale (case insensitive).
    create table T4 (
        Col1      nvarchar (256)
    )
    insert into T4 values (‘Food’) 
    insert into T4 values (‘FOOD’)
    select * from T4 where Col1 = ‘food’
    -- Both the rows are returned because of case-insensitive matching.
    
' Visual Basic equivalent on collections of Strings in place of
' nvarchars.
Dim strings() As String = {"food", "FOOD"}
For Each s As String In strings
    If s = "food" Then
        Console.WriteLine(s)
    End If
Next
' Only "food" is returned.
// C# equivalent on collections of Strings in place of nvarchars.
String[] strings = { "food", "FOOD" };
foreach (String s in strings)
{
    if (s == "food")
    {
        Console.WriteLine(s);
    }
}
// Only "food" is returned.
  • Los operadores o las funciones aplicados a argumentos de tipo de caracteres de longitud fija en SQL tienen una semántica significativamente diferente a la de las mismas funciones y operadores aplicados a System.String de CLR.Esto también podría interpretarse como una implicación más del problema de la ausencia de tipos equivalentes que se describió en la sección relativa a los tipos.

    create table T4 (
        Col1      nchar(4)
    )
    Insert into T5(Col1) values ('21');
    Insert into T5(Col1) values ('1021');
    Select * from T5 where Col1 like '%1'
    -- Only the second row with Col1 = '1021' is returned.
    -- Not the first row!
    
    ' Assume Like(String, String) method.
    Dim s As String    ' Map to T4.Col1.
    If s Like (System.Data.Linq.SqlClient.SqlMethods.Like(s, "%1")) Then
        Console.WriteLine(s)
    End If
    ' Expected to return true for both "21" and "1021".
    
    // Assume Like(String, String) method.
    string s = ""; // map to T4.Col1
    if (System.Data.Linq.SqlClient.SqlMethods.Like(s, "%1"))
    {
        Console.WriteLine(s);
    }
    // Expected to return true for both "21" and "1021"
    

    Un problema similar se produce con la concatenación de cadenas.

    create table T6 (
        Col1      nchar(4)
        Col2       nchar(4)
    )
    Insert into T6 values ('a', 'b');
    Select Col1+Col2 from T6
    -- Returns concatenation of padded strings "a   b   " and not "ab".
    

En resumen, podría requerirse una conversión compleja para las expresiones CLR, así como operadores o funciones adicionales para exponer la funcionalidad de SQL.

Conversión de tipos

En C# y en SQL, los usuarios pueden invalidar la semántica predeterminada de las expresiones utilizando conversiones de tipos explícitas (Cast y Convert).Sin embargo, exponer esta funcionalidad entre los límites del sistema de tipos puede plantear un dilema.Una conversión de tipos de SQL que proporciona la semántica deseada no se puede convertir con facilidad a la conversión de tipos de C# correspondiente.Por otro lado, una conversión de tipos de C# no se puede convertir directamente en una conversión de tipos de SQL equivalente debido a las discordancias de los tipos, la ausencia de tipos equivalentes y las diferencias en las jerarquías de prioridades de tipos.Es necesario elegir entre exponer la discordancia con el sistema de tipos y perder una importante capacidad de expresión.

En otros casos, puede que la conversión de tipos no sea necesaria para validar una expresión en cualquiera de los dos dominios, pero podría serlo para garantizar que una asignación no predeterminada se aplica correctamente a la expresión.

-- Example from "Non-default Mapping" section extended
create table T5 (
    Col1      nvarchar(10),
    Col2      nvarchar(10)
)
Insert into T5(col1, col2) values (‘3’, ‘2’);
Class C
    Dim x As Integer        ' Map to T5.Col1.
    Dim y As Integer        ' Map to T5.Col2.

    Sub Casting()
        ' Intended predicate.
        If (x + y) > 4 Then
            ' Valid for the data above.
        End If
    End Sub
End Class
class C
{
    int x;        // Map to T5.Col1.
    int y;        // Map to T5.Col2.

    void Casting()
    {
        // Intended predicate.
        if (x + y > 4)
        {
            // valid for the data above
        }
    }
}
Select *
From T5
Where Col1 + Col2 > 4   
-- "Col1 + Col2" expr evaluates to '32' 

Problemas de rendimiento

Si se tienen en cuenta algunas de las diferencias de los tipos de SQL Server-CLR, el resultado puede ser una reducción del rendimiento si se entrecruzan los sistemas de tipos de CLR y SQL Server.A continuación se muestran ejemplos de escenarios cuyo rendimiento se ve muy afectado:

  • Orden forzado de evaluación de los operadores lógicos AND/OR

  • La generación de SQL para aplicar el orden de evaluación del predicado limita la funcionalidad del optimizador de SQL.

  • Las conversiones de tipos, ya sean originadas por un compilador de CLR o por una implementación de consulta relacional de objetos, pueden reducir el uso de los índices.

    Por ejemplo:

    -- Table DDL
    create table T5 (
        Col1      varchar(100)
    )
    
    Class C5
        Dim s As String ' Map to T5.Col1.
    End Class
    
    class C5
    {
        string s;        // Map to T5.Col1.
    }
    

    Considere la conversión de la expresión (s = SOME_STRING_CONSTANT).

    -- Corresponding part of SQL where clause
    Where …
    Col1 = SOME_STRING_CONSTANT
    -- This expression is of the form <varchar> = <nvarchar>.
    -- Hence SQL introduces a conversion from varchar to nvarchar,
    --     resulting in
    Where …
    Convert(nvarchar(100), Col1) = SOME_STRING_CONSTANT
    -- Cannot use the index for column Col1 for some implementations.
    

Además de las diferencias semánticas, es importante tener en cuenta el impacto en el rendimiento si se entrecruzan los sistemas de tipos de CLR y SQL Server.Para los conjuntos de datos grandes, estos problemas de rendimiento pueden determinar si una aplicación se puede implementar o no.

Vea también

Otros recursos

Información general (LINQ to SQL)