Documentos de Académico
Documentos de Profesional
Documentos de Cultura
INDICE GENERAL
1. Introducción a LINQ
2. Introducción a LINQ en Visual Basic
2.1. Escribir la Primera Consulta con LINQ
2.2. Operaciones Básicas de Consulta
2.3. Características de Visual Basic que Admiten LINQ
2.4. Relaciones entre Tipos en Operaciones de Consulta
3. Cómo: Crear un Proyecto con LINQ
4. Características de IDE de Visual Studio y Herramientas para LINQ
5. Guía de Programación General con LINQ
5.1. Información General sobre Operadores de Consulta Estándar
5.1.1. Sintaxis de Expresiones de Consulta para Operadores de Consulta Estándar
5.1.2. Clasificación de Operadores de Consulta Estándar por Modo de Ejecución
5.1.3. Ordenar Datos
5.1.4. Operaciones Set
5.1.5. Filtrar Datos
5.1.6. Operaciones Cuantificadotas
5.1.7. Operaciones de Proyección
5.1.8. Realizar Particiones de Datos
5.1.9. Operaciones de Combinación
5.1.10. Agrupar Datos
5.1.11. Operaciones de Generación
5.1.12. Operaciones de Igualdad
5.1.13. Operaciones de Elementos
5.1.14. Convertir Tipos de Datos
5.1.15. Operaciones de Concatenación
5.1.16. Operaciones de Agregación
5.2. Arboles de Expresión en LINQ
5.2.1. Cómo: Usar Arboles de Expresión para Crear Consultas Dinámicas
5.3. Habilitar un Origen de Datos para Realizar Consultas
6. LINQ a Objectos
6.1. LINQ y Cadenas
6.1.1. Cómo: Realizar Recuento de las Repeticiones de una Palabra en una Cadena
6.1.2. Cómo: Buscar Frases que Contengan un Conjunto Especificado de Palabras
6.1.3. Cómo: Buscar Caracteres en una Cadena
6.1.4. Cómo: Combinar Consultas LINQ con Expresiones Regulares
6.1.5. Cómo: Buscar la Diferencia de Conjuntos entre Dos Listas
6.1.6. Cómo: Ordenar o Filtrar Datos de Texto por Palabra o Campo
6.1.7. Cómo: Reordenar los Campos de un Archivo Delimitado
6.1.8. Cómo: Combinar y Comparar Colecciones de Cadenas
6.1.9. Cómo: Rellenar Colecciones de Objetos de Varios Orígenes
6.1.10. Cómo: Dividir un Archivo en Varios Archivos Mediante el Uso de Grupos
6.1.11. Cómo: Combinar Contenido de Archivos No Similares
6.1.12. Cómo: Calcular Valores de Columna en un Archivo de Texto CSV
6.2. LINQ y Reflection
6.2.1. Cómo: Consultar los Metadatos de un Ensamblado con la Función de Reflexión
6.3. LINQ y Directorios de Archivos
6.3.1. Cómo: Buscar Archivos con un Nombre o Atributo Especificados
6.3.2. Cómo: Agrupar Archivos por Extensión
6.3.3. Cómo: Buscar el Número Total de Bytes en un Conjunto de Carpetas
6.3.4. Cómo: Comparar el Contenido de dos Carpetas
6.3.5. Cómo: Buscar el Archivo(s) de Mayor Tamaño en un Arbol de Directorios
6.3.6. Cómo: Buscar Archivos Duplicados en un Arbol de Directorios
6.3.7. Cómo: Consultar el Contenido de los Archivos de una Carpeta
6.4. Cómo: Consultar un Objeto ArrayList con LINQ
7. LINQ a XML
7.1. Introducción LINQ a XML
7.1.1. Información General Acerca de LINQ a XML
7.1.2. Diferencias entre LINQ a XML y DOM
1. Introducción a LINQ
Language-Integrated Query (LINQ) es una importante innovación en Visual Studio 2008 y .NET
Framework versión 3.5 que elimina la distancia que separa el mundo de los objetos y el mundo de los
datos.
Tradicionalmente, las consultas con datos se expresan como cadenas sencillas, sin comprobación de tipos
en tiempo de compilación ni compatibilidad con IntelliSense. Además, es necesario aprender un lenguaje
de consultas diferente para cada tipo de origen de datos: bases de datos SQL, documentos XML,
servicios web diversos, etc. LINQ convierte una consulta en una construcción de lenguaje de primera
clase en C# y Visual Basic. Las consultas se escriben para colecciones de objetos con establecimiento
inflexible de tipos, utilizando palabras clave del lenguaje y operadores con los que se está familiarizado.
La ilustración siguiente muestra una consulta LINQ parcialmente completada en una base de datos de
SQL Server en C#, con comprobación de tipos completa y compatibilidad con IntelliSense.
En Visual Studio se pueden escribir consultas LINQ en Visual Basic o en C# con bases de datos de SQL
Server, documentos XML, conjuntos de datos ADO.NET y cualquier colección de objetos que admita
IEnumerable o la interfaz genérica IEnumerable<(Of <(T>)>). También se ha previsto la compatibilidad
de LINQ con el marco de entidades ADO.NET, y otros fabricantes se encuentran escribiendo proveedores
LINQ para muchos servicios web y otras implementaciones de base de datos.
Puede utilizar consultas LINQ en proyectos nuevos o junto a consultas que no son LINQ en proyectos
existentes. El único requisito es que el proyecto esté orientado a la versión 3.5 de .NET Framework.
Esta sección contiene descripciones generales, ejemplos e información básica que le ayudarán a entender
y utilizar Visual Basic y Language-Integrated Query (LINQ).
Language-Integrated Query (LINQ) simplifica esta situación al proporcionar un modelo coherente para
trabajar con los datos de varios formatos y orígenes de datos. En una consulta LINQ, siempre se trabaja
con objetos. Se utilizan los mismos modelos de codificación básicos para consultar y transformar los
datos de documentos XML, bases de datos de SQL, conjuntos de datos y entidades de ADO.NET,
colecciones de .NET Framework y cualquier otro formato u origen de datos para el que haya un
proveedor LINQ disponible. En este documento se describen las tres fases para crear y utilizar consultas
LINQ básicas.
En el ejemplo siguiente se muestran las tres partes de una operación de consulta. En el ejemplo se
utiliza una matriz de enteros como origen de datos, cómodo a efectos de demostración. Sin embargo, los
mismos conceptos también se aplican a otros orígenes de datos.
' Data source.
Dim numbers() As Integer = {0, 1, 2, 3, 4, 5, 6}
' Query creation.
Dim evensQuery = From num In numbers Where num Mod 2 = 0 Select num
' Query execution.
For Each number In evensQuery
Console.Write(number & " ")
Next
Resultado:
0246
El origen de datos
Dado que el origen de datos del ejemplo anterior es una matriz, se admite implícitamente la interfaz
genérica IEnumerable<(Of <(T>)>). Es este hecho lo que permite utilizar una matriz como origen de
datos para una consulta LINQ. Los tipos que admiten IEnumerable(Of T) o una interfaz derivada, como la
genérica IQueryable<(Of <(T>)>), se conocen como tipos que se pueden consultar.
Como tipo que se puede consultar implícitamente, la matriz no requiere modificaciones ni un tratamiento
especial para actuar como origen de datos LINQ. Lo mismo sucede con cualquier tipo de colección que
admita IEnumerable(Of T), incluidas las genéricas List<(Of <(T>)>), Dictionary<(Of <(TKey,
TValue>)>) y otras clases de la biblioteca de clases de .NET Framework.
Si los datos de origen todavía no implementan IEnumerable(Of T), se requiere un proveedor LINQ para
implementar la funcionalidad de los operadores de consulta estándar para ese origen de datos. Por
ejemplo, LINQ a XML controla el trabajo de cargar un documento XML en un tipo XElement que se pueda
consultar, como se muestra en el ejemplo siguiente.
' Create a data source from an XML document.
Dim contacts As XElement = XElement.Load("c:\myContactList.xml")
Con LINQ a SQL, primero se crea una asignación relacional de objetos en tiempo de diseño, ya sea
manualmente o mediante el Diseñador relacional de objetos (Diseñador R/O). Después, se escriben las
consultas en los objetos y, en tiempo de ejecución, LINQ a SQL controla la comunicación con la base de
datos. En el ejemplo siguiente, customers representa una tabla concreta de la base de datos y Table<(Of
<(TEntity>)>) admite la interfaz genérica IQueryable<(Of <(T>)>).
' Create a data source from a SQL table.
Dim db As New DataContext("C:\Northwind\Northwnd.mdf")
Dim customers As Table(Of Customer) = db.GetTable(Of Customer)
La regla básica es simple: un origen de datos LINQ es cualquier objeto que admite la interfaz genérica
IEnumerable<(Of <(T>)>) o una interfaz que herede de ella.
Nota:
También se pueden utilizar tipos como ArrayList, que admite la interfaz no genérica IEnumerable,
como orígenes de datos LINQ.
La consulta
En la consulta se especifica qué información se desea recuperar del origen o de los orígenes de datos.
También tiene la opción de especificar cómo se debería ordenar, agrupar o estructurar esa información
antes de devolverse. Para habilitar la creación de consultas, Visual Basic incorpora nueva sintaxis de
consulta en el lenguaje.
Cuando se ejecuta, la consulta del ejemplo siguiente devuelve todos los números pares de una matriz de
enteros, numbers.
' Data source.
Dim numbers() As Integer = {0, 1, 2, 3, 4, 5, 6}
' Query creation.
Dim evensQuery = From num In numbers Where num Mod 2 = 0 Select num
' Query execution.
For Each number In evensQuery
Console.Write(number & " ")
Next
La expresión de consulta contiene tres cláusulas: From, Where y Select. La función y el propósito
específicos de cada una de las cláusulas de las expresiones de consulta se analiza en Operaciones básicas
de consulta (Visual Basic). Observe que en LINQ una definición de consulta suele almacenarse en una
variable y se ejecuta después. La variable de consulta, como evensQuery en el ejemplo anterior, debe
ser un tipo que se pueda consultar. El tipo de evensQuery es IEnumerable(Of Integer), asignado por el
compilador mediante la inferencia de tipo de variable local.
Es importante recordar que la propia variable de consulta no realiza ninguna acción ni devuelve datos.
Sólo almacena la definición de la consulta. En el ejemplo anterior, es el bucle For Each el que ejecuta la
consulta.
Ejecución de la consulta
La ejecución de la consulta es proceso independiente de su creación. La creación de la consulta la define,
pero su ejecución la desencadena un mecanismo diferente. Se puede ejecutar un consulta en cuanto esté
definida (ejecución inmediata), o se puede guardar la definición y ejecutar la consulta más tarde
(ejecución diferida).
Ejecución diferida
Una consulta LINQ típica se parece a la del ejemplo anterior, en el que se define evensQuery. En él se
crea la consulta, pero no se ejecuta de inmediato. La definición de la consulta se almacena en la variable
de consulta evensQuery. La consulta se ejecuta más adelante, normalmente mediante un bucle For
Each, que devuelve una secuencia de valores, o aplicando un operador de consulta estándar, como
Count o Max. Este proceso se denomina ejecución diferida.
' Query execution that results in a sequence of values.
For Each number In evensQuery
Console.Write(number & " ")
Next
' Query execution that results in a single value.
Dim evens = evensQuery.Count()
Para una secuencia de valores, se tiene acceso a los datos recuperados mediante la variable de iteración
del bucle For Each (number en el ejemplo anterior). Dado que la variable de consulta, evensQuery,
contiene la definición de la consulta en lugar de los resultados, puede ejecutar una consulta tantas veces
como desee, utilizando la variable de consulta. Por ejemplo, podría tener una base de datos en su
aplicación que sea actualizada continuamente por una aplicación independiente. Después de haber
creado una consulta que recupere los datos de esa base de datos, puede utilizar un bucle For Each para
ejecutar la consulta una y otra vez, recuperando en cada ocasión los datos más recientes.
En el siguiente ejemplo se muestra cómo funciona la ejecución diferida. Una vez definida evensQuery2 y
ejecutada con un bucle For Each, como en los ejemplos anteriores, algunos elementos del origen de
datos numbers cambian. A continuación, un segundo bucle For Each vuelve a ejecutar evensQuery2. Los
resultados son diferentes la segunda vez, porque el bucle For Each ejecuta la consulta otra vez,
utilizando los nuevos valores de numbers.
Dim numberArray() As Integer = {0, 1, 2, 3, 4, 5, 6}
Dim evensQuery2 = From num In numberArray Where num Mod 2 = 0 Select num
Console.WriteLine("Evens in original array:")
For Each number In evensQuery2
Console.Write(" " & number)
Next
Console.WriteLine()
' Change a few array elements.
numberArray(1) = 10
numberArray(4) = 22
numberArray(6) = 8
' Run the same query again.
Console.WriteLine(vbCrLf & "Evens in changed array:")
For Each number In evensQuery2
Console.Write(" " & number)
Next
Console.WriteLine()
Resultado:
La consulta siguiente devuelve un recuento de los números pares de una matriz de enteros. La definición
de la consulta no se guarda y numEvens es un Integer simple.
Dim numEvens=(From num In numbers Where num Mod 2 = 0 Select num).Count()
Se puede conseguir el mismo resultado utilizando el método Aggregate.
Dim numEvensAgg = Aggregate num In numbers Where num Mod 2 = 0 _
Select num Into Count()
También puede forzar la ejecución de una consulta llamando al método ToList o ToArray en una
consulta (ejecución inmediata) o variable de consulta (ejecución diferida), como se muestra en el código
siguiente.
' Immediate execution.
Dim evensList=(From num In numbers Where num Mod 2 = 0 Select num).ToList
' Deferred execution.
Dim evensQuery3 = From num In numbers Where num Mod 2 = 0 Select num
Dim evensArray = evensQuery.ToArray()
En los ejemplos anteriores, evensQuery3 es una variable de consulta, pero evensList es una lista y
evensArray es una matriz.
El uso de ToList o ToArray para forzar la ejecución inmediata es especialmente útil cuando se desea
ejecutar la consulta inmediatamente y almacenar los resultados en memoria caché en un objeto de
colección único.
También puede provocar la ejecución de una consulta mediante el uso de un método IEnumerable,
como GetEnumerator (Método, objeto Collection).
un bucle For Each, la variable de rango actúa como referencia para cada elemento sucesivo de
customers. Dado que el compilador puede deducir el tipo de cust, no tiene que especificarlo
explícitamente.
Para recuperar una colección formada por objetos Customer completos, seleccione la variable de rango:
Dim londonCusts2 = From cust In customers Where cust.City = "London" _
Order By cust.Name Ascending Select cust
Si una instancia de Customer es un objeto grande con muchos campos y lo único que desea recuperar es
el nombre, puede seleccionar cust.Name, como se muestra en el ejemplo siguiente. La inferencia de tipo
de variable local reconoce que se cambia el tipo de resultado de una colección de objetos Customer a
una colección de cadenas.
Dim londonCusts3 = From cust In customers Where cust.City = "London" _
Order By cust.Name Ascending Select cust.Name
Para seleccionar varios campos del origen de datos, tiene dos opciones:
En la cláusula Select, especifique los campos que desea incluir en el resultado. El compilador
definirá un tipo anónimo que tiene esos campos como propiedades.
Dado que los elementos devueltos en el ejemplo siguiente son instancias de un tipo anónimo, no
puede hacer referencia al tipo por su nombre en ninguna otra parte del código. El nombre
designado para el tipo por el compilador contiene caracteres que no son válidos en el código de
Visual Basic normal. En el ejemplo siguiente, los elementos de la colección devuelta por la
consulta en londonCusts4 son todos instancias de un tipo anónimo.
Dim londonCusts4=From cust In customers Where cust.City="London" _
Order By cust.Name Ascending Select Name=cust.Name,Phone=cust.Phone
For Each londonCust In londonCusts4
Console.WriteLine(londonCust.Name & " " & londonCust.Phone)
Next
O bien,
Defina un tipo con nombre que contenga los campos concretos que desea incluir en el resultado
y cree e inicialice instancias del tipo en la cláusula Select. Utilice esta opción sólo si tiene que
utilizar los resultados individuales fuera de la colección en la que se devuelven, o si tiene que
pasarlos como parámetros en llamadas a método. El tipo de londonCusts5 en el ejemplo
siguiente es IEnumerable(Of NamePhone).
Public Class NamePhone
Public Name As String
Public Phone As String
' Additional class elements
End Class
Dim londonCusts5=From cust In customers Where cust.City="London" _
Order By cust.Name Ascending Select New NamePhone With {.Name =
cust.Name,.Phone = cust.Phone}
Combinar los datos (Join y Group Join)
Puede combinar más de un origen de datos en la cláusula From de varias maneras. Por ejemplo, el
código siguiente utiliza dos orígenes de datos y combina implícitamente las propiedades de ambos en el
resultado. La consulta selecciona los estudiantes cuyos apellidos empiezan por vocal.
Dim vowels() As String = {"A", "E", "I", "O", "U"}
Dim vowelNames = From student In students, vowel In vowels Where _
student.Last.IndexOf(vowel) = 0 Select Name = student.First & " " & _
student.Last, Initial = vowel Order By Initial
For Each vName In vowelNames
Console.WriteLine(vName.Initial & ": " & vName.Name)
Next
Nota:
Puede ejecutar este código con la lista de estudiantes creada en Cómo: Crear una lista de elementos.
La palabra clave Join es equivalente a INNER JOIN en SQL. Combina dos colecciones según los valores
de clave coincidentes entre los elementos de las dos colecciones. La consulta devuelve la totalidad o una
parte de los elementos de la colección que tienen valores de clave coincidentes. Por ejemplo, el código
siguiente duplica la acción de la combinación implícita anterior.
Dim vowelNames2 = From student In students Join vowel In vowels _
On student.Last(0) Equals vowel Select Name = student.First & " " & _
student.Last, Initial = vowel Order By Initial
Group Join combina las colecciones en una sola colección jerárquica, igual que LEFT JOIN en SQL.
Puede agregar una cláusula Group By para agrupar los elementos de un resultado de consulta según
uno o más campos de los elementos. Por ejemplo, el código siguiente agrupa los estudiantes por año de
clase.
Dim studentsByYear = From student In students Select student Group By
year = student.Year Into Classes = Group
For Each yearGroup In studentsByYear
Console.WriteLine(vbCrLf & "Year: " & yearGroup.year)
For Each student In yearGroup.Classes
Console.WriteLine(" " & student.Last & ", " & student.First)
Next
Next
Si ejecuta este código utilizando la lista de estudiantes creada en Cómo: Crear una lista de elementos, el
resultado de la instrucción For Each es:
Year: Junior
Tucker, Michael
Garcia, Hugo
Garcia, Debra
Tucker, Lance
Year: Senior
Omelchenko, Svetlana
Osada, Michiko
Fakhouri, Fadi
Feng, Hanying
Adams, Terry
Year: Freshman
Mortensen, Sven
Garcia, Cesar
La variación mostrada en el código siguiente ordena los años de clase y, a continuación, ordena los
estudiantes de cada año por apellido.
Dim studentsByYear2=From student In students Select student Order By
student.Year,student.Last Group By year=student.Year Into Classes=Group
Las secciones siguientes describen las nuevas construcciones de lenguaje con suficiente detalle como
para permitirle empezar con la lectura de la documentación introductoria, ejemplos de código y
aplicaciones de ejemplo. También puede hacer clic en los vínculos para encontrar explicaciones más
detalladas de cómo las características del lenguaje contribuyen a permitir las consultas integradas en el
lenguaje.
Expresiones de consulta
Las expresiones de consulta en Visual Basic 2008 se pueden expresar en una sintaxis declarativa similar
a la de SQL o XQuery. En tiempo de compilación, la sintaxis de consulta se convierte en llamadas a
métodos en una implementación del proveedor LINQ de los métodos de extensión de operadores de
consulta estándar. Las aplicaciones controlan qué operadores de consulta estándar están dentro del
ámbito especificando el espacio de nombres adecuado con una instrucción Imports. La sintaxis para una
expresión de consulta en Visual Basic es similar a ésta:
Dim londonCusts = From cust In customers Where cust.City = "London" _
Order By cust.Name Ascending Select cust.Name, cust.Phone
Variables con tipo implícito
En lugar de especificar explícitamente un tipo al declarar e inicializar una variable, puede permitir ahora
al compilador deducir y asignar el tipo, como se muestra en el ejemplo siguiente. Esto se conoce como
inferencia de tipo de variable local.
Nota:
La inferencia de tipo de variable local sólo funciona cuando define una variable local dentro del
cuerpo de un método con Option Infer establecida en On. On es el valor predeterminado para
nuevos proyectos en LINQ.
Nota:
En Visual Basic 2005 y versiones anteriores, estos ejemplos se compilan bien, pero el tipo asignado a
aNumber y aName es Object. Por consiguiente, un proyecto existente que se vuelve a compilar en
Visual Basic 2008 con Option Infer establecida en On puede comportarse de manera diferente que
en versiones anteriores del lenguaje.
Inicializadores de objeto
Los inicializadores de objeto se utilizan en expresiones de consulta cuando se debe crear un tipo anónimo
para albergar los resultados de una consulta. También se pueden utilizar para inicializar objetos de tipos
con nombre fuera de las consultas. Utilizando un inicializador de objeto, puede inicializar un objeto en
una sola línea sin llamar explícitamente a un constructor. Suponiendo que tiene una clase denominada
Customer con propiedades públicas Name y Phone, además de otras propiedades, un inicializador de
objeto se puede utilizar de esta manera:
Dim aCust As Customer=New Customer With{.Name="Mike",.Phone = "555-0212"}
Tipos anónimos
Los tipos anónimos proporcionan una manera práctica de agrupar temporalmente un conjunto de
propiedades en un elemento que se desea incluir en un resultado de la consulta. Esto permite elegir
cualquier combinación de campos disponibles en la consulta, en cualquier orden, sin definir un tipo de
datos con nombre para el elemento.
El tipo anónimo lo construye dinámicamente el compilador. El nombre del tipo es asignado por el
compilador, y podría cambiar con cada nueva compilación. Por tanto, el nombre no se puede utilizar
directamente. Los tipos anónimos se inicializan de la manera siguiente:
' Outside a query.
Dim product = New With {.Name = "paperclips", .Price = 1.29}
' Inside a query.
' You can use the existing member names of the selected fields, as was
' shown previously in the Query Expressions section of this topic.
Dim londonCusts1 = From cust In customers Where cust.City = "London" _
Select cust.Name, cust.Phone
' Or you can specify new names for the selected fields.
Dim londonCusts2 = From cust In customers Where cust.City = "London" _
Select CustomerName = cust.Name, CustomerPhone = cust.Phone
Métodos de extensión
Los métodos de extensión permiten agregar métodos a un tipo de datos o a una interfaz desde fuera de
la definición. Esta característica permite agregar nuevos métodos a un tipo existente sin tener que
modificar el tipo. Los operadores de consulta estándar son un conjunto de métodos de extensión que
proporcionan funcionalidad de consulta LINQ para cualquier tipo que implementa IEnumerable<(Of
<(T>)>). Otras extensiones a IEnumerable<(Of <(T>)>) incluyen Count, Union y Intersect.
Por ejemplo, el código siguiente define una consulta que devuelve todos los alumnos de último curso de
una lista de alumnos.
Dim seniorsQuery = From stdnt In students Where stdnt.Year = "Senior" _
Select stdnt
La definición de la consulta se compila en código similar al del ejemplo siguiente, el cual utiliza dos
expresiones lambda para especificar los argumentos de Where y Select.
Dim seniorsQuery2 = students .Where(Function(st) st.Year = "Senior") _
.Select(Function(s) s)
Cualquiera de las versiones se puede ejecutar mediante un bucle For Each:
For Each senior In seniorsQuery
Console.WriteLine(senior.Last & ", " & senior.First)
Next
El tipo de la variable de rango de la consulta debe ser compatible con el tipo de los elementos del origen
de datos. El tipo de la variable de consulta debe ser compatible con el elemento de secuencia definido en
la cláusula Select. Finalmente, el tipo de los elementos de secuencia también debe ser compatible con el
tipo de la variable de control del bucle que se utiliza en la instrucción For Each que ejecuta la consulta.
Este establecimiento inflexible de tipos facilita la identificación de los errores de tipos en tiempo de
compilación.
Visual Basic 2008 simplifica el establecimiento inflexible de tipos al implementar la inferencia de tipo de
variable local, lo que también se conoce como tipo implícito. Esta característica se utiliza en el ejemplo
anterior y, como observará, se utiliza en los ejemplos y la documentación de LINQ. En Visual Basic, la
inferencia de tipo de variable local se consigue simplemente utilizando una instrucción Dim sin una
cláusula As. En el ejemplo siguiente, city tiene establecimiento inflexible de tipos como cadena.
Dim city = "Seattle"
Nota:
La inferencia de tipo de variable local sólo funciona cuando Option Infer se establece en On.
Sin embargo, aun cuando se utiliza la inferencia de tipo de variable local en una consulta, las mismas
relaciones de tipo se encuentran entre las variables del origen de datos, la variable de consulta y el bucle
de ejecución de la consulta. Es útil conocer los fundamentos de estas relaciones de tipos para escribir
consultas LINQ o trabajar con los ejemplos y ejemplos de código de la documentación.
En el ejemplo siguiente se muestra una operación de consulta LINQ que devuelve una secuencia de
elementos seleccionados de los datos de origen. El origen, names, contiene una matriz de cadenas y el
resultado de la consulta es una secuencia que contiene cadenas que empiezan por la letra M.
Dim names = New String() {"John", "Rick", "Maggie", "Mary"}
Dim mNames = From name In names Where name.IndexOf("M") = 0 Select name
For Each nm In mNames
Console.WriteLine(nm)
Next
Es equivalente al código siguiente, pero es mucho más corto y más fácil de escribir. En Visual Basic, se
prefiere la inferencia de tipo de variable local en las consultas.
Dim names2() As String = {"John", "Rick", "Maggie", "Mary"}
Dim mNames2 As IEnumerable(Of String) = From name As String In names _
Where name.IndexOf("M") = 0 Select name
For Each nm As String In mNames
Console.WriteLine(nm)
Next
En los dos ejemplos de código anteriores existen las siguientes relaciones, se determinen los tipos de
forma implícita o explícita.
1. El tipo de los elementos del origen de datos, names, es el tipo de la variable de rango, name, en
la consulta.
2. El tipo de objeto seleccionado, name, determina el tipo de la variable de consulta, mNames. Aquí
name es una cadena, por lo que la variable de consulta es IEnumerable(Of String) en Visual Basic.
3. La consulta definida en mNames se ejecuta en el bucle For Each El bucle recorre en iteración el
resultado de ejecutar la consulta. Dado que mNames, cuando se ejecuta, devuelve una secuencia
de cadenas, la variable de iteración del bucle, nm, también es una cadena.
1. El tipo de los elementos del origen de datos, customers, es el tipo de la variable de rango, cust,
en la consulta. En este ejemplo, ese tipo es Customer.
2. La instrucción Select devuelve la propiedad Name de cada objeto Customer en lugar del objeto
completo. Dado que Name es una cadena, la variable de consulta, custNames, será de nuevo
IEnumerable(Of String), no Customer.
3. Dado que custNames representa una secuencia de cadenas, la variable de iteración del bucle For
Each, custName, debe ser una cadena.
Sin la inferencia de tipo de variable local, el ejemplo anterior sería más difícil de escribir y de entender,
como demuestra el ejemplo siguiente.
' Method GetTable returns a table of Customer objects.
Dim customers As Table(Of Customer) = db.GetTable(Of Customer)()
Dim custNames As IEnumerable(Of String) = From cust As Customer In
customers Where cust.City = "London" Select cust.Name
For Each custName As String In custNames
Console.WriteLine(custName)
Next
Consultas que requieren tipos anónimos
En el ejemplo siguiente se muestra una situación más compleja. En el ejemplo anterior, no era apropiado
especificar explícitamente los tipos de todas las variables. En este ejemplo, no se puede. En lugar de
seleccionar los elementos Customer completos del origen de datos o un campo único de cada elemento,
la cláusula Select de esta consulta devuelve dos propiedades del objeto Customer original: Name y City.
Como respuesta a la cláusula Select, el compilador define un tipo anónimo que contiene esas dos
propiedades. El resultado de ejecutar nameCityQuery en el bucle For Each es una colección de instancias
del nuevo tipo anónimo. Dado que el tipo anónimo no tiene ningún nombre utilizable, no puede
especificar explícitamente el tipo de nameCityQuery o custInfo. Es decir, con un tipo anónimo, no hay
ningún nombre de tipo que se pueda utilizar en lugar de String en IEnumerable(Of String).
' Method GetTable returns a table of Customer objects.
Dim customers = db.GetTable(Of Customer)()
Dim nameCityQuery = From cust In customers Where cust.City = "London" _
Select cust.Name, cust.City
For Each custInfo In nameCityQuery
Console.WriteLine(custInfo.Name)
Next
Aunque no es posible especificar los tipos de todas las variables en el ejemplo anterior, las relaciones
siguen siendo las mismas.
1. El tipo de los elementos del origen de datos es de nuevo el tipo de la variable de rango de la
consulta. En este ejemplo, cust es una instancia de Customer.
2. Dado que la instrucción Select genera un tipo anónimo, el tipo de la variable de consulta,
nameCityQuery, debe declararse implícitamente como un tipo anónimo. Un tipo anónimo no tiene
un nombre utilizable y, por consiguiente, no se puede especificar explícitamente.
3. El tipo de la variable de iteración del bucle For Each es el tipo anónimo creado en el paso 2.
Dado que no tiene un nombre utilizable, el tipo de la variable de iteración del bucle se debe
determinar implícitamente.
Para utilizar LINQ a XML o LINQ a DataSet en cualquier lenguaje, deberá agregar manualmente los
espacios de nombres y las referencias como se describe en las secciones siguientes.
Si está actualizando un proyecto creado con una versión anterior de Visual Studio, es posible que tenga
que proporcionar éstas u otras referencias relacionadas con LINQ manualmente y configurar el proyecto,
también manualmente, de manera que esté orientado a la versión 3.5 de .NET Framework.
Nota:
Si realiza la compilación desde un símbolo del sistema, debe hacer referencia manualmente a las DLL
relacionadas con LINQ ubicadas en unidad:\Archivos de programa\Reference
Assemblies\Microsoft\Framework\v3.5.
1. En Visual Studio, abra un proyecto de Visual Basic o C# creado en Visual Studio 2005 y siga las
indicaciones para convertirlo en proyecto de Visual Studio 2008.
3. Si es un proyecto de Visual Basic, haga clic en el menú Proyecto y, a continuación, haga clic en
Propiedades.
1. En un proyecto de Visual Basic o C#, haga clic en el menú Proyecto y, a continuación, haga clic
en Agregar referencia.
2. En el cuadro de diálogo Agregar referencia, haga clic en la ficha .NET, desplácese hasta el
archivo System.Core.dll y, a continuación, haga clic en él. Haga clic en Aceptar.
3. Agregue una directiva using o una instrucción Imports para System.Linq a su proyecto o
archivo de código fuente.
Si ya tiene una referencia a System.Core.dll, agregue una directiva using o una instrucción
Imports para System.Linq.Expressions.
1. Si es necesario, siga los pasos antes descritos para agregar una referencia a System.Core.dll y
una directiva using o una instrucción Imports para System.Linq.
Nota:
1. Si es necesario, siga los pasos antes descritos para agregar una referencia a System.Core.dll y
una directiva using o una instrucción Imports para System.Linq.
3. Agregue una directiva using o una instrucción Imports para System.Data.Linq o uno de los
otros espacios de nombres de System.Data.Linq, en función de los requisitos de su proyecto.
1. Si es necesario, siga los pasos antes descritos para agregar una referencia a System.Core.dll y
una directiva using o una instrucción Imports para System.Linq.
3. Agregue una directiva using o una instrucción Imports para System.Data y, opcionalmente,
para System.Data.Common o System.Data.SqlClient, en función de cómo se conecte a la base de
datos.
Tanto el editor de código de C# como el de Visual Basic admiten LINQ ampliamente, con la nueva
funcionalidad IntelliSense y de formato.
Hay dos conjuntos de operadores de consulta estándar de LINQ, uno que funciona sobre objetos de tipo
IEnumerable<(Of <(T>)>) y otro que funciona sobre objetos de tipo IQueryable<(Of <(T>)>). Los
métodos que constituyen cada conjunto son miembros estáticos de las clases Enumerable y Queryable,
respectivamente. Se definen como métodos de extensión del tipo sobre el que operan. Esto significa que
se pueden llamar utilizando sintaxis del método estático o sintaxis del método de instancia.
Además, varios métodos de operador de consulta estándar funcionan con tipos distintos de los que se
basan en IEnumerable<(Of <(T>)>) o IQueryable<(Of <(T>)>). El tipo Enumerable define dos de esos
métodos, que operan sobre objetos de tipo IEnumerable. Estos métodos, Cast<(Of
<(TResult>)>)(IEnumerable) y OfType<(Of <(TResult>)>)(IEnumerable), permiten que una colección
no parametrizada, o no genérica, pueda ser consultada en el modelo de LINQ. Esto lo consiguen creando
una colección de objetos con establecimiento inflexible de tipos. La clase Queryable define dos métodos
similares, Cast<(Of <(TResult>)>)(IQueryable) y OfType<(Of <(TResult>)>)(IQueryable), que operan
sobre objetos de tipo Queryable.
En el caso de los métodos que operan sobre colecciones en memoria, es decir, aquellos métodos que
extienden IEnumerable<(Of <(T>)>), el objeto enumerable devuelto captura los argumentos que se
pasaron al método. Cuando se enumera ese objeto, se emplea la lógica del operador de consulta y se
devuelven los resultados de la consulta.
Las llamadas a métodos de consulta se pueden encadenar juntas en una sola consulta, lo cual permite
hacer consultas arbitrariamente complejas.
El ejemplo de código siguiente muestra cómo se pueden utilizar los operadores de consulta estándar
para obtener información sobre una secuencia.
Dim sentence As String = "the quick brown fox jumps over the lazy dog"
' Split the string into individual words to create a collection.
Dim words As String() = sentence.Split(" "c)
Dim query = From word In words Group word.ToUpper() By word.Length Into
gr = Group Order By Length Select Length, GroupedWords = gr
Dim output As New System.Text.StringBuilder
For Each obj In query
output.AppendLine(String.Format("Words of length {0}:", obj.Length))
For Each word As String In obj.GroupedWords
output.AppendLine(word)
Next
Next
'Display the output
MsgBox(output.ToString())
' This code example produces the following output:
' Words of length 3:
' THE
' FOX
' THE
' DOG
' Words of length 4:
' OVER
' LAZY
' Words of length 5:
' QUICK
' BROWN
' JUMPS
Sintaxis de las expresiones de consulta
Algunos de los operadores de consulta estándar utilizados con mayor frecuencia tienen una sintaxis de
palabras clave dedicadas del lenguaje C# y Visual Basic que permite llamarlos como parte de una
expresión de consulta.
consulta, más legible que su equivalente basado en método. Las cláusulas de las expresiones de consulta
se traducen en llamadas a los métodos de consulta en tiempo de compilación.
Sintaxis de las
Sintaxis de las expresiones de
expresiones de consulta de
Método consulta de C# Visual Basic
Modos de ejecución
Inmediata
Ejecución inmediata significa que el origen de datos se lee y la operación se realiza en el punto del
código donde se declara la consulta. Todos los operadores de consulta estándar que devuelven un
resultado único no enumerable se ejecutan de forma inmediata.
Aplazada
Ejecución aplazada significa que la operación no se realiza en el punto del código donde se declara la
consulta. La operación se realiza solamente cuando se enumera la variable de consulta, por ejemplo,
cuando se utiliza una instrucción foreach (For Each en Visual Basic). Esto significa que los resultados de
la ejecución de la consulta dependen del contenido del origen de datos cuando se ejecuta la consulta, no
cuando ésta se define. Si la variable de consulta se enumera varias veces, pueden obtenerse resultados
distintos en cada ocasión. Casi todos los operadores de consulta estándar cuyo tipo de valor devuelto es
IEnumerable<(Of <(T>)>) o IOrderedEnumerable<(Of <(TElement>)>) se ejecutan de forma aplazada.
Los operadores de consulta que utilizan la ejecución aplazada pueden clasificarse a su vez en operadores
con o sin transmisión por secuencias.
Tabla de clasificación
En la tabla siguiente se clasifica cada método de operador de consulta estándar según su método de
ejecución.
Nota:
Ejecución
aplazada Ejecución
con aplazada sin
transmisión transmisión
Operador de consulta Ejecución por por
estándar Tipo de valor devuelto inmediata secuencias secuencias
Aggregate TSource X
Any Boolean X
AsEnumerable<(Of IEnumerable<(Of X
<(TSource>)>) <(T>)>)
Concat<(Of IEnumerable<(Of X
<(TSource>)>) <(T>)>)
Contains Boolean X
Count Int32 X
DefaultIfEmpty IEnumerable<(Of X
<(T>)>)
Distinct IEnumerable<(Of X
<(T>)>)
ElementAt<(Of TSource X
<(TSource>)>)
ElementAtOrDefault<(Of TSource X
<(TSource>)>)
Empty<(Of IEnumerable<(Of X
<(TResult>)>) <(T>)>)
Except IEnumerable<(Of X X
<(T>)>)
First TSource X
FirstOrDefault TSource X
GroupBy IEnumerable<(Of X
<(T>)>)
GroupJoin IEnumerable<(Of X X
<(T>)>)
Intersect IEnumerable<(Of X X
<(T>)>)
Join IEnumerable<(Of X X
<(T>)>)
Last TSource X
LastOrDefault TSource X
LongCount Int64 X
OfType<(Of IEnumerable<(Of X
<(TResult>)>) <(T>)>)
OrderBy IOrderedEnumerable<(Of X
<(TElement>)>)
OrderByDescending IOrderedEnumerable<(Of X
<(TElement>)>)
Range IEnumerable<(Of X
<(T>)>)
Repeat<(Of IEnumerable<(Of X
<(TResult>)>) <(T>)>)
Reverse<(Of IEnumerable<(Of X
<(TSource>)>) <(T>)>)
Select IEnumerable<(Of X
<(T>)>)
SelectMany IEnumerable<(Of X
<(T>)>)
SequenceEqual Boolean X
Single TSource X
SingleOrDefault TSource X
Skip<(Of IEnumerable<(Of X
<(TSource>)>) <(T>)>)
SkipWhile IEnumerable<(Of X
<(T>)>)
Take<(Of IEnumerable<(Of X
<(TSource>)>) <(T>)>)
TakeWhile IEnumerable<(Of X
<(T>)>)
ThenBy IOrderedEnumerable<(Of X
<(TElement>)>)
ThenByDescending IOrderedEnumerable<(Of X
<(TElement>)>)
Union IEnumerable<(Of X
<(T>)>)
Where IEnumerable<(Of X
<(T>)>)
La ilustración siguiente muestra los resultados de una operación de ordenación alfabética en una
secuencia de caracteres.
Los métodos de operador de consulta estándar que ordenan los datos se enumeran en la sección
siguiente.
Métodos
Sintaxis de
Sintaxis de las
las expresiones
expresiones de consulta
Nombre del de consulta de Visual
método Description de C# Basic Más información
Los métodos de operador de consulta estándar que realizan operaciones de conjuntos se enumeran en la
sección siguiente.
Métodos
Except
La ilustración siguiente muestra el comportamiento de Enumerable..::.Except. La secuencia devuelta
contiene sólo los elementos de la primera secuencia de entrada que no están en la segunda secuencia de
entrada.
Intersect
La ilustración siguiente muestra el comportamiento de Enumerable..::.Intersect. La secuencia devuelta
contiene los elementos que son comunes a las dos secuencias de entrada.
Unión
La ilustración siguiente muestra una operación de unión de dos secuencias de caracteres. La secuencia
devuelta contiene los elementos únicos de las dos secuencias de entrada.
La ilustración siguiente muestra los resultados de filtrar una secuencia de caracteres. El predicado de la
operación de filtrado especifica que el carácter debe ser 'A'.
Los métodos de operador de consulta estándar que realizan la selección se enumeran en la sección
siguiente.
Métodos
La ilustración siguiente muestra dos operaciones cuantificadoras diferentes en dos secuencias de origen
diferentes. La primera operación pregunta si uno o varios de los elementos son el carácter 'A', y el
resultado es true. La segunda operación pregunta si todos los elementos son el carácter 'A', y el
resultado es true.
Los métodos de operador de consulta estándar que realizan operaciones cuantificadoras se enumeran en
la sección siguiente.
Métodos
Sintaxis de las
Nombre Sintaxis de las expresiones de
del expresiones de consulta de
método Description consulta de C# Visual Basic Más información
End Class
Sub All()
Dim barley As New Pet With {.Name = "Barley", .Age = 4}
Dim boots As New Pet With {.Name = "Boots", .Age = 1}
Dim whiskers As New Pet With {.Name = "Whiskers", .Age = 6}
Dim bluemoon As New Pet With {.Name = "Blue Moon", .Age = 9}
Dim daisy As New Pet With {.Name = "Daisy", .Age = 3}
Dim charlotte As New Person With {.Name = "Charlotte", .Pets = _
New Pet() {barley, boots}}
Dim arlene As New Person With {.Name = "Arlene", .Pets = _
New Pet() {whiskers}}
Dim rui As New Person With {.Name = "Rui", .Pets = _
New Pet() {bluemoon, daisy}}
' Create the list of Person objects that will be queried.
Dim people As New System.Collections.Generic.List(Of Person)
(New Person() {charlotte, arlene, rui})
Dim query = From pers In people _
Where (Aggregate pt In pers.Pets Into All(pt.Age > 2)) _
Select pers.Name
Dim sb As New System.Text.StringBuilder()
For Each name As String In query
sb.AppendLine(name)
Next
' Display the results.
MsgBox(sb.ToString())
' This code produces the following output:
' Arlene
' Rui
End Sub
En el ejemplo siguiente se utiliza la cláusula Aggregate y el método de extensión Any para devolver de
una colección las personas que tienen por lo menos un animal doméstico que es mayor que una edad
especificada.
Class Person
Private _name As String
Private _pets As Pet()
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Public Property Pets() As Pet()
Get
Return _pets
End Get
Set(ByVal value As Pet())
_pets = value
End Set
End Property
End Class
Class Pet
Private _name As String
Private _age As Integer
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Public Property Age() As Integer
Get
Return _age
End Get
Set(ByVal value As Integer)
_age = value
End Set
End Property
End Class
Sub Any()
Dim barley As New Pet With {.Name = "Barley", .Age = 4}
Dim boots As New Pet With {.Name = "Boots", .Age = 1}
Dim whiskers As New Pet With {.Name = "Whiskers", .Age = 6}
Dim bluemoon As New Pet With {.Name = "Blue Moon", .Age = 9}
Dim daisy As New Pet With {.Name = "Daisy", .Age = 3}
Dim charlotte As New Person With {.Name = "Charlotte", .Pets = _
New Pet() {barley, boots}}
Dim arlene As New Person With {.Name = "Arlene", .Pets = _
New Pet() {whiskers}}
Dim rui As New Person With {.Name = "Rui", .Pets = _
New Pet() {bluemoon, daisy}}
' Create the list of Person objects that will be queried.
Dim people As New System.Collections.Generic.List(Of Person)
(New Person() {charlotte, arlene, rui})
Dim query = From pers In people Where (Aggregate pt In pers.Pets Into
Any(pt.Age > 7)) Select pers.Name
Dim sb As New System.Text.StringBuilder()
For Each name As String In query
sb.AppendLine(name)
Next
' Display the results.
MsgBox(sb.ToString())
' This code produces the following output:
' Rui
End Sub
Los métodos de operador de consulta estándar que realizan proyección se enumeran en la siguiente
sección.
Métodos
Sintaxis de
Sintaxis de las
las expresiones
Nombre expresiones de consulta
del de consulta de Visual
método Descripción de C# Basic Más información
Select word
Dim sb As New System.Text.StringBuilder()
For Each str As String In query
sb.AppendLine(str)
Next
' Display the output.
MsgBox(sb.ToString())
' This code produces the following output:
' an
' apple
' a
' day
' the
' quick
' brown
' fox
Comparación entre Select y SelectMany
El trabajo de Select() y SelectMany() es generar un valor de resultado (o valores) a partir de los
valores de origen. Select() genera un valor de resultado para cada valor de origen. El resultado total es,
por tanto, una colección que tiene el mismo número de elementos que la colección de origen. En
contraste, SelectMany() genera un resultado total único que contiene subcolecciones concatenadas
procedentes de cada valor de origen. La función de transformación que se pasa como un argumento a
SelectMany() debe devolver una secuencia enumerable de valores para cada valor de origen. Estas
secuencias enumerables se concatenan entonces mediante SelectMany() para crear una sola secuencia
mayor.
Las dos ilustraciones siguientes muestran la diferencia conceptual entre las acciones de estos dos
métodos. En cada caso, suponga que la función de selector (transformación) selecciona la matriz de
flores de cada valor de origen.
Esta ilustración describe cómo Select() devuelve una colección que tiene el mismo número de
elementos que la colección de origen.
Esta ilustración describe cómo SelectMany() concatena la secuencia intermedia de matrices en un valor
de resultado final que contiene cada valor de cada matriz intermedia.
Ejemplo de código
En el siguiente ejemplo se compara el comportamiento de Select() y SelectMany(). El código crea un
"ramo" de flores tomando los primeros dos elementos de cada lista de nombres de flores de la colección
de origen. En este ejemplo, el "valor único" que la función de transformación Select<(Of <(TSource,
TResult>)>)(IEnumerable<(Of <(TSource>)>), Func<(Of <(TSource, TResult>)>)) utiliza es en sí
mismo una colección de valores. Esto requiere el bucle foreach (For Each en Visual Basic) adicional
para enumerar cada cadena de cada subsecuencia.
Class Bouquet
Public Flowers As List(Of String)
End Class
Sub SelectVsSelectMany()
Dim bouquets As New List(Of Bouquet)(New Bouquet() { _
New Bouquet With {.Flowers = New List(Of String)(New String()
{"sunflower", "daisy", "daffodil", "larkspur"})}, _
New Bouquet With {.Flowers = New List(Of String)(New String()
{"tulip", "rose", "orchid"})}, _
New Bouquet With {.Flowers = New List(Of String)(New String()
{"gladiolis", "lily", "snapdragon", "aster", "protea"})}, _
New Bouquet With {.Flowers = New List(Of String)(New String()
{"larkspur", "lilac", "iris", "dahlia"})}})
Dim output As New System.Text.StringBuilder
' Select()
Dim query1 = bouquets.Select(Function(b) b.Flowers)
output.AppendLine("Using Select():")
For Each flowerList In query1
For Each str As String In flowerList
output.AppendLine(str)
Next
Next
' SelectMany()
Dim query2 = bouquets.SelectMany(Function(b) b.Flowers)
output.AppendLine(vbCrLf & "Using SelectMany():")
For Each str As String In query2
output.AppendLine(str)
Next
MsgBox(output.ToString())
End Sub
La ilustración siguiente muestra los resultados de tres operaciones de partición diferentes en una
secuencia de caracteres. La primera operación devuelve los tres primeros elementos de la secuencia. La
segunda operación omite los tres primeros elementos y devuelve los restantes. La tercera operación
omite los dos primeros elementos de la secuencia y devuelve los tres siguientes.
Los métodos de operador de consulta estándar que realizan particiones de las secuencias se enumeran
en la sección siguiente.
Operadores
Sintaxis de
las Sintaxis de las
Nombre expresiones expresiones
de de consulta de consulta de
operador Description de C# Visual Basic Más información
La combinación es una operación importante en las consultas orientadas a orígenes de datos cuyas
relaciones mutuas no se pueden seguir directamente. En la programación orientada a objetos, podría ser
una correlación no modelada entre objetos, como la dirección inversa de una relación unidireccional. Una
relación unidireccional es, por ejemplo, la que existe cuando una clase Customer tiene una propiedad de
tipo City pero la clase City no tiene ninguna propiedad que sea una colección de objetos Customer. Si
tiene una lista de objetos City y desea buscar todos los clientes de cada ciudad, podría utilizar una
operación de combinación para encontrarlos.
Los métodos de combinación proporcionados en el marco de trabajo LINQ son Join y GroupJoin. Estos
métodos realizan combinaciones de igualdad, es decir, combinaciones que buscan las coincidencias entre
dos orígenes de datos según la igualdad de sus claves. (Para la comparación, Transact-SQL admite
operadores de combinación que no sean el de igualdad, "equals"; por ejemplo, el operador "menor
que".) En términos de bases de datos relacionales, Join implementa una combinación interna, un tipo de
combinación en la que se devuelven sólo los objetos que tienen una coincidencia en el otro conjunto de
datos. El método GroupJoin no tiene ningún equivalente directo para las bases de datos relacionales,
pero implementa un supraconjunto de combinaciones internas y combinaciones externas izquierdas. Una
combinación externa izquierda es una combinación que devuelve cada uno de los elementos del primer
origen de datos (izquierda), aun cuando no tiene ningún elemento correlacionado en el otro origen de
datos.
La ilustración siguiente muestra una vista conceptual de dos conjuntos y los elementos de esos conjuntos
que están incluidos en una combinación interna o una combinación externa izquierda.
Métodos
Sintaxis de
las Sintaxis de las
Nombre expresiones expresiones
del de consulta de consulta de
método Description de C# Visual Basic Más información
La ilustración siguiente muestra los resultados de agrupar una secuencia de caracteres. La clave de cada
grupo es el carácter.
Los métodos de operador de consulta estándar que agrupan los elementos de datos se enumeran en la
sección siguiente.
Métodos
Los métodos de operador de consulta estándar que realizan la generación se enumeran en la sección
siguiente.
Métodos
Sintaxis de
Sintaxis de las
las expresiones
expresiones de consulta
Nombre del de consulta de Visual
método Description de C# Basic Más información
Métodos
Sintaxis de
Sintaxis de las
las expresiones
expresiones de consulta
Nombre del de consulta de Visual
método Description de C# Basic Más información
a par.
Los métodos de operador de consulta estándar que realizan operaciones de elementos se enumeran en la
sección siguiente.
Métodos
Sintaxis de
Sintaxis de las
las expresiones
expresiones de consulta
Nombre del de consulta de Visual
método Description de C# Basic Más información
Las operaciones de conversión en las consultas LINQ son útiles en varios sentidos. A continuación se
ofrecen algunos ejemplos:
Métodos
En la tabla siguiente se enumeran los métodos de operador de consulta estándar que realizan
conversiones de tipos de datos.
Los métodos de conversión de esta tabla cuyo nombre comienza por "As" cambian el tipo estático de la
colección de origen pero no lo enumeran. Los métodos cuyos nombres empiezan por "To" enumeran la
colección de origen y colocan los elementos en el tipo de colección correspondiente.
Sintaxis de
Sintaxis de las
las expresiones
expresiones de consulta
Nombre del de consulta de Visual
método Description de C# Basic Más información
función del
selector de
claves. Este
método fuerza la
ejecución de la
consulta.
Los métodos de operador de consulta estándar que realizan la concatenación se enumeran en la sección
siguiente.
Métodos
Sintaxis de las
Nombre Sintaxis de las expresiones de
del expresiones de consulta de
método Description consulta de C# Visual Basic Más información
La ilustración siguiente muestra los resultados de dos operaciones de agregación diferentes con una
secuencia de números. La primera operación suma los números. La segunda operación devuelve el valor
máximo de la secuencia.
Los métodos de operador de consulta estándar que realizan operaciones de agregación se enumeran en
la sección siguiente.
Métodos
de consulta de consulta
de C# de Visual
Basic
que genera un árbol de expresión en tiempo de ejecución. El proveedor de la consulta puede entonces
recorrer la estructura de datos del árbol de expresión y traducirla en un lenguaje de consulta apropiado
para el origen de datos.
Los árboles de expresión también se utilizan en LINQ para representar expresiones lambda asignadas a
variables de tipo Expression<(Of <(TDelegate>)>).
Los árboles de expresión también se pueden utilizar para crear consultas dinámicas de LINQ. También
podría utilizar árboles de expresión para crear un proveedor de LINQ.
Ejemplo
El ejemplo siguiente muestra cómo utilizar árboles de expresión para construir una consulta contra un
origen de datos IQueryable y, a continuación, ejecutar dicha consulta. El código genera un árbol de
expresión para representar la consulta siguiente:
Consulta en C#
' Compose the expression tree that represents the parameter to the
predicate.
Dim pe As ParameterExpression = Expression.Parameter(GetType(String),
"company")
' ***** Where(Function(company) company.ToLower() = "coho winery" OrElse
company.Length > 16) *****
' Create an expression tree that represents the expression:
company.ToLower() = "coho winery".
Dim left As Expression = Expression.Call(pe,
GetType(String).GetMethod("ToLower", System.Type.EmptyTypes))
Dim right As Expression = Expression.Constant("coho winery")
Dim e1 As Expression = Expression.Equal(left, right)
' Create an expression tree that represents the expression:
company.Length > 16.
left = Expression.Property(pe, GetType(String).GetProperty("Length"))
right = Expression.Constant(16, GetType(Integer))
Dim e2 As Expression = Expression.GreaterThan(left, right)
' Combine the expressions to create an expression tree that represents
the
' expression: company.ToLower() = "coho winery" OrElse company.Length >
16).
Dim predicateBody As Expression = Expression.OrElse(e1, e2)
' Create an expression tree that represents the expression:
' queryableData.Where(Function(company) company.ToLower() = "coho winery"
OrElse company.Length > 16)
Dim whereCallExpression As MethodCallExpression = Expression.Call( _
GetType(Queryable), "Where", _
New Type(){queryableData.ElementType},queryableData.Expression, _
Expression.Lambda(Of Func(Of String, Boolean))(predicateBody,
New ParameterExpression() {pe}))
' ***** End Where *****
' ***** OrderBy(Function(company) company) *****
' Create an expression tree that represents the expression:
' whereCallExpression.OrderBy(Function(company) company)
Dim orderByCallExpression As MethodCallExpression = Expression.Call( _
GetType(Queryable), "OrderBy", _
New Type() {queryableData.ElementType,
queryableData.ElementType}, whereCallExpression, _
Expression.Lambda(Of Func(Of String, String))(pe, New
ParameterExpression() {pe}))
' ***** End OrderBy *****
' Create an executable query from the expression tree.
Dim results As IQueryable(Of String) =
queryableData.Provider.CreateQuery(Of String)(orderByCallExpression)
' Enumerate the results.
For Each company As String In results
Console.WriteLine(company)
Next
' This code produces the following output:
' Blue Yonder Airlines
Crear métodos de operador de consulta estándar como Where y Select que extienden un tipo
para habilitar las consultas personalizadas de LINQ para ese tipo.
Crear un proveedor para el origen de datos que aproveche una tecnología de LINQ existente.
Este tipo de proveedor permitiría no sólo las consultas, sino también las operaciones de
inserción, actualización y eliminación, así como la asignación para tipos definidos por el usuario.
Datos remotos
La mejor opción para permitir las consultas de LINQ para un origen de datos remoto consiste en
implementar la interfaz IQueryable<(Of <(T>)>). Sin embargo, esto es diferente de extender un
proveedor como LINQ a SQL para un origen de datos. En Visual Studio 2008, no existen modelos de
proveedor para extender tecnologías de LINQ existentes, como LINQ a SQL, a otros tipos de origen de
datos.
Un proveedor de IQueryable menos complejo podría comunicar con un solo método de un servicio web,
como es el caso del proveedor que se crea en el tema Tutorial: Crear un proveedor LINQ IQueryable.
Este tipo de proveedor es muy específico, ya que espera información específica en las consultas que
administra. Posee un sistema de tipos cerrado, y expone quizá un único tipo de resultado. La mayor
parte de la ejecución de la consulta ocurre localmente, por ejemplo, utilizando las implementaciones
Enumerable de los operadores de consulta estándar. Un proveedor menos complejo podría examinar sólo
una expresión de llamada a método en el árbol de expresión que representa la consulta, y dejaría que la
lógica restante de la consulta se procesara en otra parte.
Un proveedor IQueryable de complejidad media podría destinarse a un origen de datos que tuviera un
lenguaje de consulta parcialmente expresivo. Si se destina a un servicio web, podría comunicarse con
más de un método del servicio web y seleccionar el método que va a llamar en función de la pregunta
que plantea la consulta. Un proveedor de complejidad media tendría un sistema de tipos más amplio que
un proveedor simple, pero todavía sería un sistema de tipos fijo. Por ejemplo, el proveedor podría
exponer tipos que tienen relaciones uno a varios y que se pueden recorrer, pero no proporcionaría
tecnología de asignación para tipos definidos por el usuario.
Un proveedor IQueryable complejo, tal como el proveedor LINQ a SQL, podría traducir consultas de
LINQ completas a un lenguaje de consulta más expresivo, como SQL. Un proveedor complejo es más
general que un proveedor menos complejo, ya que puede manejar una variedad más amplia de
preguntas en la consulta. También posee un sistema de tipos abierto y, por tanto, debe contener una
amplia infraestructura para utilizar tipos definidos por el usuario. El desarrollo de un proveedor complejo
requiere un esfuerzo significativo.
6. LINQ a Objectos
El término "LINQ a Objectos" hace referencia al uso de consultas LINQ con cualquier colección
IEnumerable o IEnumerable<(Of <(T>)>) directamente, sin utilizar ninguna API o proveedor LINQ
intermedio, como LINQ a SQL o LINQ a XML. Puede utilizar LINQ para consultar cualquier colección
enumerable, como List<(Of <(T>)>), Array o Dictionary<(Of <(TKey, TValue>)>). La colección puede
estar definida por el usuario o ser devuelta por una API de .NET Framework.
Básicamente, LINQ a Objects representa una nueva forma de ver las colecciones. De la manera
convencional, es necesario escribir bucles foreach complejos que especifican cómo recuperar los datos
de una colección. Según LINQ, se escribe código declarativo que describe lo que se desea recuperar.
Además, las consultas LINQ ofrecen tres ventajas principales respecto a los bucles foreach tradicionales:
En general, cuanto más compleja sea la operación que se deba realizar con los datos, observará un
número mayor de ventajas al utilizar LINQ en lugar de las técnicas de iteración convencionales.
El propósito de esta sección es mostrar el planteamiento de LINQ con unos cuantos ejemplos específicos.
No pretende ser exhaustiva.
También puede utilizar las técnicas descritas en esta sección para transformar datos de texto
semiestructurados en XML.
Ejemplo
Class CountWords
Shared Sub Main()
Dim text As String = "Historically, the world of data and the
world of objects" & _
" have not been well integrated. Programmers work in C#
or Visual Basic" & _
" and also in SQL or XQuery. On the one side are
concepts such as classes," & _
" objects, fields, inheritance, and .NET Framework
APIs. On the other side" & _
" are tables, columns, rows, nodes, and separate
languages for dealing with" & _
Ejemplo
Class FindSentences
Shared Sub Main()
Dim text As String = "Historically, the world of data and the
world of objects " & _
"have not been well integrated. Programmers work in C# or Visual
Basic " & _
"and also in SQL or XQuery. On the one side are concepts such as
classes, " & _
En la llamada a Split, los signos de puntuación se utilizan como separadores para quitarlos de la cadena.
De lo contrario, podría obtener una cadena "Historically," que no coincidiría con "Historically" en la matriz
wordsToMatch. Puede que sea necesario utilizar separadores adicionales, dependiendo de los tipos de
puntuación que contenga el texto de origen.
Ejemplo
En el ejemplo siguiente se consulta una cadena para determinar el número de dígitos numéricos que
contiene. Observe que la consulta se "reutiliza" después de que se ejecuta la primera vez. Esto es posible
porque la propia consulta no almacena ningún resultado real.
Class QueryAString
Shared Sub Main()
' A string is an IEnumerable data source.
Dim aString As String = "ABCDE99F-J74-12-89A"
' Select only those characters that are numbers
Dim stringQuery = From ch In aString Where Char.IsDigit(ch) _
Select ch
' Execute the query
For Each c As Char In stringQuery
Console.Write(c & " ")
Next
' Call the Count method on the existing query.
Dim count As Integer = stringQuery.Count()
Console.WriteLine(System.Environment.NewLine & "Count = " & count)
' Select all characters before the first '-'
Dim stringQuery2 = aString.TakeWhile(Function(c) c <> "-")
' Execute the second query
For Each ch In stringQuery2
Console.Write(ch)
Next
Console.WriteLine(System.Environment.NewLine & "Press any key to exit")
Console.ReadKey()
End Sub
End Class
' Output:
' 9 9 7 4 1 2 8 9
' Count = 8
' ABCDE99F
Ejemplo
Class LinqRegExVB
End Function
End Class
Observe que también se puede consultar el objeto MatchCollection que se devuelve tras una búsqueda
RegEx. En este ejemplo, sólo se genera el valor de cada coincidencia en los resultados. Sin embargo,
también es posible utilizar LINQ para realizar toda clase de operaciones de filtrado, ordenación y
agrupación en esa colección. Dado que MatchCollection es una colección IEnumerable no genérica, es
necesario declarar explícitamente el tipo de la variable de rango en la consulta.
Copie los datos de scores.csv del tema Cómo: Combinar contenido de archivos no similares
(LINQ) y guárdelos en la carpeta de la solución.
Ejemplo
Class SortLines
Shared Sub Main()
Dim scores As String() =
System.IO.File.ReadAllLines("../../../scores.csv")
' Change this to any value from 0 to 4
Dim sortField As Integer = 1
Console.WriteLine("Sorted highest to lowest by field " & sortField)
' Demonstrates how to return query from a method.
' The query is executed here.
For Each str As String In SortQuery(scores, sortField)
Console.WriteLine(str)
Next
' Keep console window open in debug mode.
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
Shared Function SortQuery(ByVal source As IEnumerable(Of String),
ByVal num As Integer) As IEnumerable(Of String)
Dim scoreQuery = From line In source Let fields = line.Split(New
Char() {","}) Order By fields(num) Descending Select line
Return scoreQuery
End Function
End Class
Supongamos que las tres columnas del ejemplo siguiente representan el "apellido", "nombre" e
"identificador" de los estudiantes. Los campos están ordenados alfabéticamente por apellido de
estudiante. La consulta genera una nueva secuencia en la que la columna de identificador aparece
primero, seguida de una segunda columna que combina el nombre del estudiante y su apellido. Las
líneas se reordenan según el campo de identificador. Los resultados se guardan en un nuevo archivo sin
que se modifiquen los datos originales.
Cree un nuevo proyecto de Visual C# o Visual Basic y copie las líneas siguientes en un archivo
de texto sin formato que se denomine spreadsheet1.csv. Guarde el archivo en la carpeta de la
solución.
Adams,Terry,120
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Cesar,114
Garcia,Debra,115
Garcia,Hugo,118
Mortensen,Sven,113
O'Donnell,Claire,112
Omelchenko,Svetlana,111
Tucker,Lance,119
Tucker,Michael,122
Zabokritski,Eugene,121
Ejemplo
Class CSVFiles
Shared Sub Main()
' Create the IEnumerable data source.
Dim lines As String() =
System.IO.File.ReadAllLines("../../../spreadsheet1.csv")
' Execute the query. Put field 2 first, then
' reverse and combine fields 0 and 1 from the old field
Dim lineQuery = From line In lines Let x = line.Split(New Char()
{","}) Order By x(2) Select x(2) & ", " & (x(1) & " " & x(0))
' Execute the query and write out the new file. Note that WriteAllLines
' takes a string array, so ToArray is called on the query.
System.IO.File.WriteAllLines("../../../spreadsheet2.csv",
lineQuery.ToArray())
' Keep console window open in debug mode.
Console.WriteLine("Spreadsheet2.csv written to disk.Press key to exit")
Console.ReadKey()
End Sub
End Class
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
2. Copie estos nombres en un archivo de texto que se denomine names2.txt y guárdelo en la
carpeta de la solución. Observe que los dos archivos tienen algunos nombres en común.
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
Ejemplo
Class ConcatenateStrings
Shared Sub Main()
' Create the IEnumerable data sources.
Dim fileA As String() =
System.IO.File.ReadAllLines("../../../names1.txt")
Dim fileB As String() =
System.IO.File.ReadAllLines("../../../names2.txt")
' Simple concatenation and sort.
Dim concatQuery = fileA.Concat(fileB).OrderBy(Function(name) name)
' Pass the query variable to another function for execution
OutputQueryResults(concatQuery, "Simple concatenation and sort.
Duplicates are preserved:")
' New query. Concatenate files and remove duplicates
Dim uniqueNamesQuery = fileA.Union(fileB).OrderBy(Function(name) name)
OutputQueryResults(uniqueNamesQuery, "Union removes duplicate names:")
' New query. Find the names that occur in both files.
Dim commonNamesQuery = fileA.Intersect(fileB)
OutputQueryResults(commonNamesQuery, "Merge based on intersect: ")
' New query in three steps for better readability
' First filter each list separately
Dim nameToSearch As String = "Garcia"
Dim mergeQueryA As IEnumerable(Of String) = From name In fileA _
Let n = name.Split(New Char() {","}) _
Where n(0) = nameToSearch _
Select name
Dim mergeQueryB = From name In fileB _
Let n = name.Split(New Char() {","}) _
Where n(0) = nameToSearch _
Select name
' Create a new query to concatenate and sort results. Duplicates
' are removed in Union. Note that none of the queries actually
' executed until the call to OutputQueryResults.
Dim mergeSortQuery=mergeQueryA.Union(mergeQueryB).OrderBy
(Function(str) str)
' Now execute mergeSortQuery
OutputQueryResults(mergeSortQuery, "Concat based on partial name
match """ & nameToSearch & """ from each list:")
' Keep console window open in debug mode.
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
Shared Sub OutputQueryResults(ByVal query As IEnumerable(Of String),
ByVal message As String)
Console.WriteLine(System.Environment.NewLine & message)
For Each item As String In query
Console.WriteLine(item)
Next
Console.WriteLine(query.Count & " total names in list")
End Sub
End Class
Nota:
No intente combinar datos en memoria o datos del sistema de archivos con datos que todavía estén
en una base de datos. Este tipo de combinaciones entre dominios puede generar resultados sin
definir, dadas las distintas maneras en las que se pueden definir las operaciones de combinación para
las consultas de base de datos y otros tipos de orígenes. Existe además el riesgo de que este tipo de
operación provoque una excepción de memoria insuficiente si la cantidad de datos de la base de
datos es lo suficientemente grande. Para combinar datos de una base de datos con datos en
memoria, llame primero a ToList o ToArray en la consulta de base de datos y, a continuación,
realice la combinación con la colección devuelta.
Cree un nuevo proyecto de Visual C# o Visual Basic y copie los archivos names.csv y scores.csv
en la carpeta de la solución.
Ejemplo
En el ejemplo siguiente se muestra cómo utilizar un tipo con nombre Student para almacenar los datos
combinados de dos colecciones de cadenas en memoria que simulan datos de hoja de cálculo en formato
.csv. La primera colección de cadenas representa los nombres e identificadores de los estudiantes y la
Los orígenes de datos de estos ejemplos se inicializan con inicializadores de objeto. La consulta utiliza
una cláusula join para hallar las correspondencias entre los nombres y las puntuaciones. Como clave
externa, se usa ID. Sin embargo, en un origen el identificador (ID) es una cadena y en el otro es un
entero. Dado que join requiere una comparación de igualdad, primero es necesario extraer el
identificador de la cadena y luego convertirlo a entero. Esto se logra en las dos cláusulas let. El
identificador temporal x de la primera cláusula let almacena una matriz de tres cadenas obtenida
mediante la división de la cadena original en cada espacio. El identificador n de la segunda cláusula let
almacena el resultado de convertir la subcadena del identificador en un entero. En la cláusula select, se
usa un inicializador de objeto para crear instancias de cada nuevo objeto Student utilizando los datos de
los dos orígenes.
Si no es necesario almacenar los resultados de la consulta, los tipos anónimos pueden ser más
adecuados que los tipos con nombre. Los tipos con nombre son necesarios si los resultados de la
consulta se pasan fuera del método en el que ésta se ejecuta.
Class SplitWithGroups
Shared Sub Main()
Dim fileA As String() =
System.IO.File.ReadAllLines("../../../names1.txt")
Dim fileB As String() =
System.IO.File.ReadAllLines("../../../names2.txt")
' Concatenate and remove duplicate names based on
Dim mergeQuery As IEnumerable(Of String) = fileA.Union(fileB)
' Group the names by the first letter in the last name
Dim groupQuery = From name In mergeQuery Let n = name.Split(New
Char() {","}) Order By n(0) Group By groupKey = n(0)(0) Into
groupName = Group
' Create a new file for each group that was created
' Note that nested foreach loops are required to access
' individual items with each group.
For Each gGroup In groupQuery
Dim fileName As String = "..'..'..'testFile_" &
gGroup.groupKey & ".txt"
Dim sw As New System.IO.StreamWriter(fileName)
Console.WriteLine(gGroup.groupKey)
For Each item In gGroup.groupName
Console.WriteLine(" " & item.name)
sw.WriteLine(item.name)
Next
sw.Close()
Next
' Keep console window open in debug mode.
Console.WriteLine("Files have been written. Press any key to exit.")
Console.ReadKey()
End Sub
End Class
El programa escribe un archivo independiente para cada grupo en la misma carpeta que los archivos de
datos.
1. Copie estas líneas en un archivo denominado scores.csv y guárdelo en la misma carpeta que el
archivo de proyecto. Este archivo representa datos de hoja de cálculo. La columna 1 es el
identificador del estudiante y las columnas de la 2 a la 5 son las puntuaciones de exámenes.
111, 97, 92, 81, 60
Ejemplo
Imports System.Reflection
Imports System.IO
Imports System.Linq
Module Module1
Sub Main()
Dim file As String = "C:\Program Files\Reference
Assemblies\Microsoft\Framework\v3.5\System.Core.dll"
Dim asmbly As Assembly = Assembly.LoadFrom(file)
Dim pubTypesQuery = From type In asmbly.GetTypes() Where
type.IsPublic From method In type.GetMethods() _
Where method.ReturnType.IsArray = True _
Let name = method.ToString() Let typeName = type.ToString() _
Group name By typeName Into methodNames = Group
Console.WriteLine("Getting ready to iterate")
For Each item In pubTypesQuery
Console.WriteLine(item.methodNames)
Nota:
Observe que las consultas de esta sección no son destructivas. No se utilizan para cambiar el contenido
de los archivos o carpetas originales. Se aplica la regla de que las consultas no deberían producir efectos
adversos. Por lo general, el código (incluidas las consultas que realizan operaciones de creación,
actualización o eliminación) que se utiliza para modificar los datos de origen se debería mantener
apartado del código que se usa simplemente para consultar los datos.
Ejemplo
Module FindFileByExtension
Sub Main()
' Change the drive\path if necessary
Dim root As String = "C:\Program Files\Microsoft Visual Studio 9.0"
'Take a snapshot of the folder contents
Dim fileList = GetFiles(root)
' This query will produce the full path for all .txt files
' under the specified folder including subfolders.
' It orders the list according to the file name.
Dim fileQuery = From file In fileList Where file.Extension =
Ejemplo
La consulta siguiente muestra cómo agrupar el contenido de un árbol de directorios especificado por la
extensión de archivo.
Module GroupByExtension
Public Sub Main()
' Root folder to query, along with all subfolders.
Dim startFolder As String = "C:\program files\Microsoft Visual
Studio 9.0\VB\"
' Used in WriteLine() to skip over startfolder in output lines.
Dim rootLength As Integer = startFolder.Length
' Take a snapshot of the file system.
Dim fileList As IEnumerable(Of System.IO.FileInfo) =
GetFiles(startFolder)
' Create the query.
Dim queryGroupByExt = From file In fileList Group By
End If
Loop
Next
End Sub
End Module
El resultado de este programa puede ser largo, dependiendo de los detalles del sistema de archivos local
y del valor en que se haya establecido startFolder. Para habilitar la presentación de todos los resultados,
este ejemplo muestra cómo recorrer los resultados página a página. Las mismas técnicas se pueden
aplicar a aplicaciones Windows y web. Observe que, dado que el código página los elementos de un
grupo, se requiere un bucle foreach anidado. Existe lógica adicional para calcular la posición actual en la
lista y para permitir al usuario detener la paginación y salir del programa. En este caso determinado, la
consulta de paginación se ejecuta contra los resultados de la consulta original almacenados en memoria
caché. En otros contextos, como LINQ a SQL, no se requiere tal almacenamiento en caché.
Ejemplo
El método Sum agrega los valores de todos los elementos seleccionados en la cláusula select. Es fácil
modificar esta consulta para recuperar el archivo mayor o menor del árbol de directorios especificado
mediante una llamada al método Min o Max en lugar de Sum.
Module QueryTotalBytes
Sub Main()
' Change the drive\path if necessary.
Dim root As String = "C:\Program Files\Microsoft Visual Studio 9.0\VB"
'Take a snapshot of the folder contents.
' This method assumes that the application has discovery permissions
' for all folders under the specified path.
Dim fileList = My.Computer.FileSystem.GetFiles _
(root, FileIO.SearchOption.SearchAllSubDirectories, "*.*")
Dim fileQuery = From file In fileList Select GetFileLength(file)
' Force execution and cache the results to avoid multiple trips
' to the file system.
Dim fileLengths = fileQuery.ToArray()
' Find the largest file
Dim maxSize = Aggregate aFile In fileLengths Into Max()
' Find the total number of bytes
Dim totalBytes = Aggregate aFile In fileLengths Into Sum()
Console.WriteLine("The largest file is " & maxSize & " bytes")
Console.WriteLine("There are " & totalBytes & " total bytes in " & _
fileList.Count & " files under " & root)
' Keep the console window open in debug mode
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
' This method is used to catch the possible exception
La consulta llama a un método independiente para obtener la longitud del archivo. La razón es utilizar la
posible excepción que se producirá si el archivo se eliminó en otro subproceso después de haber creado
el objeto FileInfo en la llamada a GetFiles. Aunque ya se haya creado el objeto FileInfo, la excepción se
puede producir igualmente si un objeto FileInfo intenta actualizar su propiedad Length con la longitud
más actualizada la primera vez que se tenga acceso a la propiedad. Al incluir esta operación en un
bloque try-catch fuera de la consulta, el código sigue la regla de evitar usar en las consultas operaciones
que pueden tener efectos adversos. Por lo general, se deben tomar precauciones al utilizar las
excepciones, para asegurarse de que una aplicación no queda en un estado desconocido.
Consultar un valor booleano que especifica si las dos listas de archivos son idénticas.
Consultar la intersección para recuperar los archivos que están en ambas carpetas.
Consultar la diferencia de conjuntos para recuperar los archivos que están en una carpeta pero
no en la otra.
Nota:
Las técnicas mostradas aquí pueden adaptarse para comparar secuencias de objetos de cualquier
tipo.
La clase FileComparer que se incluye aquí muestra cómo utilizar una clase comparadora personalizada
junto con los operadores de consulta estándar. La clase no está pensada para el uso en escenarios
reales. Simplemente utiliza el nombre y la longitud en bytes de cada archivo para determinar si el
contenido de cada carpeta es idéntico o no. En un escenario real, debería modificar este comparador
para realizar una comprobación de igualdad más rigurosa.
Ejemplo
Module CompareDirs
Public Sub Main()
' Create two identical or different temporary folders
' on a local drive and add files to them.
' Then set these file paths accordingly.
Dim pathA As String = "C:\TestDir"
Dim pathB As String = "C:\TestDir2"
' Take a snapshot of the file system.
Dim list1 = GetFiles(pathA)
Dim list2 = GetFiles(pathB)
' Create the FileCompare object we'll use in each query
Dim myFileCompare As New FileCompare
' This query determines whether the two folders contain
' identical file lists, based on the custom file comparer
' that is defined in the FileCompare class.
' The query executes immediately because it returns a bool.
Dim areIdentical As Boolean = list1.SequenceEqual(list2, myFileCompare)
If areIdentical = True Then
Console.WriteLine("The two folders are the same.")
Else
Console.WriteLine("The two folders are not the same.")
End If
' Find common files in both folders. It produces a sequence and
' doesn't execute until the foreach statement.
Dim queryCommonFiles = list1.Intersect(list2, myFileCompare)
If queryCommonFiles.Count() > 0 Then
Console.WriteLine("The following files are in both folders:")
For Each fi As System.IO.FileInfo In queryCommonFiles
Console.WriteLine(fi.FullName)
Next
Else
Console.WriteLine("There are no common files in the two folders.")
End If
' Find the set difference between the two folders.
' For this example we only check one way.
Dim queryDirAOnly = list1.Except(list2, myFileCompare)
Console.WriteLine("The following files are in dirA but not dirB:")
For Each fi As System.IO.FileInfo In queryDirAOnly
Console.WriteLine(fi.FullName)
Next
' Keep the console window open in debug mode
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
' This implementation defines a very simple comparison
' between two FileInfo objects. It only compares the name
' of the files being compared and their length in bytes.
Public Class FileCompare
Implements System.Collections.Generic.IEqualityComparer(Of
System.IO.FileInfo)
Cómo recuperar el archivo mayor o menor del objeto FileInfo de una o más carpetas bajo una
carpeta raíz especificada.
Cómo ordenar los archivos por grupos según su tamaño en bytes, omitiendo los archivos cuyo
tamaño sea menor que el especificado.
Ejemplo
El ejemplo siguiente contiene cinco consultas independientes que muestran cómo consultar y agrupar
archivos, dependiendo de su tamaño en bytes. Puede modificar fácilmente estos ejemplos para basar la
consulta en alguna otra propiedad del objeto FileInfo.
Module QueryBySize
Sub Main()
' Change the drive\path if necessary
Dim root As String = "C:\Program Files\Microsoft Visual Studio 9.0"
Dim fileList = GetFiles(root)
' Return the size of the largest file
Dim maxSize = Aggregate aFile In fileList Into Max(GetFileLength(aFile))
'Dim maxSize = fileLengths.Max
Console.WriteLine("The length of the largest file under {0} is {1}", _
root, maxSize)
' Return the FileInfo object of the largest file
' by sorting and selecting from the beginning of the list
Dim filesByLengDesc = From file In fileList Let filelength =
GetFileLength(file) Where filelength > 0 Order By filelength
Descending Select file
Dim longestFile = filesByLengDesc.First
Console.WriteLine("The largest file under {0} is {1} with a
length of {2} bytes", root, longestFile.FullName, longestFile.Length)
Dim smallestFile = filesByLengDesc.Last
Console.WriteLine("The smallest file under {0} is {1} with a
length of {2} bytes", root, smallestFile.FullName, smallestFile.Length)
' Return the FileInfos for the 10 largest files
' Based on a previous query, but nothing is executed
' until the For Each statement below.
Dim tenLargest = From file In filesByLengDesc Take 10
Console.WriteLine("The 10 largest files under {0} are:", root)
For Each fi As System.IO.FileInfo In tenLargest
Console.WriteLine("{0}: {1} bytes", fi.FullName, fi.Length)
Next
' Group files according to their size,
' leaving out the ones under 200K
Dim sizeGroups = From file As System.IO.FileInfo In
GetFiles(root) Where file.Length > 0 Let groupLength =
file.Length / 100000 Group file By groupLength Into fileGroup =
Group Where groupLength >= 2 Order By groupLength Descending
For Each group In sizeGroups
Console.WriteLine(group.groupLength + "00000")
For Each item As System.IO.FileInfo In group.fileGroup
Console.WriteLine(" {0}: {1}", item.Name, item.Length)
Next
Next
' Keep the console window open in debug mode
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
' This method is used to catch the possible exception
La consulta llama a un método independiente para obtener el tamaño de archivo en bytes con el fin de
utilizar la posible excepción que se producirá si se eliminó un archivo en otro subproceso desde que se
creó el objeto FileInfo en la llamada a GetFiles. Aunque ya se haya creado el objeto FileInfo, la excepción
se puede producir igualmente si un objeto FileInfo intenta actualizar su propiedad Length con el tamaño
en bytes más actualizado la primera vez que se tenga acceso a la propiedad. Al incluir esta operación en
un bloque try-catch fuera de la consulta, seguimos la regla de evitar usar en las consultas operaciones
que pueden tener efectos adversos. Por lo general, debemos tener cuidado al utilizar las excepciones,
para asegurarnos de que no dejamos una aplicación en un estado desconocido.
Ejemplo
Module QueryDuplicateFileNames
Public Sub Main()
Dim path As String = "C:\Program Files\Microsoft Visual Studio
9.0\Common7"
'QueryDuplicates1(path)
' Uncomment to run this query instead
QueryDuplicates2(path)
End Sub
Sub QueryDuplicates1(ByVal root As String)
Dim duplicates = From aFile In GetFiles(root) Order By aFile.Name
Group aFile By aFile.Name Into newGroup = Group Where
newGroup.Count() >= 2 Select newGroup
' Page the display so that the results can be read.
Dim trimLength = root.Length
PageOutput(duplicates, trimLength)
End Sub
Sub QueryDuplicates2(ByVal root As String)
' This time a composite key is used. This sub finds all files
' that have been copied into multiple subfolders.
Dim duplicates = From aFile In GetFiles(root) Order By aFile.Name
Group aFile By aFile.Name, aFile.CreationTime, aFile.Length Into
newGroup = Group Where newGroup.Count() >= 2 Select newGroup
' Page the display so that the results can be read.
Dim trimLength = root.Length
PageOutput(duplicates, trimLength)
End Sub
' Pages console diplay for large query results. No more than one
group per page.
' This sub specifically works with group queries of FileInfo objects
' but can be modified for any type.
Sub PageOutput(ByVal groupQuery, ByVal charsToSkip)
' "3" = 1 line for extension key + 1 for "Press any key" + 1 for
' input cursor.
Dim numLines As Integer = Console.WindowHeight - 3
' Flag to indicate whether there are more results to diplay
Dim goAgain As Boolean = True
For Each fg As IEnumerable(Of System.IO.FileInfo) In groupQuery
' Start a new extension at the top of a page.
Dim currentLine As Integer = 0
Do While (currentLine < fg.Count())
Console.Clear()
' Get the next page of results
' No more than one filename per page
Dim resultPage=From file In fg Skip currentLine Take numLines
' Execute the query. Trim the paths in the output.
For Each line In resultPage
Console.WriteLine(vbTab &
line.FullName.Substring(charsToSkip))
Next
' Advance the current position
currentLine = numLines + currentLine
' Give the user a chance to break out of the loop
Console.WriteLine("Press any key for next page or the
Ejemplo
Module QueryContents
Public Sub Main()
' Modify this path as necessary.
Dim startFolder = "c:\program files\Microsoft Visual Studio
9.0\VB\"
' Take a snapshot of the file system.
Dim fileList = GetFiles(startFolder)
Dim searchTerm = "Visual Studio"
' Search the contents of each file.
' A regular expression created with the RegEx class
' could be used instead of the Contains method.
Dim queryMatchingFiles = From file In fileList Where
file.Extension = ".htm" Let fileText = GetFileText(file.FullName)
Where fileText.Contains(searchTerm) Select file.FullName
Console.WriteLine("The term " & searchTerm & " was found in:")
' Execute the query.
Utilizar una variable de rango con un tipo declarado explícitamente en una expresión de consulta es
equivalente a llamar al método Cast<(Of <(TResult>)>). Cast<(Of <(TResult>)>) inicia una excepción
si no se puede realizar la conversión de tipos especificada. Cast<(Of <(TResult>)>) y OfType<(Of
<(TResult>)>) son los dos métodos de operador de consulta estándar que actúan en tipos IEnumerable
no genéricos.
Ejemplo
En el ejemplo siguiente se muestra una consulta simple sobre ArrayList. Observe que en este ejemplo se
utilizan inicializadores de objeto cuando el código llama al método Add, pero no es obligatorio.
Imports System.Collections
Imports System.Linq
Module Module1
Public Class Student
Public FirstName As String
Public LastName As String
Public Scores As Integer()
End Class
Sub Main()
Dim student1 As New Student With {.FirstName = "Svetlana", _
.LastName = "Omelchenko",.Scores = New Integer() {98,92,81,60}}
Dim student2 As New Student With {.FirstName = "Claire", _
.LastName = "O'Donnell",.Scores = New Integer() {75, 84, 91, 39}}
Dim student3 As New Student With {.FirstName = "Cesar", _
.LastName = "Garcia",.Scores = New Integer() {97, 89, 85, 82}}
Dim student4 As New Student With {.FirstName = "Sven", _
.LastName = "Mortensen",.Scores = New Integer() {88, 94, 65, 91}}
Dim arrList As New ArrayList()
arrList.Add(student1)
arrList.Add(student2)
arrList.Add(student3)
arrList.Add(student4)
' Use an explicit type for non-generic collections
Dim query = From student As Student In arrList Where
student.Scores(0) > 95 Select student
For Each student As Student In query
Console.WriteLine(student.LastName & ": " & student.Scores(0))
Next
' Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
End Module
' Output:
' Omelchenko: 98
' Garcia: 97
7. LINQ a XML
LINQ a XML proporciona una interfaz de programación XML en memoria que aprovecha las características
de .NET Language-Integrated Query (LINQ) Framework. LINQ a XML utiliza las características más
recientes del lenguaje .NET Framework y es comparable a una actualizada y rediseñada interfaz de
programación XML para el Modelo de objetos de documento (DOM).
La familia de tecnologías de LINQ proporciona un completo entorno de creación de consultas para objetos
(LINQ), bases de datos relacionales (LINQ a SQL) y XML (LINQ a XML).
XML se ha adoptado ampliamente como un modo de formatear datos en diversos contextos. Puede
encontrar XML en la Web, en archivos de configuración, en archivos de Microsoft Office Word y en bases
de datos.
LINQ a XML es un método actualizado y rediseñado para la programación con XML. Proporciona
capacidades de modificación de documento en memoria de Document Object Model (DOM), y es
compatible con expresiones de consulta LINQ. Aunque estas expresiones de consulta difieren
sintácticamente de XPath, proporcionan una funcionalidad similar con un establecimiento inflexible de
tipos superior.
Los desarrolladores profesionales pueden usar LINQ a XML para aumentar considerablemente su
productividad. Con LINQ a XML, pueden escribir menos código, que a su vez resulte más expresivo,
compacto y eficaz. Pueden usar expresiones de consulta de distintos dominios de datos
simultáneamente.
Se parece a Document Object Model (DOM) en lo que respecta a la inserción del documento XML en la
memoria. Puede consultar y modificar el documento; una vez modificado, puede guardarlo en un archivo
o serializarlo y enviarlo a través de una conexión. Sin embargo, LINQ a XML difiere de DOM: proporciona
un nuevo modelo de objetos más ligero con el que se trabaja más fácilmente y que aprovecha las
mejoras de lenguaje de Visual C# 2008.
La ventaja más importante de LINQ a XML radica en su integración con Language-Integrated Query
(LINQ). Esta integración permite escribir consultas en el documento XML en memoria para recuperar
colecciones de elementos y atributos. Las capacidades de consulta de LINQ a XML son comparables en
cuanto a funcionalidad (aunque no en sintaxis) a XPath y XQuery. La integración de LINQ en Visual C#
2008 proporciona una escritura más rápida, comprobación en tiempo de compilación y una
compatibilidad mejorada con el depurador.
La capacidad LINQ de LINQ a XML permite ejecutar consultas en XML. Por ejemplo, es posible que tenga
un pedido de compra XML típico, tal como se describe en Archivo XML de muestra: pedido de compra
típico (LINQ a XML). Mediante LINQ a XML, podría ejecutar la siguiente consulta para obtener el valor del
atributo de número de pieza relativo a cada elemento del pedido de compra:
Dim partNos = From item In purchaseOrder...<Item> Select item.@PartNumber
A modo de ejemplo, imagine que necesita una lista, ordenada por números de pieza, de los elementos
con un valor superior a 100 $. Para obtener esta información, podría ejecutar la siguiente consulta:
Por ejemplo, a continuación se muestra una forma típica de crear un árbol XML usando la
implementación de DOM de Microsoft, XmlDocument:
Dim doc As XmlDocument = New XmlDocument()
Dim name As XmlElement = doc.CreateElement("Name")
name.InnerText = "Patrick Hines"
Dim phone1 As XmlElement = doc.CreateElement("Phone")
phone1.SetAttribute("Type", "Home")
phone1.InnerText = "206-555-0144"
Dim phone2 As XmlElement = doc.CreateElement("Phone")
phone2.SetAttribute("Type", "Work")
phone2.InnerText = "425-555-0145"
Dim street1 As XmlElement = doc.CreateElement("Street1")
street1.InnerText = "123 Main St"
Dim city As XmlElement = doc.CreateElement("City")
city.InnerText = "Mercer Island"
Dim state As XmlElement = doc.CreateElement("State")
state.InnerText = "WA"
Dim postal As XmlElement = doc.CreateElement("Postal")
postal.InnerText = "68042"
Dim address As XmlElement = doc.CreateElement("Address")
address.AppendChild(street1)
address.AppendChild(city)
address.AppendChild(state)
address.AppendChild(postal)
Dim contact As XmlElement = doc.CreateElement("Contact")
contact.AppendChild(name)
contact.AppendChild(phone1)
contact.AppendChild(phone2)
contact.AppendChild(address)
Dim contacts As XmlElement = doc.CreateElement("Contacts")
contacts.AppendChild(contact)
doc.AppendChild(contacts)
Console.WriteLine(doc.OuterXml)
Este estilo de codificación no proporciona mucha información visual acerca de la estructura del árbol
XML. LINQ a XML admite este enfoque para crear un árbol XML, pero también admite un enfoque
alternativo, la construcción funcional. La construcción funcional usa los constructores XElement y
XAttribute para crear un árbol XML.
A continuación se muestra cómo se crea el mismo árbol XML usando la construcción funcional LINQ a
XML:
Dim contacts = _
<Contacts>
<Contact>
<Name>Patrick Hines</Name>
<Phone Type="Home">206-555-0144</Phone>
<Phone Type="Work">425-555-0145</Phone>
<Address>
<Street1>123 Main St</Street1>
<City>Mercer Island</City>
<State>WA</State>
<Postal>68042</Postal>
</Address>
</Contact>
</Contacts>
El compilador de Visual Basic traduce los literales XML a llamadas a métodos en LINQ a XML.
Si tiene un documento XML y desea crear un árbol XML a partir de él, puede abrir el documento XML en
un editor, copiar el código XML en el portapapeles, abrir un módulo de Visual Basic en Visual Studio y
pegar el código XML directamente en el editor de código de Visual Basic.
Crear elementos XML sin usar un objeto de documento. Esto simplifica la programación cuando
se tiene que trabajar con fragmentos de árboles XML.
Compare esto con modelo DOM del consorcio W3C, en el que el documento XML se usa como contenedor
lógico para el árbol XML. En DOM, los nodos XML, incluyendo elementos y atributos, se deben crear en el
contexto de un documento XML. A continuación se muestra un fragmento del código para crear un
nombre de elemento en DOM:
Dim doc As XmlDocument = New XmlDocument()
Dim name As XmlElement = doc.CreateElement("Name")
name.InnerText = "Patrick Hines"
doc.AppendChild(name)
Si desea usar un elemento en varios documentos, debe importar los nodos en los documentos. Se trata
de una complejidad innecesaria que LINQ a XML evita.
Cuando se utiliza LINQ a XML, se usa la clase XDocument solamente si se desea agregar un comentario o
una instrucción de procesamiento en el nivel de raíz del documento.
LINQ a XML también elimina el requisito de que el usuario comprenda qué es NameTable y cómo se
usa.
Otro problema con DOM es que no permite cambiar el nombre de un nodo. En su lugar, se tiene que
crear un nuevo nodo y copiar en él todos los nodos secundarios, perdiéndose así la identidad del nodo
original. LINQ a XML evita este problema permitiendo establecer la propiedad XName en un nodo.
Un caso muy común es aquel en el que se leen datos XML con sangría, se crea un árbol XML en memoria
sin ningún nodo de texto con espacios en blanco (es decir, sin preservar los espacios en blanco), se
realizan ciertas operaciones sobre el XML y éste se guarda con sangría. Cuando se serializa el código XML
con formato, sólo se preservan en el árbol XML los espacios en blanco más significativos. Éste es el
comportamiento predeterminado para LINQ a XML.
Otro escenario muy común es aquel en el que se lee y se modifica código XML en el que se ha aplicado
sangría de forma intencionada. Es posible que no desee realizar ningún tipo de modificación en esta
sangría. En LINQ a XML, puede llevarlo a cabo si preserva los espacios en blanco cuando cargue o analice
el código XML y deshabilite el formato al serializar el código XML.
LINQ a XML almacena un espacio en blanco como un nodo XText, en lugar de tener un tipo de nodo
Whitespace especializado, al igual que DOM.
LINQ a XML se implementa sobre XmlReader y ambos están estrechamente integrados. No obstante,
también puede usar XmlReader de forma independiente.
Por ejemplo, supongamos que está creando un servicio web que analizará cientos de de documentos XML
por segundo y los documentos tienen la misma estructura, por lo que sólo tiene que escribir una
implementación de código para analizar el XML. En ese caso, probablemente deseará usar XmlReader de
forma independiente.
Por el contrario, si está creando un sistema que analiza varios documentos XML más pequeños y cada
uno es diferente, quizás desee aprovechar las mejoras de productividad que LINQ a XML proporciona.
XSLT puede ser un sistema muy productivo que produce aplicaciones de alto rendimiento. Algunas
grandes compañías web utilizan XSLT como forma de generar HTML a partir de XML extraído de varios
almacenes de datos. El motor XSLT administrado compila código XSLT a CLR y tiene un rendimiento
incluso mejor en algunos escenarios que el motor XSLT nativo.
Cuando se dominen las expresiones de consulta LINQ a XML, las transformaciones de LINQ a XML son
una tecnología eficaz y fácil de usar. Básicamente, se forman documentos XML utilizando construcciones
funcionales, extrayendo datos de varios orígenes, construyendo objetos XElement dinámicamente y
ensamblando el conjunto en un nuevo árbol XML. La transformación puede generar un documento
completamente nuevo. Construir transformaciones en LINQ a XML es relativamente sencillo e intuitivo y
el código resultante es legible. Esto reduce los costos de desarrollo y mantenimiento.
LINQ a XML no se ha diseñado para sustituir a XSLT. XSLT sigue siendo la herramienta preferida de
transformaciones XML complicadas y basadas en documentos, especialmente si la estructura del
documento no está bien definida.
XSLT tiene la ventaja de ser un estándar de W3C. Si tiene el requisito de usar solamente tecnologías que
son estándares, XSLT puede ser más adecuado.
aplicaciones de estilo AJAX. MSXML se puede usar en cualquier lenguaje de programación que admita
COM, incluyendo C++, JavaScript y Visual Basic 6.0.
La principal ventaja de XmlLite es que es un analizador XML rápido y ligero que es seguro en la mayoría
de casos. Su área de exposición a amenazas es muy pequeña. Si debe analizar documentos que no son
de confianza y desea protegerse de ataques tales como denegación de servicio o exposición de datos,
XmlLite puede ser una buena opción.
El objetivo de esta documentación es facilitar el uso de LINQ a XML a todo tipo de desarrolladores. LINQ
a XML consigue que la programación XML sea más sencilla. No necesita ser un experto desarrollador para
utilizarla.
LINQ a XML se basa principalmente en clases genéricas. Por tanto, es muy importante que comprenda
cómo se utilizan las clases genéricas. Más aún, resultaría de utilidad que estuviera familiarizado con los
delegados que se declaran como tipos parametrizados.
Algunas aplicaciones toman los documentos XML de origen y crean nuevos documentos
XML que tienen una forma diferente que los documentos de origen.
Algunas aplicaciones toman documentos XML de origen e insertan registros en una base de
datos.
Algunas aplicaciones toman datos de otro origen de datos, como una base de datos y
crean documentos XML a partir de él.
Estos no son todos los tipos de aplicaciones XML, pero son un conjunto representativo de los tipos de
funcionalidad que un programador XML debe implementar.
Con todos esos tipos de aplicaciones un desarrollador puede tomar dos enfoques opuestos:
Cuando se utiliza el enfoque funcional, la idea fundamental es escribir transformaciones que toman los
documentos de origen y general documentos de resultados completamente nuevos con la forma que se
desee.
Cuando se modifica un árbol XML, la idea fundamental es escribir código que atraviese y se desplace por
los nodos en un árbol XML en memoria, insertando, eliminando y modificando nodos según sea
necesario.
Puede usar LINQ a XML con cualquier enfoque. Se usan las mismas clases y, en algunos casos, los
mismos métodos. No obstante, la estructura y los objetivos de los dos enfoques son muy diferentes.
Los dos enfoques suelen tener diferentes perfiles de rendimiento. En situaciones diferentes, uno u otro
enfoque tendrá a menudo un rendimiento mejor y usará más o menos memoria.
Asimismo, uno u otro enfoque será más fácil de escribir y proporcionará código más fácil de mantener.
Clase XCData
XCData representa un nodo de texto CDATA.
Clase XComment
XComment representa un comentario XML.
Clase XContainer
XContainer es una clase base abstracta para todos los nodos que pueden tener nodos secundarios. Las
siguientes clases se derivan de la clase XContainer:
XElement
XDocument
Clase XDeclaration
XDeclaration representa una declaración XML. Una declaración XML se utiliza para declarar la versión de
XML y la codificación de un documento. Asimismo, una declaración XML especifica si el documento XML
es independiente.
Clase XDocument
XDocument representa un documento XML.
Clase XDocumentType
XDocumentType representa una definición de tipo del documento (DTD) XML.
Clase XElement
XElement representa un elemento XML.
Clase XName
XName representa nombres de elementos (XElement) y atributos (XAttribute).
LINQ a XML se ha diseñado para hacer que los nombres XML sean tan sencillos como sea posible. Debido
a su complejidad, los nombres XML a menudo se consideran un tema avanzado en XML. Puede
argumentarse que la complejidad no proviene de los espacios de nombres, que los desarrolladores usan
regularmente en la programación, sino de los prefijos de los espacios de nombres. Los prefijos de los
espacios de nombres pueden ser útiles para reducir las pulsaciones de teclas necesarias cuando se
especifica código XML o hacer que el código XML sea más fácil de leer. No obstante, a menudo los
prefijos son un acceso directo al espacio de nombres XML completo y no son necesarios en la mayoría de
los casos. LINQ a XML simplifica los nombres XML resolviendo todos los prefijos a su espacio de nombres
XML correspondiente. Los prefijos están disponibles, si son necesarios, a través del método
GetPrefixOfNamespace.
Clase XNamespace
XNamespace representa un espacio de nombres para un XElement o XAttribute. Los espacios de nombres
son un componente de un XName.
Clase XNode
XNode es una clase abstracta que representa los nodos de un árbol XML. Las siguientes clases se derivan
de la clase XNode:
XText
XContainer
XComment
XProcessingInstruction
XDocumentType
Clase XNodeDocumentOrderComparer
Clase XNodeEqualityComparer
XNodeEqualityComparer proporciona la funcionalidad para comparar la igualdad del valor de los nodos.
Clase XObject
XObject es una clase base abstracta de XNode y XAttribute. Proporciona funcionalidad de evento y
anotación.
Clase XObjectChange
XObjectChange especifica el tipo de evento cuando se produce para un XObject.
Clase XObjectChangeEventArgs
XObjectChangeEventArgs proporciona datos para los eventos Changing y Changed.
Clase XProcessingInstruction
XProcessingInstruction representa una instrucción de procesamiento de XML. Una instrucción de
procesamiento comunica información a una aplicación que procesa el XML.
Clase XText
XText representa un nodo de texto. En la mayoría de casos no tiene que usar esta clase. Esta clase se
utiliza principalmente para el contenido mixto.
Funcionalidad de XElement
Este tema describe la funcionalidad que ofrece la clase XElement.
Puede analizar XML a partir de diferentes orígenes, incluyendo un TextReader, archivos de texto
o direcciones Web (URL).
<Contact>
<Name>Patrick Hines</Name>
<Phone>206-555-0144</Phone>
<Address>
<Street1>123 Main St</Street1>
<City>Mercer Island</City>
<State>WA</State>
<Postal>68042</Postal>
</Address>
</Contact>
</Contacts>
Otra técnica utilizada con frecuencia para crear un árbol XML implica el uso de los resultados de una
consulta LINQ para rellenar el árbol XML, tal y como se muestra en el ejemplo siguiente:
Dim srcTree As XElement = _
<Root>
<Element>1</Element>
<Element>2</Element>
<Element>3</Element>
<Element>4</Element>
<Element>5</Element>
</Root>
Dim xmlTree As XElement = _
<Root>
<Child>1</Child>
<Child>2</Child>
<%= From el In srcTree.Elements() _
Where el.Value > 2 _
Select el %>
</Root>
Console.WriteLine(xmlTree)
Este ejemplo genera el siguiente resultado:
Xml
<Root>
<Child>1</Child>
<Child>2</Child>
<Element>3</Element>
<Element>4</Element>
<Element>5</Element>
</Root>
Serializar árboles XML
Puede serializar un árbol XML en un File, un TextWriter o en un XmlWriter.
Información general
Trabajar con atributos en LINQ a XML es similar a trabajar con elementos. Sus constructores son
similares. Los métodos que usa para recuperar colecciones de ellos también son similares. Una expresión
de consulta LINQ de una colección de atributos se parece mucho a una expresión de consulta LINQ de
una colección de elementos.
Se conserva el orden en el que se agregaron los atributos a un elemento. Es decir, cuando procese una
iteración en los atributos, los verá en el mismo orden en el que se agregaron.
El constructor XAttribute
El siguiente constructor de la clase XAttribute es el que usará normalmente:
Constructor Descripción
<Customer>
<Name>John Doe</Name>
<PhoneNumbers>
<Phone type="home">555-555-5555</Phone>
<Phone type="work">666-666-6666</Phone>
</PhoneNumbers>
</Customer>
</Customers>
Los atributos no son nodos
Existen algunas diferencias entre los atributos y los elementos. Los objetos XAttribute no son nodos en el
árbol XML. Son pares de nombre y valor asociados a un elemento XML. A diferencia de Document Object
Model (DOM), esto refleja de manera más precisa la estructura del código XML. Aunque los objetos
XAttribute no son realmente nodos en el árbol XML, trabajar con objetos XAttribute es muy similar a
trabajar con objetos XElement.
Esta distinción sólo resulta de importancia para los desarrolladores que escriban código que trabaje con
árboles XML en el nivel de nodo. A muchos desarrolladores no les afecta esta distinción.
Tenga en cuenta que sólo debe crear objetos XDocument si necesita la funcionalidad específica que
proporciona la clase XDocument. En muchas circunstancias, puede trabajar directamente con XElement.
Trabajar directamente con XElement constituye un modelo de programación más simple.
XDocument se deriva de XContainer. Por lo tanto, puede contener nodos secundarios. Sin embargo, los
objetos XDocument sólo pueden tener un nodo XElement secundario. Esto refleja el estándar XML: sólo
puede haber un elemento raíz en un documento XML.
Componentes de XDocument
Un objeto XDocument puede contener los siguientes elementos:
Cualquier número de objetos XComment. Los comentarios serán del mismo nivel que el
elemento raíz. El objeto XComment no puede ser el primer argumento de la lista, ya que no es
válido que el documento XML empiece con un comentario.
Usar XDocument
Para construir un objeto XDocument, use la construcción funcional, al igual que cuando se construyen
objetos XElement.
Ejemplo
El siguiente código contiene las instrucciones Imports que los ejemplos de Visual Basic requieren para
crearse y ejecutarse. No todas las instrucciones Imports son necesarias para cada ejemplo.
Imports System
Imports System.Diagnostics
Imports System.Collections
Imports System.Collections.Generic
Imports System.Collections.Specialized
Imports System.Text
Imports System.Linq
Imports System.Xml
Imports System.Xml.Linq
Imports System.Xml.Schema
Imports System.Xml.XPath
Imports System.Xml.Xsl
Imports System.IO
Imports System.Threading
Imports System.Reflection
Imports System.IO.Packaging
Hay varias características fundamentales de la interfaz de programación de LINQ a XML que permiten la
construcción funcional:
El constructor XElement toma varios tipos de argumentos para el contenido. Por ejemplo, puede
pasar otro objeto XElement, que se convierte en un elemento secundario. Puede pasar un objeto
XAttribute, que se convierte en un atributo del elemento. O bien puede pasar cualquier otro tipo
de objeto, que se convierte en una cadena y en el contenido de texto del elemento.
El constructor XElement toma una matriz de params del tipo Object, de forma que puede pasar
cualquier número de objetos al constructor. Esto permite crear un elemento que tiene un
contenido complejo.
Estas características permiten escribir código para crear un árbol XML. A continuación se muestra un
ejemplo:
Dim srcTree As XElement = _
<Root>
<Element>1</Element>
<Element>2</Element>
<Element>3</Element>
<Element>4</Element>
<Element>5</Element>
</Root>
Dim xmlTree As XElement = _
<Root>
<Child>1</Child>
<Child>2</Child>
<%= From el In srcTree.Elements() _
Es importante reseñar que en una expresión incrustada sólo puede aparecer una expresión única. No es
posible incrustar varias instrucciones. Si una expresión se extiende más allá de una única línea, deberá
utilizar el carácter de continuación de línea.
Si utiliza una expresión incrustada para agregar nodos (incluyendo elementos) y atributos ya existentes
a nuevo árbol XML y los nodos existentes ya tienen elementos primarios, los nodos se clonarán. Esos
nodos clonados nuevos se agregan al nuevo árbol XML. Si los nodos ya existentes no tienen elementos
primarios, los nodos simplemente se agregan al nuevo árbol XML. El último ejemplo de este tema
muestra este comportamiento.
El siguiente ejemplo utiliza una expresión incrustada para agregar un elemento al árbol:
Dim xmlTree1 As XElement = _
<Root>
<Child>Contents</Child>
</Root>
Dim xmlTree2 As XElement = _
<Root>
<%= xmlTree1.<Child> %>
</Root>
Console.WriteLine(xmlTree2)
Utilizar expresiones incrustadas para el contenido
Puede utilizar una expresión incrustada para proporcionar el contenido de un elemento:
Dim str As String
str = "Some content"
Dim root As XElement = <Root><%= str %></Root>
Console.WriteLine(root)
Este ejemplo genera el siguiente resultado:
Xml
<Root>Some content</Root>
Usar una consulta de LINQ en una expresión incrustada
Puede utilizar los resultados proporcionados por una consulta LINQ para el contenido de un elemento:
Dim arr As Integer() = {1, 2, 3}
Dim n As XElement = _
<Root>
<%= From i In arr Select <Child><%= i %></Child> %>
</Root>
Console.WriteLine(n)
Utilizar expresiones incrustadas para los nombres de los nodos
También puede utilizar expresiones incrustadas para calcular nombres de atributos, valores de atributos,
nombres de elementos y valores de elementos:
Dim eleName As String = "ele"
Dim attName As String = "att"
Dim attValue As String = "aValue"
Dim eleValue As String = "eValue"
Dim n As XElement = _
<Root <%= attName %>=<%= attValue %>>
<<%= eleName %>>
<%= eleValue %>
</>
</Root>
Console.WriteLine(n)
Este ejemplo genera el siguiente resultado:
Xml
<Root att="aValue">
<ele>eValue</ele>
</Root>
Diferencias entre agregar y clonar
Como ya se mencionó anteriormente, si utiliza una expresión incrustada para agregar nodos (incluyendo
elementos) y atributos ya existentes a nuevo árbol XML y los nodos existentes ya tienen elementos
primarios, los nodos se clonarán y esos nuevos nodos clonados se agregarán al nuevo árbol XML. Si los
nodos ya existentes no tienen elementos primarios, simplemente se agregan al nuevo árbol XML.
' Create a tree with a child element
Dim xmlTree1 As XElement = _
<Root>
<Child1>1</Child1>
</Root>
' Create an element that is not parented
Dim child2 As XElement = <Child2>2</Child2>
' Create a tree and add Child1 and Child2 to it
Dim xmlTree2 As XElement = _
<Root>
<%= xmlTree1.<Child1>(0) %>
<%= child2 %>
</Root>
' Compare Child1 identity.
Console.WriteLine("Child1 was {0}", _
IIf(xmlTree1.Element("Child1") Is xmlTree2.Element("Child1"), _
"attached", "cloned"))
' Compare Child2 identity.
Console.WriteLine("Child2 was {0}", _
IIf(child2 Is xmlTree2.Element("Child2"), _
"attached", "cloned"))
Ejemplo
El siguiente código demuestra qué ocurre si agrega un elemento que tiene elemento primario a un árbol
y qué ocurre si agrega un elemento que no tenga elemento primario a un árbol.
' Create a tree with a child element
Dim xmlTree1 As XElement = _
<Root>
<Child1>1</Child1>
</Root>
' Create an element that is not parented
Dim child2 As XElement = <Child2>2</Child2>
' Create a tree and add Child1 and Child2 to it
Dim xmlTree2 As XElement = _
<Root>
<%= xmlTree1.<Child1>(0) %>
Ejemplo
Puede analizar una cadena en Visual Basic de forma similar. Sin embargo, resulta más eficaz usar
literales XML, tal como se muestra en el código siguiente, ya que dichos literales no sufren las mismas
pérdidas de rendimiento que se producen al analizar XML de una cadena.
Mediante los literales XML, puede copiar y pegar XML en el programa de Visual Basic.
Nota:
Analizar texto o cargar un documento XML de un archivo de texto es menos eficaz que la
construcción funcional. Si inicializa un árbol XML a partir de código, el uso de la construcción
funcional consume menos tiempo de procesador que el análisis de texto.
<State>WA</State>
<Postal>68042</Postal>
</Address>
<NetWorth>11</NetWorth>
</Contact>
</Contacts>
Ejemplo
El siguiente ejemplo muestra cómo cargar un documento XML desde un archivo. El siguiente ejemplo
carga books.xml y produce el árbol XML en la consola.
Dim booksFromFile As XElement = XElement.Load("books.xml")
Console.WriteLine(booksFromFile)
Un caso muy común es aquel en el que se leen datos XML con sangría, se crea un árbol XML en memoria
sin ningún nodo de texto con espacios en blanco (es decir, sin preservar los espacios en blanco), se
realizan ciertas operaciones sobre el XML y éste se guarda con sangría. Si se serializa el código XML con
formato, sólo se preservan en el árbol XML aquellos espacios en blanco más significativos. Éste es el
comportamiento predeterminado para LINQ a XML.
Otro caso muy común es aquel en el que se lee y se modifica código XML en el que se ha aplicado
sangría de forma intencionada. Es posible que no desee modificar esta sangría de ninguna forma. En
LINQ a XML, puede conseguirlo si preserva los espacios en blanco a la hora de cargar o analizar el código
XML y si deshabilita el formato cuando serializa el código XML.
En este tema se describe el comportamiento de espacios en blanco de métodos que rellenan los árboles
XML.
Con estos métodos, si se conservan los espacios en blanco, se inserta un espacio en blanco insignificante
en el árbol XML como nodos XText. Si no se conservan los espacios en blanco, entonces no se insertan
los nodos de texto.
Puede crear un árbol XML usando XmlWriter. Los nodos escritos en XmlWriter se rellenan en el árbol. No
obstante, al crear un árbol XML usando este método se conservan todos los nodos, independientemente
de si el nodo es un espacio en blanco o insignificante.
LINQ a XML se implementa sobre XmlReader. Si se pasa XML no válido o mal formado a LINQ a XML, la
clase XmlReader subyacente iniciará una excepción. Los diferentes métodos que analizan XML, como
XElement..::.Parse no detectan la excepción; la excepción se propaga de forma que puede detectarla.
Tenga en cuenta que no puede obtener errores de análisis si utiliza literales XML. El compilador de Visual
Basic detectará errores de XML no válido o mal formado.
Ejemplo
El siguiente código intenta analizar XML no válido:
Try
Dim contacts As XElement = XElement.Parse("<Contacts>" & vbCrLf & _
" <Contact>" & vbCrLf & _
" <Name>Jim Wilson</Name>" & vbCrLf & _
" </Contact>" & vbCrLf & _
"</Contcts>")
Console.WriteLine(contacts)
Catch e As System.Xml.XmlException
Console.WriteLine(e.Message)
End Try
Cuando ejecuta este código, devuelve la siguiente excepción:
The 'Contacts' start tag on line 1 does not match the end tag of
'Contcts'. Line 5, position 13.
Ejemplo
El siguiente código crea un objeto T:System.Xml.XmlReader y lee nodos hasta que encuentra el primer
nodo de elemento. A continuación, carga el objeto XElement.
Dim r As XmlReader = XmlReader.Create("books.xml")
Do While r.NodeType <> XmlNodeType.Element
r.Read()
Loop
Dim e As XElement = XElement.Load(r)
Console.WriteLine(e)
Una de las formas más efectivas de usar XmlReader para leer objetos XElement es escribir un método de
eje personalizado propio. Un método de eje suele devolver una recopilación como IEnumerable<(Of
<(T>)>) de XElement, tal y como se muestra en el ejemplo de este tema. En el método de eje
personalizado, tras crear el fragmento XML llamando al método ReadFrom, devuelva la recopilación
usando yield return. Esto proporciona semántica de ejecución aplazada al método de eje personalizado.
Cuando crea un árbol XML de un objeto XmlReader, XmlReader debe estar posicionado en un elemento.
El método ReadFrom no vuelve hasta que ha leído la etiqueta de cierre del elemento.
Si desea crear un árbol parcial, puede crear una instancia de un XmlReader, colocar el lector en el nodo
que desea convertir a un árbol XElement y después crear el objeto XElement.
Ejemplo
Este ejemplo crea un método de eje personalizado. Puede consultarlo usando una consulta de LINQ. El
método de eje personalizado, StreamRootChildDoc, es un método que está específicamente diseñado
para leer un documento que tiene un elemento Child que se repite.
Nota:
En el ejemplo de código siguiente se utiliza la construcción yield return de C#. Puesto que no hay
ninguna característica equivalente en Visual Basic 2008, este ejemplo sólo se proporciona en C#.
}
}
static void Main(string[] args)
{
string markup = @"<Root>
<Child Key=""01"">
<GrandChild>aaa</GrandChild>
</Child>
<Child Key=""02"">
<GrandChild>bbb</GrandChild>
</Child>
<Child Key=""03"">
<GrandChild>ccc</GrandChild>
</Child>
</Root>";
IEnumerable<string> grandChildData =
from el in StreamRootChildDoc(new StringReader(markup))
where (int)el.Attribute("Key") > 1
select (string)el.Element("GrandChild");
foreach (string str in grandChildData) {
Console.WriteLine(str);
}
}
Este ejemplo genera el siguiente resultado:
bbb
ccc
En este ejemplo el documento de origen es muy pequeño. No obstante, aunque hubiera millones de
elementos Child, este ejemplo seguiría teniendo una superficie de memoria pequeña.
Normalmente se utiliza este método cuando se utiliza LINQ a XML con otra clase que espera escribir en
un XmlWriter, como XslCompiledTransform.
Ejemplo
Un uso posible para CreateWriter es la invocación de una transformación XSLT. Este ejemplo crea un
árbol XML, crea un XmlReader del árbol XML, crea un nuevo documento y después crea un XmlWriter
para escribir en el nuevo documento. A continuación invoca la transformación XSLT, pasando XmlReader
y XmlWriter. Después de que se complete correctamente la transformación, se rellenará el nuevo árbol
XML con los resultados de la transformación.
Dim xslMarkup As XDocument = _
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
version='1.0'>
<xsl:template match='/Parent'>
<Root>
<C1>
<xsl:value-of select='Child1'/>
</C1>
<C2>
<xsl:value-of select='Child2'/>
</C2>
</Root>
</xsl:template>
</xsl:stylesheet>
Dim xmlTree As XDocument = _
<?xml version='1.0'?>
<Parent>
<Child1>Child1 data</Child1>
<Child2>Child2 data</Child2>
</Parent>
Dim newTree As XDocument = New XDocument()
Using writer As XmlWriter = newTree.CreateWriter()
' Load the style sheet.
Dim xslt As XslCompiledTransform = New XslCompiledTransform()
xslt.Load(xslMarkup.CreateReader())
' Execute the transformation and output the results to a writer.
xslt.Transform(xmlTree.CreateReader(), writer)
End Using
Console.WriteLine(newTree)
Ejemplo
En el ejemplo siguiente se crea un XmlSchemaSet, a continuación, se validan dos objetos XDocument
con el esquema establecido. Uno de los documentos es válido, el otro no.
Dim errors As Boolean = False
Private Sub XSDErrors(ByVal o As Object, ByVal e As ValidationEventArgs)
Console.WriteLine("{0}", e.Message)
errors = True
End Sub
Sub Main()
Dim xsdMarkup As XElement = _
<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
<xsd:element name='Root'>
<xsd:complexType>
<xsd:sequence>
<xsd:element name='Child1' minOccurs='1' maxOccurs='1'/>
<xsd:element name='Child2' minOccurs='1' maxOccurs='1'/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
Contenido válido
Puesto que las consultas a menudo se evalúan como IEnumerable<(Of <(T>)>) de XElement o
IEnumerable<(Of <(T>)>) de XAttribute, resulta conveniente pasar los resultados de una consulta como
contenido a los métodos y constructores que use para rellenar árboles XML.
Al agregar contenido simple, se pueden pasar varios tipos a este método. Entre los tipos válidos se
incluyen los siguientes:
String
Double
Single
Decimal
Boolean
DateTime
TimeSpan
DateTimeOffset
Cualquier tipo que implemente ToString()()().
Cualquier tipo que implemente IEnumerable<(Of <(T>)>).
Al agregar contenido complejo, se pueden pasar varios tipos a este método:
XObject
XNode
XAttribute
Cualquier tipo que implemente IEnumerable<(Of <(T>)>).
Si un objeto implementa IEnumerable<(Of <(T>)>), se enumera la colección del objeto y se agregan
todos los elementos de la colección. Si la colección contiene objetos XNode o XAttribute, cada elemento
de la colección se agrega por separado. Si la colección contiene texto (u objetos convertidos a texto), el
texto de la colección se concatena y se agrega como un nodo de texto.
Si el contenido es null, no se agrega nada. Al pasar una colección, se permite que los elementos de la
colección sean null. Un elemento null de la colección no tiene ningún efecto en el árbol.
Cuando se agregan objetos XNode o XAttribute, si el contenido nuevo no tiene un elemento primario, los
objetos simplemente se agregan al árbol XML. Si el contenido nuevo ya tiene un elemento primario y
forma parte de otro árbol XML, el nuevo contenido se clonará y dicho clon se agregará al árbol XML.
Método Descripción
Add Agrega al final del contenido secundario del objeto XElement o del objeto
XDocument.
AddFirst Agrega contenido al comienzo del contenido secundario del objeto XContainer.
Nombres XML
Los nombres XML a menudo son una fuente de complejidad en la programación XML. Un nombre XML se
compone de un espacio de nombres XML (también denominado URI de espacio de nombres XML) y un
nombre local. Un espacio de nombres XML responde a un objetivo similar al del espacio de nombres de
los programas basados en .NET Framework. Permite certificar de manera exclusiva los nombres de los
elementos y los atributos. Evita conflictos de nombres entre varias partes de un documento XML. Una
vez que haya declarado un espacio de nombres XML, puede seleccionar un nombre local que sólo debe
ser único en dicho espacio de nombres.
Otro aspecto de los nombres XML hace referencia a los prefijos de espacios de nombres XML. Los prefijos
XML generan la mayor parte de la complejidad de los nombres XML. Estos prefijos permiten crear un
acceso directo de un espacio de nombres XML, lo que hace que el documento XML sea más conciso y
comprensible. Sin embargo, los prefijos XML dependen de su contexto para tener un significado, lo que
aumenta la complejidad. Por ejemplo, el prefijo XML aw podría asociarse a un espacio de nombres XML
en una parte del árbol XML y a un espacio de nombres XML diferente en una parte distinta de dicho
árbol.
Una de las ventajas de usar LINQ a XML con C# radica en que simplifica los nombres XML al quitar el
requisito de que los desarrolladores usen prefijos XML. Cuando LINQ a XML carga o analiza un
documento XML, cada prefijo XML se resuelve en su espacio de nombres XML correspondiente. Después,
cuando trabaje con un documento que usa espacios de nombres, la mayoría de las veces tendrá acceso a
dichos espacios de nombres mediante el URI de éstos, y no mediante prefijo. Cuando los desarrolladores
trabajan con nombres XML en LINQ a XML, siempre lo hacen con un nombre XML completo (es decir, un
espacio de nombres XML y un nombre local). Sin embargo, si es necesario, LINQ a XML permite trabajar
con prefijos de espacios de nombres y controlarlos.
Si usa LINQ a XML con literales XML y Visual Basic, debe usar los prefijos para trabajar con documentos
en espacios de nombres.
En LINQ a XML, la clase que representa los nombres XML es XName. Los nombres XML aparecen
frecuentemente en la API LINQ a XML, y cuando se requiera un nombre XML, encontrará un parámetro
XName. No obstante, apenas se trabaja directamente con un objeto XName. XName contiene una
conversión implícita de cadena.
Cuando se utilizan literales XML en Visual Basic, los usuarios pueden definir un espacio de nombres XML
predeterminado que sea global. Este espacio de nombres será el predeterminado tanto para los literales
XML como para las propiedades XML. Es posible definir el espacio de nombres XML predeterminado tanto
a nivel de proyecto como a nivel de archivo. En caso de definirlo a nivel de archivo, éste invalidará al
espacio de nombres predeterminado que se definió a nivel del proyecto.
También puede definir otros espacios de nombres y especificar para ellos los prefijos de espacio de
nombres.
Mediante la palabra clave Imports podrá definir espacios de nombres predeterminados y espacios de
nombres con un prefijo.
Observe que el espacio de nombres XML predeterminado sólo puede aplicarse a los elementos, no a los
atributos. De forma predeterminada, los atributos nunca se encuentran en un espacio de nombres. No
obstante, puede utilizar un prefijo de espacio de nombres para colocar un atributo en un espacio de
nombres.
Ejemplo
Este ejemplo crea un documento que contiene un espacio de nombres.
Imports <xmlns:aw="http://www.adventure-works.com">
Module Module1
Sub Main()
Dim root As XElement = _
<aw:Root>
<aw:Child aw:Att="attvalue"/>
</aw:Root>
Console.WriteLine(root)
End Sub
End Module
Este ejemplo crea un documento que contiene dos espacios de nombres, uno de los cuales es el
predeterminado.
Imports <xmlns="http://www.adventure-works.com">
Imports <xmlns:fc="www.fourthcoffee.com">
Module Module1
Sub Main()
Dim root As XElement = _
<Root>
<Child Att="attvalue"/>
<fc:Child2>child2 content</fc:Child2>
</Root>
Console.WriteLine(root)
End Sub
End Module
El ejemplo siguiente crea un documento que contiene varios espacios de nombres, todos ellos con
prefijos de espacio de nombres.
Cuando se serializa un árbol XML, LINQ a XML genera atributos que declaran los espacios de nombres a
medida que se necesitan, de forma que cada elemento se encuentre en el espacio de nombres
designado.
Imports <xmlns:aw="http://www.adventure-works.com">
Imports <xmlns:fc="www.fourthcoffee.com">
Module Module1
Sub Main()
Dim root As XElement = _
<aw:Root>
<fc:Child>
<aw:DifferentChild>other content</aw:DifferentChild>
</fc:Child>
<aw:Child2>c2 content</aw:Child2>
<fc:Child3>c3 content</fc:Child3>
</aw:Root>
Console.WriteLine(root)
End Sub
End Module
Ejemplo
Descripción
Este ejemplo declara dos espacios de nombre. Especifica que el espacio de nombres
http://www.adventure-works.com tiene el prefijo aw y que el espacio de nombres www.fourthcoffee.com
tiene el prefijo fc.
Código
Imports <xmlns:aw="http://www.adventure-works.com">
Imports <xmlns:fc="www.fourthcoffee.com">
Module Module1
Sub Main()
Dim root As XElement = _
<aw:Root>
<fc:Child>
<aw:DifferentChild>other content</aw:DifferentChild>
</fc:Child>
<aw:Child2>c2 content</aw:Child2>
<fc:Child3>c3 content</fc:Child3>
</aw:Root>
Console.WriteLine(root)
End Sub
End Module
espacio de nombres XML que usa un prefijo o bien puede declarar un espacio de nombres XML
predeterminado.
Esta capacidad es útil en dos situaciones. En primer lugar, los espacios de nombres declarados en los
literales XML no se mantienen en expresiones incrustadas. La declaración de espacios de nombres
globales reduce la cantidad de trabajo que tiene que realizar para usar expresiones incrustadas con
espacios de nombres. En segundo lugar, debe declarar espacios de nombres globales para usar espacios
de nombres con propiedades XML.
Puede declarar espacios de nombres globales en el nivel del proyecto. También puede declarar espacios
de nombres globales en el nivel del módulo, lo que invalida los espacios de nombres globales del nivel
del proyecto. Finalmente, puede invalidad los espacios de nombres globales en un literal XML.
Cuando se utilizan literales XML o propiedades XML que están en espacios de nombres que se declaran
globalmente se puede ver el nombre expandido de las propiedades o los literales desplazándose por
encima de ellos en Visual Studio. Verá el nombre expandido en una información sobre herramientas.
Puede obtener un objeto XNamespace que se corresponde con un espacio de nombres global usando el
método GetXmlNamespace.
Un mejor enfoque es declarar un espacio de nombres predeterminado global. A continuación puede usar
los literales XML sin declarar espacios de nombres. El XML resultante estará en el espacio de nombres
predeterminado declarado globalmente.
Imports <xmlns="http://www.adventure-works.com">
Module Module1
Sub Main()
Dim root As XElement = <Root>
<%= <Child/> %>
</Root>
Console.WriteLine(root)
End Sub
End Module
Usar espacios de nombres con propiedades XML
Si está trabajando con un árbol XML que está en un espacio de nombres y utiliza propiedades XML, debe
usar un espacio de nombres global para que las propiedades XML también estén en el espacio de
nombres correcto. En el ejemplo siguiente se declara un árbol XML en un espacio de nombres. Después
se muestra el número de elementos Child.
Dim root As XElement = _
<Root xmlns="http://www.adventure-works.com">
<Child>content</Child>
</Root>
Console.WriteLine(root.<Child>.Count())
Este ejemplo indica que no hay elementos Child. Genera el siguiente resultado:
0
No obstante, si declara un espacio de nombres global predeterminado, el literal XML y la propiedad XML
estarán en el espacio de nombres global predeterminado:
Imports <xmlns="http://www.adventure-works.com">
Module Module1
Sub Main()
Dim root As XElement = _
<Root>
<Child>content</Child>
</Root>
Console.WriteLine(root.<Child>.Count())
End Sub
End Module
Este ejemplo indica que hay un elemento Child. Genera el siguiente resultado:
1
Si declara un espacio de nombres global que no tenga un prefijo, puede usar el prefijo para literales XML
y propiedades XML:
Imports <xmlns:aw="http://www.adventure-works.com">
Module Module1
Sub Main()
Dim root As XElement = _
<aw:Root>
<aw:Child>content</aw:Child>
</aw:Root>
Console.WriteLine(root.<aw:Child>.Count())
End Sub
End Module
XNamespace y espacios de nombres globales
Puede obtener un objeto XNamespace usando el método GetXmlNamespace:
Imports <xmlns:aw="http://www.adventure-works.com">
Module Module1
Sub Main()
Dim root As XElement = <aw:Root/>
Dim xn As XNamespace = GetXmlNamespace(aw)
Console.WriteLine(xn)
End Sub
End Module
Este ejemplo genera el siguiente resultado:
http://www.adventure-works.com
Para C#, el enfoque más común consiste en inicializar un XNamespace usando una cadena que contiene
la dirección URI y después usar la sobrecarga del operador de suma para combinar el espacio de
nombres con el nombre local.
Cuando se utiliza Visual Basic, el enfoque más común consiste en definir un espacio de nombres global y
después utilizar literales XML y propiedades XML que usen el espacio de nombres global. Puede definir un
espacio de nombres predeterminado global, en cuyo caso los elementos de los literales XML estarán en el
espacio de nombres de forma predeterminada. De forma alternativa puede definir un espacio de nombres
global con un prefijo y después usar el prefijo según se requiera en los literales XML y en las propiedades
XML. Al igual que con otras formas de XML, los atributos no están nunca en ningún espacio de nombres
de forma predeterminada.
Ejemplo
En el ejemplo siguiente se crea un árbol XML que está en un espacio de nombres predeterminado. A
continuación recupera una recopilación de elementos.
Imports <xmlns="http://www.adventure-works.com">
Module Module1
Sub Main()
Dim root As XElement = _
<Root>
<Child>1</Child>
<Child>2</Child>
<Child>3</Child>
<AnotherChild>4</AnotherChild>
<AnotherChild>5</AnotherChild>
<AnotherChild>6</AnotherChild>
</Root>
Dim c1 As IEnumerable(Of XElement) = _
From el In root.<Child> _
Select el
For Each el As XElement In c1
Console.WriteLine(el.Value)
Next
End Sub
End Module
Este ejemplo genera el siguiente resultado:
1
2
3
Si está escribiendo consultas en C# en un árbol XML que usa un espacio de nombres con un prefijo, el
enfoque que utilice al escribir la consulta es idéntico al enfoque que usará cuando consulte XML que esté
en un espacio de nombres predeterminado. Aunque puede controlar prefijos durante la serialización y
aunque puede determinar la dirección URI del espacio de nombres con un prefijo y puede determinar el
prefijo de un espacio de nombres, para la mayor parte los prefijos de espacio de nombre están
atenuados en la interfaz de programación de LINQ a XML.
No obstante, cuando utilice literales XML y escriba consultas en Visual Basic en un árbol XML que utilice
un espacio de nombres con un prefijo, el enfoque que utiliza es diferente. Normalmente se importa el
espacio de nombres con un prefijo utilizando la instrucción Imports. A continuación se utiliza el prefijo en
los nombres del elemento y del atributo y también se utiliza el prefijo cuando se utilizan las propiedades
XML que tienen acceso al árbol XML.
En el ejemplo siguiente se crea un árbol XML que está en un espacio de nombres con un prefijo. A
continuación recupera una recopilación de elementos.
Imports <xmlns:aw="http://www.adventure-works.com">
Module Module1
Sub Main()
Dim root As XElement = _
<aw:Root>
<aw:Child>1</aw:Child>
<aw:Child>2</aw:Child>
<aw:Child>3</aw:Child>
<aw:AnotherChild>4</aw:AnotherChild>
<aw:AnotherChild>5</aw:AnotherChild>
<aw:AnotherChild>6</aw:AnotherChild>
</aw:Root>
Dim c1 As IEnumerable(Of XElement) = _
From el In root.<aw:Child> _
Select el
For Each el As XElement In c1
Console.WriteLine(CInt(el))
Next
End Sub
End Module
Este ejemplo genera el siguiente resultado:
1
2
3
Puede controlar diversos aspectos del proceso de serialización. Por ejemplo, puede decidir si desea
agregar una sangría al XML serializado y si desea escribe una declaración XML.
Un caso muy común es aquel en el que se leen datos XML con sangría, se crea un árbol XML en memoria
sin ningún nodo de texto con espacios en blanco (es decir, sin preservar los espacios en blanco), se
realizan ciertas operaciones sobre el XML y éste se guarda con sangría. Si se serializa el XML con
formato, sólo se preservan en el XML aquellos espacios en blanco más significativos. Éste es el
comportamiento predeterminado en LINQ a XML.
Otro caso muy común es aquel en el que se lee y se modifica XML en el que ya se ha aplicado sangría
intencionadamente. Es posible que no desee modificar esta sangría de ninguna forma. En LINQ a XML,
puede conseguirlo si preserva los espacios en blanco a la hora de cargar o analizar el XML y si deshabilita
el formato cuando serialice el XML.
Tratamiento de los espacios en blanco en los métodos que serializan árboles XML
Los siguientes métodos de las clases XElement y XDocument serializan un árbol XML. Es posible serializar
un árbol XML en un archivo, un TextReader o un XmlReader. El método ToString serializa en una
cadena..
XElement..::.Save
XDocument..::.Save
XElement..::.ToString
XDocument..::.ToString
Si el método no recibe un SaveOptions como argumento, entonces el método realizará un formato
(sangría) del XML serializado. En ese caso, se descartarán todos los espacios en blanco del árbol XML que
no sean significativos.
Si el método recibe un SaveOptions como argumento, opcionalmente el método podrá no dar formato
(sangría) al XML serializado. En ese caso, se preservarán todos los espacios en blanco del árbol XML.
Tenga en cuenta que la serialización en un File o TextWriter genera una declaración XML. Cuando
serializa en XmlWriter, la configuración del escritor (especificada en un objeto XmlWriterSettings)
determina si se genera una declaración XML.
Puede serializar cualquier componente XML, entre los que se incluyen XDocument y XElement, en una
cadena con el método ToString.
Si desea suprimir el formato al serializar en una cadena, puede usar el método XNode..::.ToString.
espacios en blanco más insignificantes. Para serializar con formato, use una de las sobrecargas de los
métodos siguientes que no toman SaveOptions como argumento:
XDocument..::.Save
XElement..::.Save
Si desea que la opción no aplique una sangría y conserve los espacios en blanco más insignificantes del
árbol XML, use una de las sobrecargas de los métodos siguientes que toman SaveOptions como
argumento:
XDocument..::.Save
XElement..::.Save
End Using
Console.WriteLine(newTree)
Antes de poder escribir consultas, debe entender los ejes LINQ a XML. Hay dos clases de métodos de eje.
En primer lugar, hay métodos a los que se llama en un solo objeto XElement, un objeto XDocument o un
objeto XNode. Estos métodos funcionan en un objeto único y devuelven una colección de objetos
XElement, XAttribute o XNode. En segundo lugar, existen métodos de extensión que funcionan en
colecciones y devuelven colecciones. Los métodos de extensión enumeran la colección de origen, llaman
al método de eje adecuado en cada elemento de la colección y concatenan los resultados.
Tal y como se describe en Información general acerca de la clase XElement, un objeto XElement
representa un único nodo de elemento. El contenido de un elemento puede ser complejo (a veces
conocido como contenido estructurado) o puede ser un elemento simple. Un elemento simple puede
estar vacío o contener un valor. Si el nodo incluye contenido estructurado, puede utilizar diferentes
métodos del eje para recuperar enumeraciones con los elementos descendientes. Los métodos de ejes
que se utilizan con más frecuencia son Elements y Descendants.
Además de los métodos de ejes, que devuelven colecciones, existen dos métodos más que utilizará con
regularidad en consultas LINQ a XML. El método Element devuelve un único XElement. El método
Attribute devuelve un único XAttribute.
Por diversas razones, las consultas de LINQ ofrecen el mecanismo más potente para examinar árboles,
extraer datos de ellos y para transformarlos. Las consultas de LINQ trabajan sobre objetos que
implementan IEnumerable<(Of <(T>)>), y los ejes LINQ a XML devuelven colecciones IEnumerable<(Of
<(T>)>) de XElement y IEnumerable<(Of <(T>)>) de XAttribute. Para poder realizar las consultas,
necesitará estas colecciones.
A parte de los métodos de ejes que permiten recuperar colecciones de elementos y atributos, existen
métodos de ejes que le permiten iterar por un árbol con gran detalle. Por ejemplo, en vez de tratar
directamente con los elementos y los atributos, podría trabajar con los nodos del árbol. Los nodos tienen
un nivel de granularidad mayor que los elementos y atributos. Si trabaja con nodos, podrá examinar
comentarios XML, nodos de texto, instrucciones de procesamiento, etc. Esta funcionalidad resultaría
interesante, por ejemplo, para una persona que estuviese escribiendo un procesador de textos y quisiera
guardar los documentos en formato XML. No obstante, la mayoría de programadores en XML se centran
más en los elementos, los atributos y sus valores.
Método Descripción
Método Descripción
XContainer..::.Element Devuelve el objeto del primer elemento XElement secundario que tenga el
XName especificado.
Método Descripción
Método Descripción
Ejemplo
Este ejemplo recorre en iteración los elementos secundarios del elemento purchaseOrder.
Dim po As XElement = XElement.Load("PurchaseOrder.xml")
Dim childElements As IEnumerable(Of XElement)
childElements = _
From el In po.Elements() _
Select el
For Each el As XElement In childElements
Console.WriteLine("Name: " & el.Name.ToString())
Next
Sin embargo, con C#, la conversión suele ser el mejor método. Si convierte el elemento o el atributo a
un tipo que admite valores NULL, resulta más fácil escribir el código al recuperar el valor de un elemento
(o atributo) que podría existir o no. El último ejemplo de este tema muestra este comportamiento. No
obstante, no puede establecer el contenido de un elemento mediante la conversión del mismo modo que
con la propiedad XElement..::.Value.
Ejemplo
Para recuperar el valor de un elemento, basta con convertir el objeto XElement al tipo deseado. Siempre
puede convertir un elemento a una cadena, como se indica a continuación:
Dim e As XElement = <StringElement>abcde</StringElement>
Console.WriteLine(e)
Console.WriteLine("Value of e:" & e.Value)
También puede convertir elementos a tipos diferentes a una cadena. Por ejemplo, si tiene un elemento
que contiene un entero, puede convertirlo a int, tal como se muestra en el código siguiente:
Dim e As XElement = <Age>44</Age>
Console.WriteLine(e)
Console.WriteLine("Value of e:" & CInt(e))
LINQ a XML proporciona operadores de conversión explícita para los tipos de datos siguientes: string,
bool, bool?, int, int?, uint, uint?, long, long?, ulong, ulong?, float, float?, double, double?,
decimal, decimal?, DateTime, DateTime?, TimeSpan, TimeSpan?, GUID y GUID?.
LINQ a XML proporciona los mismos operadores de conversión para los objetos XAttribute.
End If
Console.WriteLine("v2:{0}", IIf(Not (v2.HasValue), "element does not
exist", v2))
Dim e3 As XElement = root.Element("Child3")
Dim v3 As String
If (e3 Is Nothing) Then
v3 = Nothing
Else
v3 = e3.Value
End If
Console.WriteLine("v3:{0}", IIf(v3 Is Nothing, "element does not exist",
v3))
Dim e4 As XElement = root.Element("Child4")
Dim v4 As Nullable(Of Integer)
If (e4 Is Nothing) Then
v4 = Nothing
Else
v4 = e4.Value
End If
Console.WriteLine("v4:{0}", IIf(Not (v4.HasValue), "element does not
exist", v4))
Por lo general, puede escribir un código más simple al usar la conversión para recuperar el contenido de
los elementos y los atributos.
Ejemplo
Este ejemplo muestra cómo recuperar una colección de descendientes. Sin embargo, éstos se filtran, y la
colección sólo contiene descendientes con el nombre especificado.
Dim po As XElement = XElement.Load("PurchaseOrder.xml")
Dim items As IEnumerable(Of XElement) = _
From el In po...<ProductName> _
Select el
For Each prdName As XElement In items
Console.WriteLine(prdName.Name.ToString & ":" & prdName.Value)
Next
Los otros métodos que devuelven IEnumerable<(Of <(T>)>) de colecciones XElement siguen el mismo
patrón. Sus firmas son similares a Elements y Descendants. A continuación, se incluye una lista completa
de los métodos que tienen firmas similares:
Ancestors
Descendants
Elements
ElementsAfterSelf
ElementsBeforeSelf
AncestorsAndSelf
DescendantsAndSelf
El siguiente ejemplo muestra la misma consulta sobre un XML que se encuentra en un espacio de
nombres.
Imports <xmlns:aw="http://www.adventure-works.com">
Module Module1
Sub Main()
Dim po As XElement = XElement.Load("PurchaseOrderInNamespace.xml")
Dim items As IEnumerable(Of XElement) = _
From el In po...<aw:ProductName> _
Select el
For Each prdName As XElement In items
Console.WriteLine(prdName.Name.ToString & ":" & prdName.Value)
Next
End Sub
End Module
Hay dos ejes con el nombre Elements que devuelven una colección de elementos: el método
XContainer..::.Elements y el método Extensions..::.Elements. Puede combinar estos dos ejes para
encontrar todos los elementos de un nombre especificado en una profundidad determinada del árbol.
Ejemplo
Este ejemplo usa los dos ejes descritos anteriormente para encontrar todos los elementos Name de todos
los elementos Address de todos los elementos PurchaseOrder.
Dim purchaseOrders As XElement = XElement.Load("PurchaseOrders.xml")
Dim names As IEnumerable(Of XElement) = _
From el In purchaseOrders.<PurchaseOrder>.<Address>.<Name> _
Select el
For Each e As XElement In names
Console.WriteLine(e)
Next
Esto funciona porque una de las implementaciones del eje Elements es un método de extensión en
IEnumerable<(Of <(T>)>) de XContainer. XElement se deriva de XContainer, de modo que puede llamar
al método Extensions..::.Elements según los resultados de una llamada al método
XContainer..::.Elements.
A veces, desea recuperar todos los elementos de una profundidad determinada cuando es posible que
intervengan (o no) antecesores. Por ejemplo, en el siguiente documento, podría recuperar todos los
elementos ConfigParameter que son secundarios del elemento Customer, pero no el elemento
ConfigParameter que es un secundario del elemento Root.
Xml
<Root>
<ConfigParameter>RootConfigParameter</ConfigParameter>
<Customer>
<Name>Frank</Name>
<Config>
<ConfigParameter>FirstConfigParameter</ConfigParameter>
</Config>
</Customer>
<Customer>
<Name>Bob</Name>
<!--This customer doesn't have a Config element-->
</Customer>
<Customer>
<Name>Bill</Name>
<Config>
<ConfigParameter>SecondConfigParameter</ConfigParameter>
</Config>
</Customer>
</Root>
Para ello, puede usar el eje Extensions..::.Elements de la siguiente manera:
Dim root As XElement = XElement.Load("Irregular.xml")
Dim configParameters As IEnumerable(Of XElement) = _
root.<Customer>.<Config>.<ConfigParameter>
For Each cp As XElement In configParameters
Console.WriteLine(cp)
Next
El siguiente ejemplo muestra la misma técnica para XML que se encuentre en un espacio de nombres.
Imports <xmlns:aw="http://www.adventure-works.com">
Module Module1
Sub Main()
Dim purchaseOrders As XElement =
XElement.Load("PurchaseOrdersInNamespace.xml")
Dim names As IEnumerable(Of XElement) = From el In
purchaseOrders.<aw:PurchaseOrder>.<aw:Address>.<aw:Name> _
Select el
For Each e As XElement In names
Console.WriteLine(e)
Next
End Sub
End Module
Si desea recuperar un único elemento secundario de Visual Basic, un enfoque común consiste en utilizar
la propiedad XML y después recuperar el primer elemento mediante una notación de indizador de matriz.
Ejemplo
En el siguiente ejemplo se muestra el uso del método Element. Este ejemplo toma un árbol XML con el
nombre po y busca el primer elemento con el nombre Comment.
El ejemplo de Visual Basic muestra el uso de la notación del indizador de matriz para recuperar un
elemento único.
Dim po As XElement = XElement.Load("PurchaseOrder.xml")
Dim e As XElement = po.<DeliveryNotes>(0)
Console.WriteLine(e)
El siguiente ejemplo muestra el mismo código sobre un XML que se encuentra en un espacio de nombres
Imports <xmlns:aw="http://www.adventure-works.com">
Module Module1
Sub Main()
Dim po As XElement = XElement.Load("PurchaseOrderInNamespace.xml")
Dim e As XElement = po.<aw:DeliveryNotes>(0)
Console.WriteLine(e)
End Sub
End Module
Ejemplo
En el ejemplo siguiente se muestra cómo procesar una iteración en la colección de atributos de un
elemento.
Dim val = _
<Value ID="1243" Type="int" ConvertableTo="double">100</Value>
Dim listOfAttributes As IEnumerable(Of XAttribute) = _
From att In val.Attributes() Select att
For Each att As XAttribute In listOfAttributes
Console.WriteLine(att)
Next
Ejemplo
El siguiente ejemplo utiliza el método Attribute.
Dim cust As XElement = <PhoneNumbers>
<Phone type="home">555-555-5555</Phone>
<Phone type="work">555-555-6666</Phone>
</PhoneNumbers>
Dim elList = From el In cust...<Phone>
For Each e As XElement In elList
Console.WriteLine(e.@type)
Next
Este ejemplo encuentra todos los descendientes en el árbol cuyo nombre es Phone y, a continuación,
encuentra aquel atributo cuyo nombre es type.
Si desea recuperar el valor del atributo, puede convertirlo, de la misma forma que lo haría con los
objetos XElement. En el siguiente ejemplo se muestra cómo hacerlo.
Dim cust As XElement = <PhoneNumbers>
<Phone type="home">555-555-5555</Phone>
<Phone type="work">555-555-6666</Phone>
</PhoneNumbers>
Dim elList As IEnumerable(Of XElement) = From el In cust...<Phone> _
Select el
For Each el As XElement In elList
Console.WriteLine(el.@type)
Next
LINQ a XML proporciona operadores de conversión explícita a string, bool, bool?, int, int?, uint,
uint?, long, long?, ulong, ulong?, float, float?, double, double?, decimal, decimal?, DateTime,
DateTime?, TimeSpan, TimeSpan?, GUID y GUID? para la clase XAttribute.
El siguiente ejemplo muestra el mismo código para un atributo que se encuentre en un espacio de
nombres.
Imports <xmlns:aw="http://www.adventure-works.com">
Module Module1
Sub Main()
Dim cust As XElement = _
<aw:PhoneNumbers>
<aw:Phone aw:type="home">555-555-5555</aw:Phone>
<aw:Phone aw:type="work">555-555-6666</aw:Phone>
</aw:PhoneNumbers>
Dim elList As IEnumerable(Of XElement) = _
From el In cust...<aw:Phone> _
Select el
For Each el As XElement In elList
Console.WriteLine(el.@aw:type)
Next
End Sub
End Module
Ejemplo
Para recuperar el valor de un atributo, basta con convertir el objeto XAttribute al tipo deseado.
En Visual Basic, puede usar la propiedad del atributo integrado para recuperar el valor de un atributo.
Dim root As XElement = <Root Attr="abcde"/>
Console.WriteLine(root)
Dim str As String = root.@Attr
Console.WriteLine(str)
En Visual Basic, puede usar la propiedad del atributo integrado para establecer el valor de un atributo.
Asimismo, si usa la propiedad del atributo integrado para establecer el valor de un atributo que no
existe, se creará el atributo.
Dim root As XElement = <Root Att1="content"/>
root.@Att1 = "new content"
root.@Att2 = "new attribute"
Console.WriteLine(root)
El siguiente ejemplo muestra cómo recuperar el valor de un atributo, en el que el atributo se encuentra
en un espacio de nombres.
Imports <xmlns:aw="http://www.adventure-works.com">
Module Module1
Sub Main()
Dim root As XElement = <aw:Root aw:Attr="abcde"/>
Dim str As String = root.@aw:Attr
Console.WriteLine(str)
End Sub
End Module
En esta sección
Tema Descripción
Propiedades de eje Proporciona el acceso a los elementos secundarios de uno de los siguientes:
secundario XML un objeto XElement, un objeto XDocument, una colección de objetos
XElement o una colección de objetos XDocument. Este eje es equivalente al
eje Elements.
Propiedad de eje Proporciona acceso a los descendientes de: un objeto XElement, un objeto
descendiente XML XDocument o una recopilación de objetos XElement. Este eje es equivalente al
eje Descendants.
Propiedad Axis Proporciona acceso a un atributo de un objeto XElement. Este eje es en líneas
para atributo XML generales equivalente al eje Attribute. Este eje difiere del eje Attribute en que
devuelve el valor del atributo, no un objeto XAttribute.
Una vez que haya creado una instancia del árbol XML, escribir consultas será la manera más eficaz de
extraer datos de dicho árbol. Asimismo, la consulta, combinada con la construcción funcional, permite
generar un documento XML nuevo que tiene una forma diferente del documento original.
Ejemplo
El ejemplo muestra cómo buscar el elemento Address que tiene un atributo Type con un valor de
"Billing".
Dim root As XElement = XElement.Load("PurchaseOrder.xml")
Dim address As IEnumerable(Of XElement) = From el In root.<Address> _
Where el.@Type = "Billing" Select el
For Each el As XElement In address
Console.WriteLine(el)
Next
Tenga en cuenta que la versión de Visual Basic de este ejemplo utiliza la Propiedades de eje secundario
XML, la Propiedad Axis para atributo XML y la Propiedad Value de XML.
El siguiente ejemplo muestra la misma consulta sobre un XML que se encuentra en un espacio de
nombres.
Imports <xmlns:aw='http://www.adventure-works.com'>
Module Module1
Sub Main()
Dim root As XElement = Element.Load("PurchaseOrderInNamespace.xml")
Dim address As IEnumerable(Of XElement) = From el In root.<aw:Address> _
Where el.@aw:Type = "Billing" Select el
For Each el As XElement In address
Console.WriteLine(el)
Next
End Sub
End Module
Ejemplo
El ejemplo busca el elemento Test que tenga un elemento secundario CommandLine con el valor de
"Examp2.EXE".
Dim root As XElement = XElement.Load("TestConfig.xml")
Dim tests As IEnumerable(Of XElement) = From el In root.<Test> _
Where el.<CommandLine>.Value = "Examp2.EXE" Select el
For Each el as XElement In tests
Console.WriteLine(el.@TestId)
Next
Tenga en cuenta que la versión de Visual Basic de este ejemplo utiliza la Propiedades de eje secundario
XML, la Propiedad Axis para atributo XML y la Propiedad Value de XML.
El siguiente ejemplo muestra la misma consulta sobre un XML que se encuentra en un espacio de
nombres.
Imports <xmlns='http://www.adatum.com'>
Module Module1
Sub Main()
Dim root As XElement = XElement.Load("TestConfigInNamespace.xml")
Dim tests As IEnumerable(Of XElement) = From el In root.<Test> _
Where el.<CommandLine>.Value = "Examp2.EXE" Select el
For Each el As XElement In tests
Console.WriteLine(el.@TestId)
Next
End Sub
End Module
Este primer ejemplo carga un árbol XML utilizando Load. A continuación, consulta los elementos
secundarios de la raíz del árbol.
' Create a simple document and write it to a file
File.WriteAllText("Test.xml", "<Root>" + Environment.NewLine + _
" <Child1>1</Child1>" + Environment.NewLine + _
" <Child2>2</Child2>" + Environment.NewLine + _
" <Child3>3</Child3>" + Environment.NewLine + _
"</Root>")
Console.WriteLine("Querying tree loaded with XElement.Load")
Console.WriteLine("----")
Dim doc As XElement = XElement.Load("Test.xml")
Dim childList As IEnumerable(Of XElement) = From el In doc.Elements() _
Select el
For Each e As XElement In childList
Console.WriteLine(e)
Next
El siguiente ejemplo es igual al anterior, con la diferencia de que el árbol XML se carga en un XDocument
en vez de un XElement.
' Create a simple document and write it to a file
File.WriteAllText("Test.xml", "<Root>" + Environment.NewLine + _
" <Child1>1</Child1>" + Environment.NewLine + _
" <Child2>2</Child2>" + Environment.NewLine + _
" <Child3>3</Child3>" + Environment.NewLine + _
"</Root>")
Console.WriteLine("Querying tree loaded with XDocument.Load")
Console.WriteLine("----")
Dim doc As XDocument = XDocument.Load("Test.xml")
Dim childList As IEnumerable(Of XElement) = From el In doc.Elements() _
Select el
For Each e As XElement In childList
Console.WriteLine(e)
Next
Observe que la misma consulta devolvió el nodo Root en vez de los tres nodos secundarios.
Una forma de afrontar este asunto es utilizar la propiedad Root antes de obtener acceso a los métodos
de los ejes, tal y como se indica a continuación:
' Create a simple document and write it to a file
File.WriteAllText("Test.xml", "<Root>" + Environment.NewLine + _
" <Child1>1</Child1>" + Environment.NewLine + _
" <Child2>2</Child2>" + Environment.NewLine + _
" <Child3>3</Child3>" + Environment.NewLine + _
"</Root>")
Console.WriteLine("Querying tree loaded with XDocument.Load")
Console.WriteLine("----")
Dim doc As XDocument = XDocument.Load("Test.xml")
Dim childList As IEnumerable(Of XElement) = From el In
doc.Root.Elements() Select el
For Each e As XElement In childList
Console.WriteLine(e)
Next
Ahora, esta consulta genera el mismo resultado que la consulta del árbol cuya raíz comienza en
XElement.
Ejemplo
El ejemplo siguiente muestra cómo encontrar descendientes según el nombre de elemento.
Dim root As XElement = _
<root>
<para>
<r>
Ejemplo
Este ejemplo utiliza el operador de consulta estándar First.
Dim root As XElement = _
<Root>
<Child1>
<GrandChild1>GC1 Value</GrandChild1>
</Child1>
<Child2>
<GrandChild2>GC2 Value</GrandChild2>
</Child2>
<Child3>
<GrandChild3>GC3 Value</GrandChild3>
</Child3>
<Child4>
<GrandChild4>GC4 Value</GrandChild4>
</Child4>
</Root>
Dim grandChild3 As String = (From el In root...<GrandChild3> _
Select el).First()
Console.WriteLine(grandChild3)
El siguiente ejemplo muestra la misma consulta sobre un XML que se encuentra en un espacio de
nombres.
Imports <xmlns:aw='http://www.adventure-works.com'>
Module Module1
Sub Main()
Dim root As XElement = _
<aw:Root>
<aw:Child1>
<aw:GrandChild1>GC1 Value</aw:GrandChild1>
</aw:Child1>
<aw:Child2>
<aw:GrandChild2>GC2 Value</aw:GrandChild2>
</aw:Child2>
<aw:Child3>
<aw:GrandChild3>GC3 Value</aw:GrandChild3>
</aw:Child3>
<aw:Child4>
<aw:GrandChild4>GC4 Value</aw:GrandChild4>
</aw:Child4>
</aw:Root>
Ejemplo
En este ejemplo se muestra cómo buscar todos los elementos PurchaseOrder que tienen un elemento
Address secundario que tiene un atributo Type igual a "Shipping" (envío) y un elemento State secundario
igual a "NY". Utiliza una consulta anidada en la cláusula Where y el operador Any devuelve true si la
colección tiene elementos en ella.
Dim root As XElement = XElement.Load("PurchaseOrders.xml")
Dim purchaseOrders As IEnumerable(Of XElement) = From el In
root.<PurchaseOrder> Where (From add In el.<Address> Where add.@Type =
"Shipping" And add.<State>.Value = "NY" Select add).Any()Select el
For Each el As XElement In purchaseOrders
Console.WriteLine(el.@PurchaseOrderNumber)
Next
El siguiente ejemplo muestra la misma consulta sobre un XML que se encuentra en un espacio de
nombres.
Imports <xmlns:aw='http://www.adventure-works.com'>
Module Module1
Sub Main()
Dim root As XElement = XElement.Load("PurchaseOrdersInNamespace.xml")
Dim purchaseOrders As IEnumerable(Of XElement) = From el In
root.<aw:PurchaseOrder> Where (From add In el.<aw:Address> Where
add.@aw:Type = "Shipping" And add.<aw:State>.Value = "NY" Select
add) .Any() Select el
For Each el As XElement In purchaseOrders
Console.WriteLine(el.@aw:PurchaseOrderNumber)
Next
End Sub
End Module
Ejemplo
Este ejemplo utiliza el método de extensión Elements.
<Type Value="Yes"/>
</Child4>
<Child5>
<Text>Child Five Text</Text>
</Child5>
</Root>
Dim cList As IEnumerable(Of String) = From typeElement In
root.Elements().<Type> Where typeElement.@Value = "Yes" Select
typeElement.Parent.<Text>.Value
Dim str As String
For Each str In cList
Console.WriteLine(str)
Next
End Sub
End Module
Ejemplo
En el ejemplo siguiente se crea un árbol XML con dos espacios de nombres. A continuación, recorre en
iteración los árboles y muestra los nombres de todos los elementos y atributos de esos espacios de
nombres.
Imports <xmlns:aw="http://www.adventure-works.com">
Imports <xmlns:fc="www.fourthcoffee.com">
Module Module1
Sub Main()
Dim xmlTree As XElement = _
<aw:Root>
<fc:Child1>abc</fc:Child1>
<fc:Child2>def</fc:Child2>
<aw:Child3>ghi</aw:Child3>
<fc:Child4>
<fc:GrandChild1>jkl</fc:GrandChild1>
<aw:GrandChild2>mno</aw:GrandChild2>
</fc:Child4>
</aw:Root>
Console.WriteLine("Nodes in the http://www.adventure-works.com
namespace")
Dim awElements As IEnumerable(Of XElement) = From el In
xmlTree.Descendants() Where (el.Name.Namespace =
GetXmlNamespace(aw)) Select el
For Each el As XElement In awElements
Console.WriteLine(el.Name.ToString())
Next
End Sub
End Module
El archivo XML al que tiene acceso la siguiente consulta contiene pedidos de compra en dos espacios de
nombres diferentes. La consulta crea un nuevo árbol con solo los elementos de uno de los espacios de
nombres.
Imports <xmlns:aw="http://www.adventure-works.com">
Module Module1
Sub Main()
Dim cpo As XDocument = XDocument.Load("ConsolidatedPurchaseOrders.xml")
Dim newTree As XElement = _
<Root>
<%= From el In cpo.Root.Elements() _
Where el.Name.Namespace = GetXmlNamespace(aw) _
Select el %>
</Root>
Console.WriteLine(newTree)
End Sub
End Module
Ejemplo
Dim root As XElement = XElement.Load("Data.xml")
Dim prices As IEnumerable(Of Decimal) = From el In root.<Data> Let price
= Convert.ToDecimal(el.<Price>.Value) Order By (price) Select price
For Each el As Decimal In prices
Console.WriteLine(el)
Next
El siguiente ejemplo muestra la misma consulta sobre un XML que se encuentra en un espacio de
nombres.
Imports <xmlns='http://www.adatum.com'>
Module Module1
Sub Main()
Dim root As XElement = XElement.Load("DataInNamespace.xml")
Dim prices As IEnumerable(Of Decimal) = From el In root.<Data>
Let price = Convert.ToDecimal(el.<Price>.Value) Order By (price)
Select price
For Each el As Decimal In prices
Console.WriteLine(el)
Next
End Sub
End Module
Ejemplo
En este ejemplo los resultados se ofrecen primero por código postal de envío y después por fecha de
ordenación.
Dim co As XElement = XElement.Load("CustomersOrders.xml")
Dim result = _
From c In co.<Orders>.<Order> _
Order By c.<ShipInfo>.<ShipPostalCode>.Value,
Convert.ToDateTime(c.<OrderDate>.Value) _
Select New With { _
.CustomerID = c.<CustomerID>.Value, _
.EmployeeID = c.<EmployeeID>.Value, _
.ShipPostalCode = c.<ShipInfo>.<ShipPostalCode>.Value, _
.OrderDate = Convert.ToDateTime(c.<OrderDate>.Value) _
}
For Each r In result
Console.WriteLine("CustomerID:{0} EmployeeID:{1} ShipPostalCode:{2}
OrderDate:{3:d}", r.CustomerID, r.EmployeeID, r.ShipPostalCode,
r.OrderDate)
Next
El siguiente ejemplo muestra la misma consulta sobre un XML que se encuentra en un espacio de
nombres.
Imports <xmlns='http://www.adventure-works.com'>
Module Module1
Sub Main()
Dim co As XElement = XElement.Load("CustomersOrdersInNamespace.xml")
Dim result = _
From c In co.<Orders>.<Order> _
Order By c.<ShipInfo>.<ShipPostalCode>.Value,
Convert.ToDateTime(c.<OrderDate>.Value) _
Select New With { _
.CustomerID = c.<CustomerID>.Value, _
.EmployeeID = c.<EmployeeID>.Value, _
.ShipPostalCode = c.<ShipInfo>.<ShipPostalCode>.Value, _
.OrderDate = Convert.ToDateTime(c.<OrderDate>.Value)}
For Each r In result
Console.WriteLine("CustomerID:{0} EmployeeID:{1}
ShipPostalCode:{2} OrderDate:{3:d}", _
r.CustomerID, r.EmployeeID, r.ShipPostalCode, r.OrderDate)
Next
End Sub
End Module
Ejemplo
El siguiente ejemplo utiliza la cláusula Let.
Dim root As XElement = XElement.Load("Data.xml")
Dim extensions As IEnumerable(Of Decimal) = From el In root.<Data> _
Puede hacerlo escribiendo una consulta y utilizando los resultados de la consulta en la cláusula where.
Si primero tiene que realizar una prueba con valores NULL y después probar el valor, resulta más
cómodo realizar la consulta en una cláusula let y después utilizar los resultados en la cláusula where.
Ejemplo
El siguiente ejemplo selecciona todos los elementos p que van seguidos inmediatamente por un elemento
ul.
Dim doc As XElement = _
<Root>
<p id='1'/>
<ul>abc</ul>
<Child>
<p id='2'/>
<notul/>
<p id='3'/>
<ul>def</ul>
<p id='4'/>
</Child>
<Child>
<p id='5'/>
<notul/>
<p id='6'/>
<ul>abc</ul>
<p id='7'/>
</Child>
</Root>
Dim items As IEnumerable(Of XElement) = From e In doc...<p> _
Let z = e.ElementsAfterSelf().FirstOrDefault() _
Where z IsNot Nothing AndAlso z.Name.LocalName = "ul" Select e
For Each e As XElement In items
Console.WriteLine("id = {0}", e.@<id>)
Next
El siguiente ejemplo muestra la misma consulta sobre un XML que se encuentra en un espacio de
nombres.
Imports <xmlns='http://www.adatum.com'>
Module Module1
Sub Main()
Dim doc As XElement = _
<Root>
<p id='1'/>
<ul>abc</ul>
<Child>
<p id='2'/>
<notul/>
<p id='3'/>
<ul>def</ul>
<p id='4'/>
</Child>
<Child>
<p id='5'/>
<notul/>
<p id='6'/>
<ul>abc</ul>
<p id='7'/>
</Child>
</Root>
Dim items As IEnumerable(Of XElement) = From e In doc...<p> _
Let z = e.ElementsAfterSelf().FirstOrDefault() _
Where z IsNot Nothing AndAlso z.Name =
GetXmlNamespace().GetName("ul") Select e
For Each e As XElement In items
Console.WriteLine("id = {0}", e.@<id>)
Next
End Sub
End Module
Uno de los problemas más habituales al consultar árboles XML es que si el árbol XML tiene un espacio de
nombres predeterminado, el desarrollador a veces escribe la consulta como si el código XML no estuviera
en un espacio de nombres.
El primer conjunto de ejemplos de este tema muestra una forma habitual de cargar XML en un espacio
de nombres predeterminado con una consulta incorrecta.
El segundo conjunto de ejemplos muestra las correcciones necesarias para que pueda consultar el código
XML en un espacio de nombres.
Ejemplo
Este ejemplo muestra la creación de XML en un espacio de nombres, y una consulta que devuelve un
conjunto de resultados vacío.
Dim root As XElement = _
<Root xmlns='http://www.adventure-works.com'>
<Child>1</Child>
<Child>2</Child>
<Child>3</Child>
<AnotherChild>4</AnotherChild>
<AnotherChild>5</AnotherChild>
<AnotherChild>6</AnotherChild>
</Root>
Dim c1 As IEnumerable(Of XElement) = From el In root.<Child> Select el
Console.WriteLine("Result set follows:")
For Each el As XElement In c1
Console.WriteLine(el.Value)
Next
Console.WriteLine("End of result set")
Este ejemplo muestra la creación de XML en un espacio de nombres, y una consulta que se codifica
correctamente.
Ejemplo
La versión de C# de este ejemplo usa una forma de construcción funcional en la que una consulta
proyecta nuevos objetos XElement y la recopilación resultante se pasa como argumento al constructor
del objeto raíz XElement.
La versión de Visual Basic de este ejemplo usa literales de XML y una consulta en una expresión
incrustada. La consulta proyecta nuevos objetos XElement, que se convierten en el contenido nuevo para
el objeto Root XElement.
Dim dict As Dictionary(Of String, String) = New Dictionary(Of String,
String)()
dict.Add("Child1", "Value1")
dict.Add("Child2", "Value2")
dict.Add("Child3", "Value3")
dict.Add("Child4", "Value4")
Dim root As XElement = _
<Root>
<%= From keyValue In dict _
Select New XElement(keyValue.Key, keyValue.Value) %>
</Root>
Console.WriteLine(root)
El siguiente código genera un diccionario de XML.
Dim root As XElement = _
<Root>
<Child1>Value1</Child1>
<Child2>Value2</Child2>
<Child3>Value3</Child3>
<Child4>Value4</Child4>
</Root>
Dim dict As Dictionary(Of String,String)=New Dictionary(Of String,String)
For Each el As XElement In root.Elements
dict.Add(el.Name.LocalName, el.Value)
Next
For Each str As String In dict.Keys
Console.WriteLine("{0}:{1}", str, dict(str))
Next
A veces, deberá cambiar la forma de un elemento XML. Por ejemplo, es posible que deba enviar un
documento XML a otro sistema que requiere nombres de elemento y atributo diferentes. Podría revisar el
documento y eliminar y cambiar el nombre de los elementos necesarios, pero el uso de la construcción
funcional proporciona un código más legible y fácil de mantener.
El primer ejemplo cambia la organización del documento XML. Mueve los elementos complejos de una
ubicación del árbol a otra.
El segundo ejemplo de este tema crea un documento XML con una forma diferente a la del documento de
origen. Cambia el uso de mayúsculas y minúsculas de los nombres de elemento, cambia el nombre de
algunos elementos y deja algunos de los elementos del árbol de origen fuera del árbol transformado.
Ejemplo
El código siguiente cambia la forma de un archivo XML con las expresiones de consulta incrustadas.
El documento XML de origen de este ejemplo contiene un elemento Customers en el elemento Root que
contiene todos los clientes. También contiene un elemento Orders en el elemento Root que contiene
todos los pedidos. Este ejemplo crea un nuevo árbol XML en el que los pedidos de cada cliente se
incluyen en un elemento Orders dentro del elemento Customer. El documento original también contiene
un elemento CustomerID en el elemento Order; este elemento se quitará del documento con la forma
cambiada.
Dim co As XElement = XElement.Load("CustomersOrders.xml")
Dim newCustOrd = _
<Root>
<%= From cust In co.<Customers>.<Customer> _
Select _
<Customer>
<%= cust.Attributes() %>
<%= cust.Elements() %>
<Orders>
<%= From ord In co.<Orders>.<Order> _
Where ord.<CustomerID>.Value = cust.@CustomerID _
Select _
<Order>
<%= ord.Attributes() %>
<%= ord.<EmployeeID> %>
<%= ord.<OrderDate> %>
<%= ord.<RequiredDate> %>
<%= ord.<ShipInfo> %>
</Order> _
%>
</Orders>
</Customer> _
%>
</Root>
Console.WriteLine(newCustOrd)
Este ejemplo cambia el nombre de algunos elementos y convierte algunos atributos en elementos.
El código llama a ConvertAddress, que devuelve una lista de objetos XElement. El argumento del método
es una consulta que determina el elemento complejo Address en el que el atributo Type tiene un valor
"Shipping".
Function ConvertAddress(ByVal add As XElement) As IEnumerable(Of
XElement)
Dim fragment = New List(Of XElement)
fragment.Add(<NAME><%= add.<Name>.Value %></NAME>)
fragment.Add(<STREET><%= add.<Street>.Value %></STREET>)
fragment.Add(<CITY><%= add.<City>.Value %></CITY>)
fragment.Add(<ST><%= add.<State>.Value %></ST>)
fragment.Add(<POSTALCODE><%= add.<Zip>.Value %></POSTALCODE>)
fragment.Add(<COUNTRY><%= add.<Country>.Value %></COUNTRY>)
Return fragment
End Function
Sub Main()
Dim po As XElement = XElement.Load("PurchaseOrder.xml")
Dim newPo As XElement = _
<PO>
<ID><%= po.@PurchaseOrderNumber %></ID>
<DATE><%= CDate(po.@OrderDate) %></DATE>
<%= ConvertAddress((From el In po.<Address> _
Where el.@Type = "Shipping" Select el).First())%>
</PO>
Console.WriteLine(newPo)
End Sub
Ejemplo
En el ejemplo siguiente, se define un nuevo tipo, Customer. La expresión de consulta crea una instancia
de nuevos objetos Customer en la cláusula Select. Esto hace que el tipo de la expresión de consulta sea
IEnumerable<(Of <(T>)>) de Customer.
Public Class Customer
Private customerIDValue As String
Public Property CustomerID() As String
Get
Return customerIDValue
End Get
Set(ByVal value As String)
customerIDValue = value
End Set
End Property
Private companyNameValue As String
Public Property CompanyName() As String
Get
Return companyNameValue
End Get
Set(ByVal value As String)
companyNameValue = value
End Set
End Property
Private contactNameValue As String
Public Property ContactName() As String
Get
Return contactNameValue
End Get
Set(ByVal value As String)
contactNameValue = value
End Set
End Property
Public Sub New(ByVal customerID As String, _
ByVal companyName As String, _
ByVal contactName As String)
CustomerIDValue = customerID
CompanyNameValue = companyName
ContactNameValue = contactName
End Sub
Public Overrides Function ToString() As String
Return String.Format("{0}:{1}:{2}", Me.CustomerID,
Me.CompanyName, Me.ContactName)
End Function
End Class
Sub Main()
Dim custOrd As XElement = XElement.Load("CustomersOrders.xml")
Dim custList As IEnumerable(Of Customer) = _
From el In custOrd.<Customers>.<Customer> _
Select New Customer(el.@<CustomerID>, el.<CompanyName>.Value, _
el.<ContactName>.Value)
For Each cust In custList
Console.WriteLine(cust)
Next
End Sub
<(T>)>) de int. Se trata de tipos de resultados comunes, pero no son adecuados para cada caso. En
muchos casos querrá que sus consultas devuelvan un IEnumerable<(Of <(T>)>) de algún otro tipo.
Ejemplo
En este ejemplo se muestra como crear instancias de objetos en la cláusula select. El código primero
define una nueva clase con un constructor y después modifica la instrucción select para que la expresión
sea una nueva instancia de la nueva clase.
Public Class NameQty
Public name As String
Public qty As Integer
Public Sub New(ByVal n As String, ByVal q As Integer)
name = n
qty = q
End Sub
End Class
Public Class Program
Shared Sub Main()
Dim po As XElement = XElement.Load("PurchaseOrder.xml")
Dim nqList As IEnumerable(Of NameQty) = From n In po...<Item> _
Select New NameQty(n.<ProductName>.Value, CInt(n.<Quantity>.Value))
For Each n As NameQty In nqList
Console.WriteLine(n.name & ":" & n.qty)
Next
End Sub
End Class
Ejemplo
El siguiente código rellena un gráfico de objetos con las clases Address, PurchaseOrder y
PurchaseOrderItem.
Class Address
Public Enum AddressUse
Shipping
Billing
End Enum
Private addressTypeVal As AddressUse
Private nameVal As String
Private streetVal As String
Private cityVal As String
Private stateVal As String
Private zipVal As String
Private countryVal As String
Public Property AddressType() As AddressUse
Get
Return addressTypeVal
End Get
Set(ByVal value As AddressUse)
addressTypeVal = value
End Set
End Property
Public Property Name() As String
Get
Return nameVal
End Get
Set(ByVal value As String)
nameVal = value
End Set
End Property
Public Property Street() As String
Get
Return streetVal
End Get
Set(ByVal value As String)
streetVal = value
End Set
End Property
Public Property City() As String
Get
Return cityVal
End Get
Set(ByVal value As String)
cityVal = value
End Set
End Property
Public Property State() As String
Get
Return stateVal
End Get
Set(ByVal value As String)
stateVal = value
End Set
End Property
Public Property Zip() As String
Get
Return zipVal
End Get
Set(ByVal value As String)
zipVal = value
End Set
End Property
Public Property Country() As String
Get
Return countryVal
End Get
Set(ByVal value As String)
countryVal = value
End Set
End Property
Public Overrides Function ToString() As String
Dim sb As StringBuilder = New StringBuilder()
sb.Append(String.Format("Type: {0}" + vbNewLine, _
IIf(AddressType = AddressUse.Shipping, "Shipping", "Billing")))
sb.Append(String.Format("Name: {0}" + vbNewLine, Name))
sb.Append(String.Format("Street: {0}" + vbNewLine, Street))
sb.Append(String.Format("City: {0}" + vbNewLine, City))
sb.Append(String.Format("State: {0}" + vbNewLine, State))
sb.Append(String.Format("Zip: {0}" + vbNewLine, Zip))
sb.Append(String.Format("Country: {0}" + vbNewLine, Country))
Return sb.ToString()
End Function
End Class
Class PurchaseOrderItem
Private partNumberVal As String
Private productNameVal As String
Private quantityVal As Integer
Private usPriceVal As Decimal
Private commentVal As String
Private shipDateVal As DateTime
Public Property PartNumber() As String
Get
Return partNumberVal
End Get
Set(ByVal value As String)
partNumberVal = value
End Set
End Property
Public Property ProductName() As String
Get
Return productNameVal
End Get
Set(ByVal value As String)
productNameVal = value
End Set
End Property
Public Property Quantity() As Integer
Get
Return quantityVal
End Get
Set(ByVal value As Integer)
quantityVal = value
End Set
End Property
Public Property USPrice() As Decimal
Get
Return usPriceVal
End Get
Set(ByVal value As Decimal)
usPriceVal = value
End Set
End Property
Public Property Comment() As String
Get
Return commentVal
End Get
Set(ByVal value As String)
commentVal = value
End Set
End Property
Public Property ShipDate() As DateTime
Get
Return shipDateVal
End Get
Set(ByVal value As DateTime)
shipDateVal = value
End Set
End Property
Public Overrides Function ToString() As String
Dim sb As StringBuilder = New StringBuilder()
sb.Append(String.Format("PartNumber: {0}" + vbNewLine,
PartNumber))
sb.Append(String.Format("ProductName: {0}" + vbNewLine,
ProductName))
sb.Append(String.Format("Quantity: {0}" + vbNewLine, Quantity))
sb.Append(String.Format("USPrice: {0}" + vbNewLine, USPrice))
If (Comment <> Nothing) Then
sb.Append(String.Format("Comment: {0}" + vbNewLine, Comment))
End If
If (ShipDate <> DateTime.MinValue) Then
sb.Append(String.Format("ShipDate: {0:d}" + vbNewLine,
ShipDate))
End If
Return sb.ToString()
End Function
End Class
Class PurchaseOrder
Private purchaseOrderNumberVal As String
Private orderDateVal As DateTime
Private commentVal As String
Private addressesVal As List(Of Address)
Private itemsVal As List(Of PurchaseOrderItem)
Public Property PurchaseOrderNumber() As String
Get
Return purchaseOrderNumberVal
End Get
Set(ByVal value As String)
purchaseOrderNumberVal = value
End Set
End Property
Public Property OrderDate() As DateTime
Get
Return orderDateVal
End Get
Set(ByVal value As DateTime)
orderDateVal = value
End Set
End Property
Public Property Comment() As String
Get
Return commentVal
End Get
Set(ByVal value As String)
commentVal = value
End Set
End Property
Public Property Addresses() As List(Of Address)
Get
Return addressesVal
End Get
Set(ByVal value As List(Of Address))
addressesVal = value
End Set
End Property
Public Property Items() As List(Of PurchaseOrderItem)
Get
Return itemsVal
End Get
Set(ByVal value As List(Of PurchaseOrderItem))
itemsVal = value
End Set
End Property
Public Overrides Function ToString() As String
Dim sb As StringBuilder = New StringBuilder()
sb.Append(String.Format("PurchaseOrderNumber: {0}" _
+ vbNewLine, PurchaseOrderNumber))
sb.Append(String.Format("OrderDate: {0:d}" + vbNewLine,
OrderDate))
sb.Append(vbNewLine)
sb.Append("Addresses" + vbNewLine)
sb.Append("=====" + vbNewLine)
For Each address As Address In Addresses
sb.Append(address)
sb.Append(vbNewLine)
Next
sb.Append("Items" + vbNewLine)
sb.Append("=====" + vbNewLine)
For Each item As PurchaseOrderItem In Items
sb.Append(item)
sb.Append(vbNewLine)
Next
Return sb.ToString()
End Function
End Class
Sub Main()
Dim po As XElement = XElement.Load("PurchaseOrder.xml")
Dim purchOrder = New PurchaseOrder With { _
.PurchaseOrderNumber = po.@<PurchaseOrderNumber>, _
.OrderDate = Convert.ToDateTime(po.@<OrderDate>), _
.Addresses = ( _
From a In po.<Address> _
Select New Address With { _
.AddressType = IIf(a.@<Type> = "Shipping", _
Address.AddressUse.Shipping, _
Address.AddressUse.Billing), _
.Name = a.<Name>.Value, _
.Street = a.<Street>.Value, _
.City = a.<City>.Value, _
.State = a.<State>.Value, _
.Zip = a.<Zip>.Value, _
.Country = a.<Country>.Value _
} _
).ToList(), _
.Items = ( _
From i In po.<Items>.<Item> _
Select New PurchaseOrderItem With { _
.PartNumber = i.@<PartNumber>, _
.ProductName = i.<ProductName>.Value, _
.Quantity = i.<Quantity>.Value, _
.USPrice = i.<USPrice>.Value, _
.Comment = i.<Comment>.Value, _
.ShipDate = IIf(i.<ShipDate>.Value <> Nothing, _
Convert.ToDateTime(i.<ShipDate>.Value), _
DateTime.MinValue) _
} _
).ToList() _
}
Console.WriteLine(purchOrder)
End Sub
En este ejemplo el resultado de la consulta de LINQ se devuelve como un IEnumerable<(Of <(T>)>) de
PurchaseOrderItem. Los elementos de la clase PurchaseOrder son del tipo IEnumerable<(Of <(T>)>) de
PurchaseOrderItem. El código usa el método de extensión ToList<(Of <(TSource>)>) para crear una
recopilación List<(Of <(T>)>) de los resultados de la consulta.
anónimo. Los tipos anónimos permiten definir una clase y, a continuación, declarar e inicializar un objeto
de esa clase, sin dar un nombre a la clase.
Los tipos anónimos son la implementación C# del concepto matemático de una tupla. El término
matemático tupla se originó de la secuencia único, doble, triple, cuádruple, quíntuple, n-tuple. Se refiere
a una secuencia finita de objetos, cada uno de un tipo específico. A veces se denomina una lista de pares
nombre/valor. Por ejemplo, el contenido de una dirección en el documento XML Pedido de compra típico
se puede expresar de la siguiente manera:
Name: Ellen Adams
Street: 123 Maple Street
City: Mill Valley
State: CA
Zip: 90952
Country: USA
Cuando se crea una instancia de un tipo anónimo, resulta práctico pensar en ella como si se creara una
tupla de orden n. Si escribe una consulta que crea una tupla en la cláusula select, la consulta devuelve
un IEnumerable de la tupla.
Ejemplo
En este ejemplo, la cláusula select proyecta un tipo anónimo. A continuación, el ejemplo utiliza var para
crear el objeto IEnumerable. En el bucle foreach, la variable de iteración se convierte en una instancia
del tipo anónimo creado en la expresión de consulta.
Dim custOrd As XElement = XElement.Load("CustomersOrders.xml")
Dim custList = From el In custOrd.<Customers>.<Customer> _
Select New With { _
.CustomerID = el.@<CustomerID>, _
.CompanyName = el.<CompanyName>.Value, _
.ContactName = el.<ContactName>.Value }
For Each cust In custList
Console.WriteLine("{0}:{1}:{2}", cust.CustomerID, cust.CompanyName,
cust.ContactName)
Next
Ejemplo
La versión en C# de este ejemplo utiliza sintaxis de métodos y el operador Aggregate para generar un
archivo CSV a partir de un documento XML en una única expresión.
La versión para Visual Basic utiliza un código basado en procedimientos para agrupar la colección de
cadenas en una única cadena.
Dim custOrd As XElement = XElement.Load("CustomersOrders.xml")
Dim strCollection As IEnumerable(Of String) = _
From el In custOrd.<Customers>.<Customer> _
Select _
String.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9}{10}", _
el.@CustomerID, _
el.<CompanyName>.Value, _
el.<ContactName>.Value, _
el.<ContactTitle>.Value, _
el.<Phone>.Value, _
el.<FullAddress>.<Address>.Value, _
el.<FullAddress>.<City>.Value, _
el.<FullAddress>.<Region>.Value, _
el.<FullAddress>.<PostalCode>.Value, _
el.<FullAddress>.<Country>.Value, _
Environment.NewLine )
Dim sb As StringBuilder = New StringBuilder()
For Each str As String In strCollection
sb.Append(str)
Next
Console.WriteLine(sb.ToString())
Ejemplo
El siguiente código realiza una consulta LINQ sobre una matriz de cadenas.
' Create the text file.
Dim csvString As String = "GREAL,Great Lakes Food Market,Howard
Snyder,Marketing Manager,(503) 555-7555,2732 Baker
Blvd.,Eugene,OR,97403,USA" & vbCrLf & _
"HUNGC,Hungry Coyote Import Store,Yoshi Latimer,Sales
Representative,(503) 555-6874,City Center Plaza 516 Main
St.,Elgin,OR,97827,USA" & vbCrLf & _
"LAZYK,Lazy K Kountry Store,John Steel,Marketing Manager,(509) 555-
7969,12 Orchestra Terrace,Walla Walla,WA,99362,USA" & vbCrLf & _
"LETSS,Let's Stop N Shop,Jaime Yorres,Owner,(415) 555-5938,87 Polk
St. Suite 5,San Francisco,CA,94117,USA"
File.WriteAllText("cust.csv", csvString)
' Read into an array of strings.
Dim source As String() = File.ReadAllLines("cust.csv")
Dim cust As XElement = _
<Root>
<%= From strs In source _
Let fields = Split(strs, ",") _
Select _
<Customer CustomerID=<%= fields(0) %>>
<CompanyName><%= fields(1) %></CompanyName>
<ContactName><%= fields(2) %></ContactName>
<ContactTitle><%= fields(3) %></ContactTitle>
<Phone><%= fields(4) %></Phone>
<FullAddress>
<Address><%= fields(5) %></Address>
<City><%= fields(6) %></City>
Ejemplo
Este ejemplo agrupa primero los datos por categorías y, a continuación, genera un nuevo archivo XML en
el que la jerarquía XML refleja la agrupación.
Dim doc As XElement = XElement.Load("Data.xml")
Dim newData As XElement = _
<Root>
<%= From data In doc.<Data> _
Group By category = data.<Category>(0).Value _
Into groupedData = Group _
Select <Group ID=<%= category %>>
<%= _
From g In groupedData _
Select _
<Data>
<%= g.<Quantity>(0) %>
<%= g.<Price>(0) %>
</Data> _
%>
</Group> _
%>
</Root>
Console.WriteLine(newData)
A menos que tenga un motivo muy específico para realizar consultas con XPath, como en el caso del uso
intensivo de código heredado, no se recomienda usar XPath con LINQ a XML. Las consultas XPath no se
realizarán tan bien como las consultas LINQ a XML.
Ejemplo
En el ejemplo siguiente se crea un árbol XML pequeño y se utiliza XPathSelectElements para seleccionar
un conjunto de elementos.
Dim root As XElement = _
<Root>
<Child1>1</Child1>
<Child1>2</Child1>
<Child1>3</Child1>
<Child2>4</Child2>
<Child2>5</Child2>
<Child2>6</Child2>
</Root>
Dim list As IEnumerable(Of XElement)=root.XPathSelectElements("./Child2")
For Each el As XElement In list
Console.WriteLine(el)
Next
No resulta particularmente útil ejecutar expresiones XPath mediante LINQ a XML. Las expresiones XPath
tienen un rendimiento menor que las consultas de LINQ a XML y las consultas de LINQ a XML son mucho
más eficaces. No obstante, como forma de identificar nodos en el árbol XML, XPath funciona bien.
Ejemplo
Este ejemplo muestra una función con el nombre GetXPath que genera una expresión XPath específica
para cualquier nodo en el árbol XML. Genera expresiones XPath adecuadas incluso cuando hay nodos en
un espacio de nombres. Las expresiones XPath se generan usando prefijos de espacio de nombres.
A continuación el ejemplo crea un pequeño árbol de texto que contiene un ejemplo de varios tipos de
nodos. Después recorre en iteración los nodos descendientes y muestra la expresión XPath para cada
nodo.
A continuación se muestra la lista de nodos del árbol XML anterior, expresados como expresiones XPath:
/processing-instruction()
/Root
/Root/@AttName
/Root/@xmlns:aw
/Root/comment()
/Root/Child[1]
/Root/Child[1]/text()
/Root/Child[2]
/Root/Child[2]/text()
/Root/ChildWithMixedContent
/Root/ChildWithMixedContent/text()[1]
/Root/ChildWithMixedContent/b
/Root/ChildWithMixedContent/b/text()
/Root/ChildWithMixedContent/text()[2]
/Root/aw:ElementInNamespace
/Root/aw:ElementInNamespace/aw:ChildInNamespace
Module Module1
<System.Runtime.CompilerServices.Extension()> _
Private Function StrCat(Of T)(ByVal source As IEnumerable(Of T), _
ByVal separator As String) As String
Return _
source.Aggregate(New StringBuilder(), _
Function(sb, i) sb _
.Append(i.ToString()) _
.Append(separator), _
Function(s) s.ToString())
End Function
<System.Runtime.CompilerServices.Extension()> _
Public Function GetXPath(ByVal xobj As XObject) As String
Dim retStr As String
If xobj.Parent Is Nothing Then
Dim doc As XDocument = TryCast(xobj, XDocument)
If doc IsNot Nothing Then
Return "."
End If
Dim el As XElement = TryCast(xobj, XElement)
If el IsNot Nothing Then
Return ("/" & NameWithPredicate(el))
End If
' The XPath data model does not include white space text nodes
' that are children of a document, so this method returns null.
Dim xt As XText = TryCast(xobj, XText)
If xt IsNot Nothing Then
Return Nothing
End If
Dim com As XComment = TryCast(xobj, XComment)
If com IsNot Nothing Then
If com.Document().Nodes().OfType(Of XComment)().Count() <> 1 Then
& "text()"
If cd.Parent.Nodes().OfType(Of XText)().Count() <> 1 Then
retStr &= "[" & (cd.NodesBeforeSelf().OfType(Of
XText)(). Count() + 1) & "]"
End If
Return retStr
End If
Dim tx As XText = TryCast(xobj, XText)
If tx IsNot Nothing Then
retStr = "/" &
tx.Parent.AncestorsAndSelf().InDocumentOrder(). _
Select(Function(e) NameWithPredicate(e)).StrCat("/") & "text()"
If tx.Parent.Nodes().OfType(Of XText)().Count() <> 1 Then
retStr &= "[" & (tx.NodesBeforeSelf().OfType(Of XText)(). _
Count() + 1) & "]"
End If
Return retStr
End If
Dim pi As XProcessingInstruction = TryCast(xobj,
XProcessingInstruction)
If pi IsNot Nothing Then
retStr = "/" &
pi.Parent.AncestorsAndSelf().InDocumentOrder(). _
Select(Function(e) NameWithPredicate(e)). _
StrCat("/") & "processing-instruction()"
If pi.Parent.Nodes().OfType(Of
XProcessingInstruction)().Count() <> 1 Then
retStr &= "[" & (pi.NodesBeforeSelf().OfType(Of
XProcessingInstruction)(). Count() + 1) & "]"
End If
End If
End If
Return Nothing
End Function
Private Function GetQName(ByVal xe As XElement) As String
Dim prefix As String = xe.GetPrefixOfNamespace(xe.Name.Namespace)
If xe.Name.Namespace = XNamespace.None Or prefix Is Nothing Then
Return xe.Name.LocalName.ToString()
Else
Return prefix + ":" & xe.Name.LocalName.ToString()
End If
End Function
Private Function GetQName(ByVal xa As XAttribute) As String
Dim prefix As String = _
xa.Parent.GetPrefixOfNamespace(xa.Name.Namespace)
If xa.Name.Namespace = XNamespace.None Or prefix Is Nothing Then
Return xa.Name.ToString()
Else
Return prefix + ":" & xa.Name.LocalName
End If
End Function
Public Function NameWithPredicate(ByVal el As XElement) As String
If el.Parent IsNot Nothing AndAlso
el.Parent.Elements(el.Name).Count() <> 1 Then
Return GetQName(el) + "[" & _
(el.ElementsBeforeSelf(el.Name).Count() + 1) & "]"
Else
Return GetQName(el)
End If
End Function
Sub Main()
Dim aw As XNamespace = "http://www.adventure-works.com"
Dim doc As XDocument = _
<?xml version='1.0' encoding="utf-8" standalone='yes'?>
<?target data?>
<Root AttName='An Attribute' xmlns:aw='http://www.adventure-works.com'>
<!--This is a comment-->
<Child>Text</Child>
<Child>Other Text</Child>
<ChildWithMixedContent>text<b>BldTxt</b>otherText</ChildWithMixedContent>
<aw:ElementInNamespace>
<aw:ChildInNamespace/>
</aw:ElementInNamespace>
</Root>
doc.Save("Test.xml")
Console.WriteLine(File.ReadAllText("Test.xml"))
Console.WriteLine("------")
For Each obj As XObject In doc.DescendantNodes()
Console.WriteLine(obj.GetXPath())
Dim el As XElement = TryCast(obj, XElement)
If el IsNot Nothing Then
For Each at As XAttribute In el.Attributes()
Console.WriteLine(at.GetXPath())
Next
End If
Next
End Sub
End Module
Ejemplo
El ejemplo siguiente rellena un árbol XML desde el sistema de archivos local mediante la recursividad. A
continuación, consulta el árbol y calcula el total de los tamaños de todos los archivos del árbol.
Module Module1
Function CreateFileSystemXmlTree(ByVal source As String) As XElement
Dim di As DirectoryInfo = New DirectoryInfo(source)
Return <Dir Name=<%= di.Name %>>
<%= From d In Directory.GetDirectories(source) _
Select CreateFileSystemXmlTree(d) %>
<%= From fi In di.GetFiles() _
Select <File>
<Name><%= fi.Name %></Name>
<Length><%= fi.Length %></Length>
</File> %>
</Dir>
End Function
Sub Main()
Dim fileSystemTree As XElement=CreateFileSystemXmlTree("C:/Tmp")
Console.WriteLine(fileSystemTree)
Console.WriteLine("------")
Dim totalFileSize As Long = _
( _
From f In fileSystemTree...<File> _
Select CLng(f.<Length>(0)) _
).Sum()
Console.WriteLine("Total File Size:{0}", totalFileSize)
End Sub
End Module
Todos los ejemplos utilizan la funcionalidad XPath en LINQ a XML que está disponible con los métodos de
extensión de System.Xml.XPath..::.Extensions. Los ejemplos ejecutan la expresión XPath y la expresión
LINQ a XML. A continuación, cada ejemplo compara los resultados de ambas consultas, validando que la
expresión XPath sea funcionalmente equivalente a la consulta de LINQ a XML. Como ambos tipos de
consultas devuelven nodos del mismo árbol XML, la comparación del resultado de las consultas se realiza
mediante identidad referencial.
en una nueva forma. Las consultas de LINQ a XML abarcan una funcionalidad mucho mayor y son mucho
más eficaces que las expresiones XPath.
Las expresiones XPath existen de forma aislada en una cadena. El compilador de C# o Visual Basic no
puede ayudar a analizar la expresión XPath en tiempo de compilación. Por el contrario, el compilador de
C# o Visual Basic analiza y compila las consultas de LINQ a XML. El compilador puede detectar muchos
errores de consulta.
Ordenación de resultados
La recomendación XPath 1.0 indica que una recopilación que es el resultado de evaluar una expresión
XPath está desordenada.
No obstante, cuando se recorre en iteración una recopilación devuelta por un método de eje XPath de
LINQ a XML, los nodos de la recopilación se devuelven en el orden del documento. Esto es aplicable
incluso cuando se tiene acceso a los ejes XPath donde se expresan los predicados en términos de orden
de documento inverso, como preceding y preceding-sibling.
Por el contrario, la mayoría de los ejes de LINQ a XML devuelven recopilaciones en el orden del
documento, pero dos de ellos, Ancestors y AncestorsAndSelf, devuelven recopilaciones en el orden
inverso del documento. En la siguiente tabla se enumeran los ejes y se indica el orden de la recopilación
para cada uno:
Predicados de posición
En una expresión XPath, los predicados de posición se expresan en términos de orden del documento
para muchos ejes, pero se expresan en el orden del documento inverso para los ejes inversos, que son
preceding, preceding-sibling, ancestor y ancestor-or-self. Por ejemplo, la expresión de XPath preceding-
sibling::*[1] devuelve el elemento del mismo nivel inmediatamente anterior. Esto es aplicable aunque el
conjunto de resultados finales se presenta en el orden del documento.
Por el contrario, todos los predicados de posición de LINQ a XML siempre se expresan en términos del
orden del eje. Por ejemplo, anElement.ElementsBeforeSelf().ToList()[0] devuelve el primer elemento
secundario del elemento primario del elemento consultado, no el elemento del mismo nivel
inmediatamente anterior. Otro ejemplo: anElement.Ancestors().ToList()[0] devuelve el elemento
primario.
Tenga en cuenta que el enfoque anterior materializa toda la recopilación. Ésta no es la forma más
eficiente de escribir esa consulta. Se ha escrito de esa forma para demostrar el comportamiento de los
predicados de posición. Una forma más adecuada de escribir la misma consulta es usar el método First
de la siguiente manera: anElement.ElementsBeforeSelf().First().
ElementsBeforeSelf().Last()
Diferencias de rendimiento
Las consultas de XPath que usan la funcionalidad en XPath LINQ a XML no tienen un rendimiento tan
bueno como las consultas de LINQ a XML.
Comparación de la composición
La composición de una consulta de LINQ a XML es en cierta medida paralela a la composición de una
expresión XPath, aunque tiene una sintaxis muy diferente.
Por ejemplo, si tiene un elemento de una variable con el nombre customers y desea buscar un elemento
inferior con el nombre CompanyName bajo todos los elementos secundarios con el nombre Customer,
debe escribir una expresión XPath de la siguiente manera:
customers.XPathSelectElements("./Customer/CompanyName")
La consulta de LINQ a XML equivalente es:
customers.Element("Customer").Elements("CompanyName")
Existen elementos similares para cada uno de los ejes XPath.
siguientes-relacionados XNode..::.ElementsAfterSelf
o bien
XNode..::.NodesAfterSelf
precedentes-relacionados XNode..::.ElementsBeforeSelf
o bien
XNode..::.NodesBeforeSelf
Ejemplo
Este ejemplo busca el elemento secundario DeliveryNotes.
Dim cpo As XDocument = XDocument.Load("PurchaseOrders.xml")
Dim po As XElement = cpo.Root.<PurchaseOrder>.FirstOrDefault
'LINQ a XML query
Dim el1 As XElement = po.<DeliveryNotes>.FirstOrDefault
' XPath expression
Dim el2 As XElement = po.XPathSelectElement("DeliveryNotes")
' same as "child::DeliveryNotes"
' same as "./DeliveryNotes"
If el1 Is el2 Then
Console.WriteLine("Results are identical")
Else
Console.WriteLine("Results differ")
End If
Console.WriteLine(el1)
Ejemplo
Este ejemplo busca todos los elementos secundarios del elemento Address.
Dim cpo As XDocument = XDocument.Load("PurchaseOrders.xml")
Dim po As XElement = cpo.Root.<PurchaseOrder>.<Address>.FirstOrDefault
' LINQ a XML query
Dim list1 As IEnumerable(Of XElement) = po.Elements()
' XPath expression
Dim list2 As IEnumerable(Of XElement) = po.XPathSelectElements("./*")
If (list1.Count() = list2.Count()) AndAlso _
(list1.Intersect(list2).Count() = list1.Count()) Then
Console.WriteLine("Results are identical")
Else
Console.WriteLine("Results differ")
End If
For Each el As XElement In list1
Console.WriteLine(el)
Next
Ejemplo
Este ejemplo busca el elemento raíz.
Dim po As XDocument = XDocument.Load("PurchaseOrders.xml")
' LINQ a XML query
Dim el1 As XElement = po.Root
' XPath expression
Dim el2 As XElement = po.XPathSelectElement("/PurchaseOrders")
If el1 Is el2 Then
Console.WriteLine("Results are identical")
Else
Console.WriteLine("Results differ")
End If
Console.WriteLine(el1.Name)
Ejemplo
Este ejemplo busca todos los descendientes con el nombre Name.
Dim po As XDocument = XDocument.Load("PurchaseOrders.xml")
' LINQ a XML query
Dim list1 As IEnumerable(Of XElement) = po...<Name>
' XPath expression
Dim list2 As IEnumerable(Of XElement) = po.XPathSelectElements("//Name")
Ejemplo
Este ejemplo encuentra todos los elementos descendientes con el nombre Address y con un atributo
Type con el valor "Shipping".
Dim po As XDocument = XDocument.Load("PurchaseOrders.xml")
' LINQ a XML query
Dim list1 As IEnumerable(Of XElement) = From el In po...<Address> Where
el.@Type = "Shipping" Select el
' XPath expression
Dim list2 As IEnumerable(Of XElement) = _
po.XPathSelectElements(".//Address[@Type='Shipping']")
If (list1.Count = list2.Count And _
list1.Intersect(list2).Count() = list1.Count()) Then
Console.WriteLine("Results are identical")
Else
Console.WriteLine("Results differ")
End If
For Each el As XElement In list1
Console.WriteLine(el)
Next
Ejemplo
Este ejemplo encuentra el duodécimo (12) elemento Order, y después encuentra el cliente de dicho
pedido.
Tenga en cuenta que la indización en una lista de .Net se basa en "cero". Sin embargo, la indización en
una colección de nodos de un predicado XPath se basa en "uno". Este ejemplo refleja esta diferencia.
Dim co As XDocument = XDocument.Load("CustomersOrders.xml")
Ejemplo
En el ejemplo siguiente se lee un árbol XML que contiene dos espacios de nombres. Usa un objeto
XmlReader para leer el documento XML. Después obtiene un objeto XmlNameTable de XmlReader y un
objeto XmlNamespaceManager de XmlNameTable. Utiliza XmlNamespaceManager al seleccionar
elementos.
Dim reader As XmlReader=XmlReader.Create("ConsolidatedPurchaseOrder.xml")
Dim root As XElement = XElement.Load(reader)
Dim nameTable As XmlNameTable = reader.NameTable
Dim namespaceManager As XmlNamespaceManager = New
XmlNamespaceManager(nameTable)
namespaceManager.AddNamespace("aw", "http://www.adventure-works.com")
Dim list1 As IEnumerable(Of XElement) = _
root.XPathSelectElements("./aw:*", namespaceManager)
Dim list2 As IEnumerable(Of XElement) = From el In root.Elements() Where
el.Name.Namespace = "http://www.adventure-works.com" Select el
If list1.Count() = list2.Count() And list1.Intersect(list2).Count() =
list1.Count() Then
Console.WriteLine("Results are identical")
Else
Console.WriteLine("Results differ")
End If
For Each el As XElement In list2
Console.WriteLine(el)
Next
Ejemplo
El siguiente ejemplo busca el elemento FullAddress y, a continuación, recupera los elementos anteriores
utilizando el eje preceding-sibling.
Dim co As XElement = XElement.Load("CustomersOrders.xml")
Dim add As XElement = co.<Customers>.<Customer>. _
<FullAddress>.FirstOrDefault
' LINQ a XML query
Dim list1 As IEnumerable(Of XElement) = add.ElementsBeforeSelf()
' XPath expression
Dim list2 As IEnumerable(Of XElement) =
add.XPathSelectElements("preceding-sibling::*")
If list1.Count() = list2.Count() And _
list1.Intersect(list2).Count() = list1.Count() Then
Console.WriteLine("Results are identical")
Else
Console.WriteLine("Results differ")
End If
For Each el As XElement In list2
Console.WriteLine(el)
Next
Ejemplo
Este ejemplo simula los problemas de extraer texto de una representación XML de un documento de un
procesador de texto. Primero selecciona todos los elementos de Paragraph y después selecciona todos
los elementos descendientes de Text de cada elemento Paragraph. Esto no selecciona los elementos Text
descendientes del elemento Comment.
Dim root As XElement = _
<Root>
<Paragraph>
<Text>This is the start of</Text>
</Paragraph>
<Comment>
<Text>This comment is not part of the paragraph text.</Text>
</Comment>
<Paragraph>
<Annotation Emphasis='true'>
<Text> a sentence.</Text>
</Annotation>
</Paragraph>
<Paragraph>
<Text> This is a second sentence.</Text>
</Paragraph>
</Root>
' LINQ a XML query
Dim str1 As String = _
root.<Paragraph>...<Text>.Select(Function(ByVal s) s.Value). _
Aggregate(New StringBuilder(),Function(ByVal s, ByVal i) s.Append(i),
Function(ByVal s) s.ToString())
' XPath expression
Dim str2 As String =
DirectCast(root.XPathEvaluate("./Paragraph//Text/text()"), IEnumerable) _
.Cast(Of XText)().Select(Function(ByVal s) s.Value) _
.Aggregate(New StringBuilder(),Function(ByVal s, ByVal i)
s.Append(i), Function(ByVal s) s.ToString())
If str1 = str2 Then
Console.WriteLine("Results are identical")
Else
Console.WriteLine("Results differ")
End If
Console.WriteLine(str2)
Puede conseguir los mismos resultados usando el operador de consulta estándar Concat<(Of
<(TSource>)>).
Ejemplo
Este ejemplo busca todos los elementos Category y todos los elementos Price y los concatena en una
única recopilación. Tenga en cuenta que la consulta LINQ a XML llama a InDocumentOrder<(Of <(T>)>)
para ordenar los resultados. Los resultados de la evaluación de la expresión XPath también están en el
orden del documento.
Dim data As XDocument = XDocument.Load("Data.xml")
' LINQ a XML query
Dim list1 As IEnumerable(Of XElement) = _
data...<Category>.Concat(data...<Price>).InDocumentOrder()
' XPath expression
Dim list2 As IEnumerable(Of XElement) = _
data.XPathSelectElements("//Category|//Price")
If list1.Count() = list2.Count() And _
list1.Intersect(list2).Count() = list1.Count() Then
Console.WriteLine("Results are identical")
Else
Console.WriteLine("Results differ")
End If
For Each el As XElement In list1
Console.WriteLine(el)
Next
Ejemplo
Este ejemplo primero busca un elemento Book y después busca todos los elementos secundarios con el
nombre Book. La recopilación resultante incluye el nodo de contexto.
Dim books As XDocument = XDocument.Load("Books.xml")
Dim book As XElement = books.Root.<Book>.Skip(1).First()
' LINQ a XML query
Dim list1 As IEnumerable(Of XElement) = book.Parent.<Book>
' XPath expression
Dim list2 As IEnumerable(Of XElement)=book.XPathSelectElements("../Book")
If list1.Count() = list2.Count() And _
list1.Intersect(list2).Count() = list1.Count() Then
Console.WriteLine("Results are identical")
Else
Console.WriteLine("Results differ")
End If
For Each el As XElement In list1
Console.WriteLine(el)
Next
Ejemplo
Este ejemplo busca primero un elemento Author. Después busca el atributo id del elemento primario.
Dim books As XDocument = XDocument.Load("Books.xml")
Dim author As XElement = books.Root.<Book>.<Author>.FirstOrDefault()
' LINQ a XML query
Dim att1 As XAttribute = author.Parent.Attribute("id")
' XPath expression
Dim att2 As XAttribute = DirectCast(author.XPathEvaluate("../@id"), _
IEnumerable).Cast(Of XAttribute)().First()
If att1 Is att2 Then
Console.WriteLine("Results are identical")
Else
Console.WriteLine("Results differ")
End If
Console.WriteLine(att1)
Ejemplo
Este ejemplo primero busca un elemento Book, después busca todos los elementos secundarios con el
nombre Book y después busca todos los atributos con el nombre id. El resultado es una recopilación de
atributos.
Dim books as XDocument = XDocument.Load("Books.xml")
Dim book As XElement = books.Root.<Book>(0)
' LINQ a XML query
Dim list1 As IEnumerable(Of XAttribute) = From el In book.Parent.<Book>
Select el.Attribute("id")
' XPath expression
Dim list2 As IEnumerable(Of XAttribute) = DirectCast(book. _
XPathEvaluate("../Book/@id"), IEnumerable).Cast(Of XAttribute)()
If list1.Count() = list2.Count() And _
(list1.Intersect(list2)).Count() = list1.Count() Then
Console.WriteLine("Results are identical")
Else
Console.WriteLine("Results differ")
End If
For Each el As XAttribute In list1
Console.WriteLine(el)
Next
En ocasiones, deseará buscar todos los elementos que tengan un atributo en particular. Pero no le
preocupa cuáles es el contenido del atributo. En vez de ello, desea realizar la selección en función de si
existe o no el atributo.
Ejemplo
El siguiente código selecciona únicamente los elementos que tengan el atributo Select.
Dim doc As XElement = _
<Root>
<Child1>1</Child1>
<Child2 Select='true'>2</Child2>
<Child3>3</Child3>
<Child4 Select='true'>4</Child4>
<Child5>5</Child5>
</Root>
' LINQ a XML query
Dim list1 As IEnumerable(Of XElement) = From el In doc.Elements() Where
el.@Select <> Nothing Select el
' XPath expression
Dim list2 As IEnumerable(Of XElement) = DirectCast(doc.XPathEvaluate _
("./*[@Select]"), IEnumerable).Cast(Of XElement)()
If list1.Count() = list2.Count() And _
list1.Intersect(list2).Count() = list1.Count() Then
Console.WriteLine("Results are identical")
Else
Console.WriteLine("Results differ")
End If
For Each el As XElement In list1
Console.WriteLine(el)
Next
Existen dos aproximaciones posibles para escribir esta consulta de LINQ a XML de forma diferida. Puede
utilizar los operadores Skip<(Of <(TSource>)>) y Take<(Of <(TSource>)>), o bien utilizar la
sobrecarga Where que recibe un índice. Si utiliza la sobrecarga Where, estará utilizando una expresión
lambda que recibe dos argumentos. El siguiente ejemplo muestra ambos métodos para seleccionar
elementos en base a la posición.
Ejemplo
Este ejemplo encontrará el segundo en el cuarto elemento de Test. El resultado es una colección de
elementos.
Dim testCfg As XElement = XElement.Load("TestConfig.xml")
' LINQ a XML query
Dim list1 As IEnumerable(Of XElement) = _
testCfg.Elements("Test").Skip(1).Take(3)
'LINQ a XML query
Dim list2 As IEnumerable(Of XElement) = testCfg.Elements("Test"). _
Where(Function(ByVal el, ByVal idx) idx >= 1 And idx <= 3)
' XPath expression
Dim list3 As IEnumerable(Of XElement) = _
testCfg.XPathSelectElements("Test[position()>=2 and position()<=4]")
If list1.Count() = list2.Count() And list1.Count() = list3.Count() And _
list1.Intersect(list2).Count() = list1.Count() And _
list1.Intersect(list3).Count() = list1.Count() Then
Console.WriteLine("Results are identical")
Else
Console.WriteLine("Results differ")
End If
For Each el As XElement In list1
Console.WriteLine(el)
Next
Ejemplo
En este ejemplo, la consulta LINQ a XML usa el operador Last para buscar el último nodo de la
recopilación devuelto por ElementsBeforeSelf. Por el contrario, la expresión XPath usa un predicado con
un valor de 1 para buscar el elemento inmediatamente anterior.
Dim root As XElement = _
<Root>
<Child1/>
<Child2/>
<Child3/>
<Child4/>
</Root>
Dim child4 As XElement = root.Element("Child4")
' LINQ a XML query
Dim el1 As XElement = child4.ElementsBeforeSelf().Last()
' XPath expression
Dim el2 As XElement = _
DirectCast(child4.XPathEvaluate("preceding-sibling::*[1]"), _
IEnumerable).Cast(Of XElement)().First()
A la hora de modificar un árbol, puede utilizar ciertos métodos, como por ejemplo, Add.
No obstante, existe otra aproximación posible, la cual consiste en utilizar una construcción funcional para
generar un árbol nuevo que tenga un aspecto diferente. Dependiendo de los tipos de cambios que
necesite hacer en el árbol XML y del tamaño de éste, es posible que esta aproximación resulte más
robusta y sencilla de desarrollar. El primer punto de esta sección compara ambas aproximaciones.
LINQ a XML permite otro enfoque es útil en muchos escenarios: construcción funcional. La construcción
funcional trata la modificación de datos como un problema de transformación en lugar de una
manipulación detallada de un almacén de datos. Si puede tomar una representación de datos y
transformarla eficientemente de una a otra, el resultado es el mismo que si ha tomado un almacén de
datos y lo ha manipulado de alguna manera para que tome otra forma. Un aspecto fundamental del
enfoque de construcción funcional es pasar los resultados de las consultas a los constructores
XDocument y XElement.
En muchos casos puede escribir el código de transformación en una fracción del tiempo que tardaría en
manipular el almacén de datos y ese código es más eficaz y fácil de mantener. En esos casos, aunque el
enfoque de transformación puede necesitar más potencia de procesamiento, es una forma más efectiva
de modificar datos. Si un desarrollador está familiarizado con el enfoque funcional, el código resultante
es en muchos casos más fácil de entender. Resulta sencillo buscar el código que modifica cada parte del
árbol.
El enfoque en el que se modifica un árbol XML directamente es mucho más familiar para muchos
programadores de DOM, mientras que el código escrito usando el enfoque funcional puede ser poco
familiar para un desarrollador que aún no comprende este enfoque. Si sólo tiene que realizar una
pequeña modificación en un árbol XML grande, el enfoque en el que se modifica un árbol directamente
en muchos casos consumirá menos tiempo de CPU.
El ejemplo funcional de este caso no es más corto que el primer ejemplo y en realidad no es más
sencillo. No obstante, si tiene muchos cambios que realizar a un árbol XML, el enfoque no funcional se
hará más complejo y difícil de entender. Por el contrario, cuando se usa el enfoque funcional, se sigue
formando el XML deseado, incrustando consultas y expresiones según convenga, para extraer el
contenido deseado. El enfoque funcional produce código que es más fácil de mantener.
Tenga en cuenta que el enfoque funcional probablemente no tendrá un rendimiento tan bueno como la
manipulación del árbol. El principal problema es que el enfoque funcional crea objetos de corta duración.
No obstante, la contrapartida es eficacia si el uso del enfoque funcional permite una mayor productividad
del programador.
Éste es un ejemplo muy sencillo, pero sirve para mostrar la diferencia de filosofía entre los dos enfoques.
El enfoque funcional ofrece mayores ganancias de productividad para transformar documentos XML de
gran tamaño.
Método Descripción
Los métodos siguientes agregan contenidos como nodos relacionados de un XNode. El nodo al que se
agregan habitualmente contenidos relacionados es XElement, aunque es posible agregar contenidos
relacionados válidos a otros tipos de nodos, como por ejemplo, al nodo XText o al nodo XComment.
Método Descripción
Ejemplo
Descripción
El siguiente ejemplo crear dos árboles XML y, a continuación, modifica uno de ellos.
Código
Dim srcTree As XElement = _
<Root>
<Element1>1</Element1>
<Element2>2</Element2>
<Element3>3</Element3>
<Element4>4</Element4>
<Element5>5</Element5>
</Root>
Dim xmlTree As XElement = _
<Root>
<Child1>1</Child1>
<Child2>2</Child2>
<Child3>3</Child3>
<Child4>4</Child4>
<Child5>5</Child5>
</Root>
xmlTree.Add(<NewChild>new content</NewChild>)
xmlTree.Add(From el In srcTree.Elements() Where CInt(el) > 3 Select el)
' Even though Child9 does not exist in srcTree, the following statement
' will not throw an exception, and nothing will be added to xmlTree.
xmlTree.Add(srcTree.Element("Child9"))
Console.WriteLine(xmlTree)
Método Descripción
Método Descripción
Método Descripción
Método Descripción
Puede modificar un árbol XML mediante la eliminación de elementos, atributos y otros tipos de nodos.
Quitar un elemento o un atributo de un documento XML resulta sencillo. Sin embargo, al quitar
colecciones de elementos o atributos, primero debe materializar una colección en una lista y, a
continuación, eliminar los elementos o los atributos de ésta. El mejor método consiste en usar el método
de extensión Remove, que se ocupará de todo esto.
El motivo principal radica en que la mayoría de las colecciones que se recuperan de un árbol XML se
producen con una ejecución aplazada. Si no las materializa primero en una lista, o bien si no usa los
métodos de extensión, es posible que aparezca una clase determinada de errores.
Los siguientes métodos sirven para quitar nodos y atributos de un árbol XML.
Método Descripción
Ejemplo
Descripción
Este ejemplo demuestra tres métodos para quitar elementos. Primero, quita un solo elemento. En
segundo lugar, recupera una colección de elementos, los materializa con el operador
Enumerable..::.ToList<(Of <(TSource>)>) y quita la colección. Por último, recupera una colección de
elementos y los quita con el método de extensión Remove.
Código
Dim root As XElement = _
<Root>
<Child1>
<GrandChild1/>
<GrandChild2/>
<GrandChild3/>
</Child1>
<Child2>
<GrandChild4/>
<GrandChild5/>
<GrandChild6/>
</Child2>
<Child3>
<GrandChild7/>
<GrandChild8/>
<GrandChild9/>
</Child3>
</Root>
root.<Child1>.<GrandChild1>.Remove()
root.<Child2>.Elements().ToList().Remove()
root.<Child3>.Elements().Remove()
Console.WriteLine(root)
Una diferencia existente entre almacenar la información como atributos o como elementos secundarios
es que los atributos tienen, como limitación, que sólo puede existir un atributo con un nombre en
particular para un atributo. Esto no se aplica a los elementos secundarios.
SetAttributeValue y SetElementValue
Los dos métodos que facilitan el mantenimiento de pares nombre/valor son SetAttributeValue y
SetElementValue. La semántica de ambos métodos es muy similar.
Si llama al método SetAttributeValue con el nombre de un atributo que no existe, éste creará un
nuevo atributo y lo agregará al elemento especificado.
Ejemplo
El siguiente ejemplo crea un elemento que no tiene atributos. A continuación, utiliza el método
SetAttributeValue para crear y mantener una lista de pares nombre/valor.
' Create an element with no content.
Dim root As XElement = <Root/>
' Add a number of name/value pairs as attributes.
root.SetAttributeValue("Top", 22)
root.SetAttributeValue("Left", 20)
root.SetAttributeValue("Bottom", 122)
root.SetAttributeValue("Right", 300)
root.SetAttributeValue("DefaultColor", "Color.Red")
Console.WriteLine(root)
' Replace the value of Top.
root.SetAttributeValue("Top", 10)
Console.WriteLine(root)
' Remove DefaultColor.
root.SetAttributeValue("DefaultColor", Nothing)
Console.WriteLine(root)
Ejemplo
El siguiente ejemplo crea un elemento que no tiene elementos secundarios. A continuación, utiliza el
método SetElementValue para crear y mantener una lista de pares nombre/valor.
' Create an element with no content.
Dim root As XElement = <Root/>
' Add a number of name/value pairs as elements.
root.SetElementValue("Top", 22)
root.SetElementValue("Left", 20)
root.SetElementValue("Bottom", 122)
root.SetElementValue("Right", 300)
root.SetElementValue("DefaultColor", "Color.Red")
Console.WriteLine(root)
Console.WriteLine("----")
' Replace the value of Top.
root.SetElementValue("Top", 10)
Console.WriteLine(root)
Console.WriteLine("----")
' Remove DefaultColor.
root.SetElementValue("DefaultColor", Nothing)
Console.WriteLine(root)
Ejemplo
En el código siguiente se crean dos árboles XML que no están en ningún espacio de nombres. A
continuación, se cambia el espacio de nombres de cada árbol y se combinan ambos árboles en uno sólo.
Dim tree1 As XElement = _
<Data>
<Child MyAttr="content">content</Child>
</Data>
Dim tree2 As XElement = _
<Data>
<Child MyAttr="content">content</Child>
</Data>
Dim aw As XNamespace = "http://www.adventure-works.com"
Dim ad As XNamespace = "http://www.adatum.com"
' change the namespace of every element and attribute in the first tree
For Each el As XElement In tree1.DescendantsAndSelf
el.Name = aw.GetName(el.Name.LocalName)
Dim atList As List(Of XAttribute) = el.Attributes().ToList()
el.Attributes().Remove()
For Each at As XAttribute In atList
el.Add(New XAttribute(aw.GetName(at.Name.LocalName), at.Value))
Next
Next
' change the namespace of every element and attribute in the second tree
For Each el As XElement In tree2.DescendantsAndSelf()
el.Name = ad.GetName(el.Name.LocalName)
Dim atList As List(Of XAttribute) = el.Attributes().ToList()
el.Attributes().Remove()
For Each at As XAttribute In atList
el.Add(New XAttribute(ad.GetName(at.Name.LocalName), at.Value))
Next
Next
' add attribute namespaces so that the tree will be serialized with
' the aw and ad namespace prefixes
tree1.Add(New XAttribute(XNamespace.Xmlns + "aw", "http://www.adventure-
works.com"))
tree2.Add(New XAttribute(XNamespace.Xmlns+"ad","http://www.adatum.com"))
' create a new composite tree
Dim root As XElement = _
<Root>
<%= tree1 %>
<%= tree2 %>
</Root>
Console.WriteLine(root)
Las anotaciones de LINQ a XML le permite asociar cualquier objeto de cualquier tipo con un componente
XML de un árbol XML.
Si desea agregar una anotación a un componente XML, como puede ser un XElement o un XAttribute,
deberá llamar al método AddAnnotation. Las anotaciones se obtienen por tipos.
Tenga en cuenta que las anotaciones no forman parte del conjunto de información de XML, por lo que no
se serializan o deserializan.
Métodos
Puede utilizar los siguientes métodos a la hora de trabajar con anotaciones:
Método Descripción
Puede agregar eventos a una instancia de cualquier XObject. El controlador de eventos recibirá entonces
eventos relacionados con las modificaciones realizadas en ese XObject y en cualquiera de sus
descendientes. Por ejemplo, puede agregar un controlador de evento a la raíz del árbol y controlar todas
las modificaciones que se realicen al árbol desde ese controlador de eventos.
Tipos y eventos
Puede utilizar los siguientes tipos a la hora de trabajar con eventos:
Tipo Descripción
Evento Descripción
Changing Se produce justo antes de que este XObject o cualquiera de sus descendientes se vayan
a modificar.
Ejemplo
Descripción
Los eventos resultan útiles cuando desea mantener cierta información de agregado en un árbol XML. Por
ejemplo, quizá desee mantener el total de una factura que es la suma de los conceptos de la factura.
Este ejemplo utiliza eventos para mantener el total de todos los elementos secundarios que se
encuentran bajo el elemento complejo Items.
Código
Module Module1
Dim WithEvents items As XElement = Nothing
Dim total As XElement = Nothing
Dim root As XElement = _
<Root>
<Total>0</Total>
<Items></Items>
</Root>
Private Sub XObjectChanged(ByVal sender As Object, _
ByVal cea As XObjectChangeEventArgs) Handles items.Changed
Select Case cea.ObjectChange
Case XObjectChange.Add
If sender.GetType() Is GetType(XElement) Then
total.Value = CStr(CInt(total.Value) + _
CInt((DirectCast(sender, XElement)).Value))
End If
If sender.GetType() Is GetType(XText) Then
total.Value = CStr(CInt(total.Value) + _
CInt((DirectCast(sender, XText)).Value))
End If
Case XObjectChange.Remove
If sender.GetType() Is GetType(XElement) Then
total.Value = CStr(CInt(total.Value) - _
CInt((DirectCast(sender, XElement)).Value))
End If
If sender.GetType() Is GetType(XText) Then
total.Value = CStr(CInt(total.Value) - _
CInt((DirectCast(sender, XText)).Value))
End If
End Select
Console.WriteLine("Changed {0} {1}",sender.GetType().ToString(),_
cea.ObjectChange.ToString())
End Sub
Sub Main()
total = root.<Total>(0)
items = root.<Items>(0)
items.SetElementValue("Item1", 25)
items.SetElementValue("Item2", 50)
items.SetElementValue("Item2", 75)
items.SetElementValue("Item3", 133)
items.SetElementValue("Item1", Nothing)
items.SetElementValue("Item4", 100)
Console.WriteLine("Total:{0}", CInt(total))
Console.WriteLine(root)
End Sub
End Module
Console.WriteLine(">>{0}<<", textNode2)
Un nodo de texto vacío incide sobre la serialización
Si un elemento contiene solamente un nodo de texto que está vacío, se serializa con la sintaxis de
etiqueta larga: <Child></Child>. Si un elemento no contiene ningún nodo secundario, se serializa con la
sintaxis de etiqueta corta: <Child />.
Dim child1 As XElement = New XElement("Child1", New XText(""))
Dim child2 As XElement = New XElement("Child2")
Console.WriteLine(child1)
Console.WriteLine(child2)
Los espacios de nombres son atributos en el árbol LINQ a XML
Aunque las declaraciones del espacio de nombres tienen una sintaxis idéntica a los atributos, en algunas
interfaces de programación como XSLT y XPath, las declaraciones de espacios de nombres no se
consideran atributos. No obstante, en LINQ a XML, los espacios de nombres se almacenan como objetos
XAttribute en el árbol XML. Si recorre en iteración los atributos de un elemento que contiene una
declaración de espacio de nombres, verá la declaración de espacio de nombres como uno de los
elementos de la recopilación devuelta.
<Root/>
doc.Save("Temp.xml")
Console.WriteLine(File.ReadAllText("Temp.xml"))
' This shows that there is only one child node of the document.
Console.WriteLine(doc.Nodes().Count())
Si establece Encoding en un nombre de página de códigos válido, LINQ a XML se serializará con la
codificación especificada.
Ejemplo
En el ejemplo siguiente se crean dos documentos, uno con codificación utf-8 y otro con codificación utf-
16. A continuación se cargan los documentos y se imprime la codificación en la consola.
Console.WriteLine("Creating a document with utf-8 encoding")
Dim encodedDoc8 As XDocument = _
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<Root>Content</Root>
encodedDoc8.Save("EncodedUtf8.xml")
Console.WriteLine("Encoding is:{0}", encodedDoc8.Declaration.Encoding)
Console.WriteLine()
Console.WriteLine("Creating a document with utf-16 encoding")
Dim encodedDoc16 As XDocument = _
<?xml version='1.0' encoding='utf-16' standalone='yes'?>
<Root>Content</Root>
encodedDoc16.Save("EncodedUtf16.xml")
Console.WriteLine("Encoding is:{0}", encodedDoc16.Declaration.Encoding)
Console.WriteLine()
Dim newDoc8 As XDocument = XDocument.Load("EncodedUtf8.xml")
Console.WriteLine("Encoded document:")
Console.WriteLine(File.ReadAllText("EncodedUtf8.xml"))
Console.WriteLine()
Console.WriteLine("Encoding of loaded document is:{0}",
newDoc8.Declaration.Encoding)
Console.WriteLine()
Dim newDoc16 As XDocument = XDocument.Load("EncodedUtf16.xml")
Console.WriteLine("Encoded document:")
Console.WriteLine(File.ReadAllText("EncodedUtf16.xml"))
Console.WriteLine()
Console.WriteLine("Encoding of loaded document is:{0}",
newDoc16.Declaration.Encoding)
Ejemplo
Dim xslMarkup As XDocument = _
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
version='1.0'>
<xsl:template match='/Parent'>
<Root>
<C1>
<xsl:value-of select='Child1'/>
</C1>
<C2>
<xsl:value-of select='Child2'/>
</C2>
</Root>
</xsl:template>
</xsl:stylesheet>
Dim xmlTree As XElement = _
<Parent>
<Child1>Child1 data</Child1>
<Child2>Child2 data</Child2>
</Parent>
Dim newTree As XDocument = New XDocument()
Using writer As XmlWriter = newTree.CreateWriter()
' Load the style sheet.
Dim xslt As XslCompiledTransform = _
New XslCompiledTransform()
xslt.Load(xslMarkup.CreateReader())
' Execute the transform and output the results to a writer.
xslt.Transform(xmlTree.CreateReader(), writer)
End Using
Console.WriteLine(newTree)
Si debe procesar datos que no son de confianza de orígenes desconocidos, Microsoft recomienda usar
una instancia de la clase XmlReader que se ha configurado para filtrar ataques de denegación de servicio
(DoS) XML.
Si ha configurado XmlReader para mitigar los ataques de denegación de servicio, puede usar ese lector
para rellenar un árbol de LINQ a XML y seguir sacando provecho de las mejoras de productividad del
programador de LINQ a XML. Muchas técnicas de reducción de riesgo implican crear lectores
configurados para mitigar el problema de seguridad y crear a continuación instancias de un árbol XML
mediante el lector configurado.
XML es intrínsecamente vulnerable a ataques de denegación de servicio porque los documentos no tienen
límite en el tamaño, la profundidad, el tamaño de nombre de elemento, etc. Independientemente del
componente que use para procesar XML, siempre debería estar preparado para reciclar el dominio de la
aplicación si utiliza demasiados recursos.
Cuando trabaja en un entorno menos seguro, existen varios problemas de seguridad asociados con XML
y con el uso de clases en System.Xml, System.Xml.Schema, System.Xml.XPath y System.Xml.Xsl.
Algunos de los problemas son:
XSD, XPath y XSLT son lenguajes basados en cadenas en los que puede especificar operaciones
que consumen mucho tiempo o memoria. Es responsabilidad de los programadores que toman
cadenas XSD, XPath o XSLT de orígenes que no son de confianza validar que dichas cadenas no
son malintencionadas o supervisar y reducir la posibilidad de que la evaluación de esas cadenas
provoque un consumo excesivo de recursos del sistema.
Los esquemas XSD (incluyendo los esquemas en línea) son intrínsecamente vulnerables a
ataques de denegación de servicio. No se deben aceptar esquemas de orígenes que no sean de
confianza.
XSD y XSLT pueden incluir referencias a otros archivos y esas referencias pueden producir
ataques entre dominios y zonas.
Las entidades externas de DTD pueden tener como resultado ataques entre dominios y zonas.
Los documentos XML con una profundidad excepcional pueden plantear problemas de
denegación de servicio. Es posible que desee limitar la profundidad de documentos XML.
Los bloques de scripts de las hojas de estilo XSLT pueden dar lugar a varios ataques.
Un ataque de elevación de privilegios que tiene éxito proporciona a un ensamblado malintencionado más
control sobre su entorno. Un ataque de elevación de privilegios que tiene éxito puede tener como
resultado una revelación de datos, una denegación de servicio, etc.
Las aplicaciones no deben revelar datos a los usuarios que no están autorizados a verlos.
Los ataques de denegación de servicio hacen que el analizador XML o LINQ a XML consuman excesiva
memoria o tiempo de CPU. Los ataques de denegación de servicio se consideran menos graves que los
ataques de elevación de privilegios o los ataques de revelación de datos. No obstante, son importantes
en un escenario en el que un servidor debe procesar documentos XML de orígenes que no sean de
confianza.
Un ejemplo de DTD aceptadas de orígenes que no son de confianza es una aplicación web que permite a
los usuarios cargar un archivo XML que hace referencia a una DTD y un archivo de DTD. Al validar el
archivo, una DTD malintencionada puede ejecutar un ataque de denegación de servicio en el servidor.
Otro ejemplo de DTD aceptadas de orígenes que no son de confianza es hacer referencia a una DTD en
un recurso compartido de red que también permite el acceso FTP anónimo.
Si un usuario malintencionado envía o carga un documento XML de gran tamaño, puede hacer que LINQ
a XML consuma demasiados recursos del sistema. Esto puede representar un ataque de denegación de
servicio. Para evitarlo, puede ajustar la propiedad XmlReaderSettings..::.MaxCharactersInDocument y
crear un lector con un límite en el tamaño del documento que puede cargar. El lector se usa a
continuación para crear el árbol XML.
Por ejemplo, si sabe que el tamaño máximo previsto de los documentos XML que provienen de un origen
que no es de confianza va a ser inferior a 50 KB, establezca
XmlReaderSettings..::.MaxCharactersInDocument en 100.000. Esto no será un inconveniente para el
procesamiento de sus documentos XML y a la vez contrarrestará amenazas de denegación de servicio en
las que se podrían cargar documentos que consumirían grandes cantidades de memoria.
Es posible que desee limitar los nombres incluidos en cualquier espacio de nombres dado (incluyendo
nombres que no están en ningún espacio de nombres) a una cantidad razonable.
Los componentes de software que comparten un árbol de LINQ a XML pueden tener acceso a
las anotaciones
LINQ a XML se puede usar para crear canalizaciones de procesamiento en las que diferentes
componentes de la aplicación cargan, validan, consultan, transforman, actualizan y guardan datos XML
que se pasan entre componentes como árboles XML. Esto puede ayudarle a optimizar el rendimiento,
porque la sobrecarga de carga y serialización de objetos a texto XML se realiza únicamente en los
extremos de la canalización. Sin embargo, los desarrolladores deben tener en cuenta que otros
componentes puedes tener acceso a todas las anotaciones y controladores de eventos creados por un
componente. Esto puede crear varias vulnerabilidades cuando los componentes tienen diferentes niveles
de confianza. Para crear canalizaciones seguras en componentes con un nivel de confianza inferior, debe
serializar los objetos LINQ a XML a texto XML antes de pasar los datos a un componente que no es de
confianza.
Common Language Runtime (CLR) proporciona cierta seguridad. Por ejemplo, un componente que no
incluye una clase privada no puede tener acceso a anotaciones con claves creadas por esa clase. No
obstante, las anotaciones pueden ser eliminadas por componentes que no las leen. Esto se podría usar
como ataque de manipulación.
LINQ se integra en varios aspectos del acceso a datos de .NET Framework, incluyendo el modelo de
programación desconectada del DataSet y los esquemas de base de datos de SQL Server existentes. En
esta sección se describe LINQ a ADO.NET, la implementación ADO.NET de LINQ.
El siguiente diagrama proporciona una visión general de cómo se relaciona LINQ a ADO.NET con
lenguajes de programación de alto nivel, diferentes de las tecnologías LINQ y orígenes de datos
compatibles con LINQ.
Basic) y un lenguaje de consulta para interactuar con la base de datos (como Transact-SQL). Esto
requiere que el programador tenga conocimientos de varios idiomas para ser efectivo y también causa
discrepancias de idiomas en el entorno de desarrollo. Por ejemplo, una aplicación que utiliza API de
acceso a datos para ejecutar una consulta en una base de datos especifica la consulta como un literal de
cadena usando comillas. Esta cadena de consulta es ilegible y no se comprueba si contiene errores, tales
como una sintaxis no válida o si existen las columnas o las filas a las que hace referencia. No hay
ninguna comprobación de tipo de los parámetros de consulta y tampoco hay compatibilidad con
IntelliSense.
Language-Integrated Query (LINQ) permite a los programadores formar consultas basadas en conjuntos
en el código de su aplicación, sin tener que usar un lenguaje de consulta independiente. Se puede
escribir consultas de LINQ en varios orígenes de datos enumerables (es decir, un origen de datos que
implementa la interfaz IEnumerable), como estructuras de datos en memoria, documentos XML, bases
de datos SQL y objetos DataSet. Aunque esos orígenes de datos enumerables se implementan de varias
formas, todos revelan las mismas construcciones de lenguaje y sintaxis. Como las consultas se pueden
formar en el lenguaje de programación mismo, no es necesario utilizar otro lenguaje de consultas que
esté incrustado como literales de cadena que el compilador no pueda entender o verificar. La integración
de consultas en el lenguaje de programación también permite a los programadores de Visual Studio ser
más productivos proporcionando comprobación de sintaxis y tipo en tiempo de compilación e
IntelliSense. Estas características reducen la necesidad de depuración y corrección de errores de
consultas.
LINQ a ADO.NET consta de dos tecnologías diferentes: LINQ a DataSet y LINQ a SQL. LINQ a DataSet
proporciona consultas más enriquecidas y optimizadas en DataSet y LINQ a SQL permite consultar
directamente esquemas de base de datos de SQL Server.
La transferencia de datos de las tablas de SQL a objetos de memoria a menudo es una tarea tediosa y
propensa a errores. El proveedor de LINQ implementado por LINQ a DataSet y LINQ a SQL convierte el
origen de datos en recopilaciones de objetos basadas en IEnumerable. El programador siempre ve los
datos como una recopilación de IEnumerable cuando se realiza la consulta y la actualización. Se
proporciona compatibilidad completa con IntelliSense para escribir consultas en esas colecciones.
En las siguientes secciones se proporciona más información acerca de LINQ a DataSet y LINQ a SQL.
LINQ a DataSet
DataSet es un elemento fundamental del modelo de programación desconectada sobre el que se basa
ADO.NET y se usa ampliamente. LINQ a DataSet permite a los programadores crear capacidades de
consulta más complejas en DataSet utilizando el mismo mecanismo de formulación de consultas que está
disponible para muchos otros orígenes de datos.
LINQ a SQL
LINQ a SQL es una herramienta útil para programadores que no requieren la asignación a un modelo
conceptual. Si utiliza LINQ a SQL, puede usar el modelo de programación de LINQ directamente en un
esquema de base de datos existente. LINQ a SQL permite a los programadores generar clases de .NET
Framework que representan datos. En lugar de la asignación a un modelo de datos conceptual, esas
clases generadas se asignan directamente a tablas de bases de datos, vistas, procedimientos
almacenados y funciones definidas por el usuario.
Con LINQ a SQL, los programadores pueden escribir código directamente en el esquema de
almacenamiento usando el mismo modelo de programación de LINQ que las recopilaciones en memoria y
DataSet, además de otros orígenes de datos como XML.
LINQ a DataSet también se puede utilizar para consultar en datos que se han consolidado de uno o más
orígenes de datos. Esto permite muchos casos que requieren flexibilidad en la forma de representar y
controlar los datos, como consultar datos agregados localmente y almacenar en caché en el nivel medio
en aplicaciones web. En concreto, las aplicaciones de Business Intelligence, análisis e informes genéricos
requieren este método de manipulación.
La funcionalidad LINQ a DataSet se expone principalmente mediante métodos de extensión en las clases
DataRowExtensions y DataTableExtensions. LINQ a DataSet se basa y utiliza la arquitectura existente
ADO.NET 2.0, y no está destinada a reemplazar ADO.NET 2.0 en el código de aplicación. El código de
ADO.NET 2.0 existente continuará funcionando en una aplicación LINQ a DataSet. La relación de LINQ a
DataSet con ADO.NET 2.0 y los datos almacenados se muestran en el diagrama siguiente.
explícitamente en caché datos de diferentes orígenes de datos. Para el nivel de presentación, DataSet
está estrechamente integrado en los controles de GUI para el enlace de datos. Para el nivel medio,
proporciona una caché que conserva la forma relacional de los datos e incluye servicios de exploración de
jerarquías y consultas rápidos y sencillos. Una técnica común que se usa para reducir el número de
solicitudes de una bases de datos consiste en utilizar DataSet para el almacenamiento en caché en el
nivel medio. Por ejemplo, piense en una aplicación web de ASP.NET controlada por datos. A menudo una
parte importante de los datos de aplicación no cambia frecuentemente y es común entre sesiones o
usuarios. Estos datos se pueden conservar en memoria o en un servidor web, lo que reduce el número
de solicitudes en la base de datos y acelera las interacciones del usuario. Otro aspecto útil de DataSet es
que permite que una aplicación lleve subconjuntos de datos de uno o más orígenes de datos al espacio
de la aplicación. La aplicación puede manipular los datos en memoria mientras retiene su forma
relacional.
A pesar de su importancia, DataSet tiene capacidades de consulta limitadas. El método Select se puede
usar para filtrar y ordenar y los métodos GetChildRows y GetParentRow se pueden usar para la
exploración de jerarquías. Sin embargo, para cualquier tarea más compleja, el programador debe escribir
una consulta personalizada. Esto puede tener como resultado aplicaciones con un bajo rendimiento y con
un mantenimiento difícil.
LINQ a DataSet facilita y acelera las consultas en datos almacenados en caché en un objeto DataSet.
Esas consultas se expresan en el lenguaje de programación mismo, en lugar de como literales de cadena
incrustados en el código de la aplicación. Esto significa que los desarrolladores no tienen que aprender un
lenguaje de consultas diferente. Adicionalmente, LINQ a DataSet permite a los desarrolladores de Visual
Studio trabajar de forma más productiva, ya que la IDE de Visual Studio proporciona comprobación de
sintaxis en tiempo de compilación, tipos estáticos y compatibilidad de IntelliSense con LINQ. LINQ a
DataSet también se puede usar para consultar los datos que se han consolidado de uno o más orígenes
de datos. Esto permite muchos casos que requieren flexibilidad en la forma de representar y controlar los
datos. En concreto, las aplicaciones de inteligencia empresaria, análisis e informes genéricos requieren
este método de manipulación.
Se admiten consultas de LINQ en objetos DataSet con tipo y sin tipo. Si el esquema de DataSet es
desconocido en tiempo de diseño, se recomienda un DataSet con tipo. En un DataSet con tipo, las tablas
y las filas tienen miembros con tipo para cada una de las columnas, lo que hace que las consultas sean
más sencillas y legibles.
En las aplicaciones con n niveles, a menudo se utiliza DataSet en el nivel medio para almacenar en caché
información para una aplicación web. La funcionalidad de consulta de LINQ a DataSet se implementa
mediante los métodos de extensión y DataSet de ADO.NET 2.0 existente.
En el siguiente diagrama se muestra cómo se relaciona LINQ a DataSet con el DataSet y cómo encaja en
una aplicación de nivel n:
Otra forma habitual de cargar datos en DataSet es utilizar la clase DataAdapter para recuperar datos
desde la base de datos. Esto se muestra en el ejemplo siguiente.
Ejemplo
En este ejemplo se utiliza DataAdapter para consultar en la base de datos AdventureWorks la
información de ventas del año 2002, y cargar los resultados en un DataSet. Una vez que se ha rellenado
DataSet, se pueden escribir consultas en él mediante LINQ a DataSet. En este ejemplo, se utiliza el
método FillDataSet en las consultas de ejemplo en Ejemplos de LINQ a DataSet.
Try
Dim connectionString As String
connectionString = "Data Source=localhost;Initial
Catalog=AdventureWorks;Integrated Security=true;"
' Create a new adapter and give it a query to fetch sales order,
' contact, address, and product information for sales in the year
' 2002. Point connection information to the configuration setting
' "AdventureWorks".
Dim da = New SqlDataAdapter( _
"SELECT SalesOrderID, ContactID, OrderDate, OnlineOrderFlag, " & _
"TotalDue, SalesOrderNumber, Status, ShipToAddressID,
BillToAddressID " & _
"FROM Sales.SalesOrderHeader " & _
"WHERE DATEPART(YEAR, OrderDate) = @year; " & _
"SELECT d.SalesOrderID, d.SalesOrderDetailID, d.OrderQty, " & _
"d.ProductID, d.UnitPrice " & _
Los distintos tipos de proyecto LINQ requieren determinados espacios de nombres importados (Visual
Basic) o directivas using (C#) y referencias. El requisito mínimo es una referencia a System.Core.dll y
una directiva using para System.Linq. De manera predeterminada, estos requisitos se proporcionan al
crear un proyecto Visual C# 2008 nuevo. LINQ a DataSet también requiere una referencia a
System.Data.dll y System.Data.DataSetExtensions.dll y una directiva Imports (VBasic) o using (C#).
Si está actualizando a un proyecto desde una versión anterior de Visual Studio, es posible que tenga que
proporcionar estas referencias relacionadas con LINQ de forma manual. También es posible que tenga
que configurar de forma manual el proyecto para establecer como destino la versión 3.5 de .NET
Framework.
Nota:
Si está generando el proyecto desde el símbolo del sistema, debe hacer referencia manualmente a
las DLL relacionadas con LINQ en drive:\Archivos de programa\Reference
Assemblies\Microsoft\Framework\v3.5.
2. Para un proyecto, haga clic en el menú Proyecto y, a continuación, haga clic en Propiedades.
3. Para un proyecto Visual Basic, haga clic en el menú Proyecto y, a continuación, haga clic en
Propiedades.
4. En el menú Proyecto, haga clic en Agregar referencia, haga clic en la ficha .NET, desplácese
hasta System.Core, haga clic en él y, a continuación, haga clic en Aceptar.
5. Agregue una directiva using o un espacio de nombres importado para System.Linq en el archivo
de código origen o proyecto.
1. Si es necesario, siga las instrucciones descritas anteriormente para agregar una referencia a
System.Core.dll y una directiva using o un espacio de nombres importado para System.Linq.
2. En C# o Visual Basic, haga clic en el menú Proyecto y, a continuación, haga clic en Agregar
referencia.
3. En el cuadro de diálogo Agregar referencia, haga clic en la ficha .NET si no está en la parte
superior. Desplácese hasta System.Data y System.Data.DataSetExtensions y haga clic en
ellos. Haga clic en el botón Aceptar.
Una operación de consulta de LINQ consta de tres acciones: obtener el origen o los orígenes de datos,
crear la consulta y ejecutar la consulta.
Los orígenes de datos que implementan la interfaz genérica IEnumerable<(Of <(T>)>) se pueden
consultar a través de LINQ. La llamada a AsEnumerable en DataTable devuelve un objeto que
implementa la interfaz IEnumerable<(Of <(T>)>) genérica, que actúa como origen de datos para las
consultas de LINQ a DataSet.
En la consulta se especifica exactamente la información que se desea recuperar del origen de datos. Una
consulta también puede especificar cómo se debe ordenar, agrupar y qué forma debe tener esa
información antes de que se devuelva. En LINQ, una consulta se almacena en una variable. Si la consulta
está diseñada para devolver una secuencia de valores, la variable misma de la consulta debe ser de tipo
enumerable. Esta variable de consulta no realiza ninguna acción y no devuelve datos; solamente
almacena la información de la consulta. Tras crear una consulta debe ejecutarla para recuperar los datos.
En una consulta que devuelve una secuencia de valores, la variable de consulta por sí misma nunca
conserva los resultados de la consulta y sólo almacena los comandos de la misma. La ejecución de la
consulta se aplaza hasta que la variable de consulta se recorre en iteración en un bucle foreach o For
Each. Esto se denomina ejecución aplazada; es decir, la ejecución de la consulta se produce después de
que se cree la consulta. Esto significa que se puede ejecutar una consulta con la frecuencia que se desee.
Esto es útil cuando, por ejemplo, se tiene una base de datos que otras aplicaciones están actualizando.
En su aplicación puede crear una consulta para recuperar la información más reciente y ejecutar de
forma repetida la consulta, devolviendo cada vez la información actualizada.
A diferencia de las consultas aplazadas, que devuelven una secuencia de valores, las consultas que
devuelven un valor singleton se ejecutan inmediatamente. Algunos ejemplos de consultas singleton son
Count, Max, Average y First. Se ejecutan inmediatamente porque se necesitan los resultados de la
consulta para calcular el resultado singleton. Por ejemplo, para buscar la media de los resultados de
consultas, debe ejecutarse la consulta para que la función de cálculo de media tenga datos de entrada
con los que trabajar. También puede usar los métodos ToList<(Of <(TSource>)>) o ToArray<(Of
<(TSource>)>) en una consulta para forzar la ejecución inmediata de una consulta que no crea un valor
singleton. Esas técnicas para forzar la ejecución inmediata pueden ser útiles si desea almacenar en caché
los resultados de una consulta.
Consultas
Las consultas de LINQ a DataSet se pueden formular en dos sintaxis diferentes: sintaxis de expresiones
de consulta y sintaxis de consultas basadas en métodos.
La sintaxis de expresiones de consulta es nueva en C# 3.0 y en Visual Basic 2008. No obstante, Common
Language Runtime (CLR) de .NET Framework no puede leer la sintaxis de expresiones de consulta en sí.
Por lo tanto, en tiempo de compilación, las expresiones de consulta se traducen a algo que CLR no
comprende: las llamadas a métodos. Esos métodos se conocen como operadores de consulta estándar.
Como desarrollador, tiene la opción de llamarlos directamente utilizando la sintaxis de método en lugar
de la sintaxis de consulta.
El siguiente ejemplo usa Select para devolver todas las filas de la tabla Product y mostrar los nombres de
producto.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim products As DataTable = ds.Tables("Product")
Dim query = From product In products.AsEnumerable() Select product
Console.WriteLine("Product Names:")
For Each p In query
Console.WriteLine(p.Field(Of String)("Name"))
Next
Sintaxis de consultas basadas en métodos
La otra forma de formular consultas de LINQ a DataSet es usar las consultas basadas en métodos. La
sintaxis de consultas basadas en métodos es una secuencia de llamadas a métodos directas a los
métodos de operador de LINQ, pasando expresiones lambda como parámetros.
Este ejemplo usa Select para devolver todas las filas de Product y mostrar los nombres de producto.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim products As DataTable = ds.Tables("Product")
Dim query = products.AsEnumerable() _
.Select(Function(product As DataRow) New With _
{ .ProductName = product.Field(Of String)("Name"), _
En el siguiente ejemplo, la primera consulta devuelve todos los productos ordenados por el precio de la
lista. El método ToArray<(Of <(TSource>)>) se usa para forzar la ejecución inmediata de la consulta:
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim products As DataTable = ds.Tables("Product")
Dim query = From product In products.AsEnumerable() Order By
product.Field(Of Decimal)("ListPrice") Descending Select product
' Force immediate execution of the query.
Al igual que con otras implementaciones de LINQ, puede crear consultas de LINQ a DataSet de dos
formas diferentes: sintaxis de expresión de consulta y sintaxis de consulta basada en métodos. Puede
usar la sintaxis de expresión de consulta o la sintaxis de consulta basada en métodos para realizar
consultas en tablas únicas en un DataSet, en tablas múltiples en un DataSet o en tablas en un DataSet
con tipo.
En el ejemplo siguiente se obtienen todos los pedidos en línea desde la tabla SalesOrderHeader y se
envían los resultados de id., fecha y número de pedido a la consola.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = _
From order In orders.AsEnumerable() _
Where order.Field(Of Boolean)("OnlineOrderFlag") = True _
Select New With { _
.SalesOrderID = order.Field(Of Integer)("SalesOrderID"), _
.OrderDate = order.Field(Of DateTime)("OrderDate"), _
.SalesOrderNumber = order.Field(Of String)("SalesOrderNumber")}
For Each onlineOrder In query
Console.Write("Order ID: " & onlineOrder.SalesOrderID)
Console.Write(" Order date: " & onlineOrder.OrderDate)
Console.WriteLine(" Order number: " & onlineOrder.SalesOrderNumber)
Next
La consulta variable local se inicializa con una expresión de consulta, la cual opera en una o varias
fuentes de información aplicando uno o varios operadores de consulta desde el operador estándar de
consulta o, en el caso de LINQ a DataSet, desde el operador específico de la clase DataSet. La expresión
de consulta del ejemplo anterior utiliza dos de los operadores estándar de consulta: Where y Select.
La cláusula Where filtra la secuencia basándose en una condición, en este caso que OnlineOrderFlag se
establezca en true. El operador Select asigna y devuelve un objeto enumerable que captura los
argumentos pasados al operador. En el ejemplo anterior, se crea un tipo anónimo con tres propiedades:
SalesOrderID, OrderDate y SalesOrderNumber. Los valores de estas tres propiedades se establecen en
los valores de las columnas SalesOrderID, OrderDate y SalesOrderNumber a partir de la tabla
SalesOrderHeader.
A continuación, el bucle foreach enumera el objeto enumerable devuelto por Select y produce los
resultados de la consulta. Dado que una consulta es un tipo Enumerable, que implementa
IEnumerable<(Of <(T>)>), la evaluación de la consulta se aplaza hasta que se procesa una iteración en
la variable de la consulta mediante el bucle foreach. El aplazamiento de la evaluación de consultas
permite que éstas se mantengan como valores que se pueden evaluar varias veces, y cada vez
produciendo resultados potencialmente diferentes.
El método Field proporciona acceso a los valores de columna de DataRow y SetField (que no se mostraba
en el ejemplo anterior) establece los valores de columna en DataRow. Tanto el método Field como el
método SetField controlan tipos que admiten valores null, por lo que no es necesario comprobar
explícitamente si hay valores null. Además, ambos son métodos genéricos, lo que significa que no es
necesario convertir el tipo de valor devuelto. Puede utilizar el descriptor de acceso de columna
preexistente en DataRow (por ejemplo, o["OrderDate"]), pero hacerlo le exigiría convertir el objeto de
valor devuelto al tipo apropiado. Si la columna admite valores null, debe comprobar si el valor es null
utilizando el método IsNull.
Observe que el tipo de datos especificado en el parámetro T genérico de los métodos Field y SetField
deben coincidir con el tipo del valor subyacente; en caso contrario, se producirá una
InvalidCastException. El nombre de columna especificado debe también coincidir con el nombre de una
columna en DataSet, en caso contrario, se producirá una ArgumentException. En ambos casos, la
excepción se produce en tiempo de ejecución de enumeración de datos, cuando se ejecuta la consulta.
El marco de trabajo Language-Integrated Query (LINQ) proporciona dos operadores de combinación, Join
y GroupJoin. Estos operadores efectúan combinaciones de igualdad, es decir, combinaciones que hacen
coincidir dos orígenes de datos únicamente cuando sus claves son iguales (por el contrario, Transact-SQL
admite operadores de combinación distintos de equals, como el operador less than).
En cuanto a las bases de datos relacionales, Join implementa una combinación interna. Una combinación
interna es un tipo de combinación en la que solamente se devuelven los objetos que tienen una
correspondencia en el conjunto de datos opuesto.
Los operadores GroupJoin no tienen equivalente directo en términos de bases de datos relacionales;
implementan un superconjunto de combinaciones internas y combinaciones externas izquierdas. Una
combinación externa izquierda es una combinación que devuelve cada elemento de la primera colección
(izquierda), aunque éste no tenga elementos correlacionados en la segunda colección.
Ejemplo
El ejemplo siguiente realiza una combinación tradicional de las tablas SalesOrderHeader y
SalesOrderDetail de la base de datos de ejemplo AdventureWorks para obtener pedidos en línea del mes
de agosto.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim details As DataTable = ds.Tables("SalesOrderDetail")
Dim query = From order In orders.AsEnumerable() _
Join detail In details.AsEnumerable() _
On order.Field(Of Integer)("SalesOrderID") Equals _
detail.Field(Of Integer)("SalesOrderID") _
Where order.Field(Of Boolean)("OnlineOrderFlag") = True And _
order.Field(Of DateTime)("OrderDate").Month = 8 _
Select New With _
{ .SalesOrderID = order.Field(Of Integer)("SalesOrderID"), _
.SalesOrderDetailID = detail.Field(Of Integer)("SalesOrderDetailID"), _
.OrderDate = order.Field(Of DateTime)("OrderDate"), _
.ProductID = detail.Field(Of Integer)("ProductID") _
}
For Each order In query
Console.WriteLine(order.SalesOrderID & vbTab & _
order.SalesOrderDetailID & vbTab & _
order.OrderDate & vbTab & _
order.ProductID)
Next
LINQ a DataSet también admite las consultas en un DataSet con tipo. Con un DataSet con tipo no hay
que usar el método Field genérico o el método SetField para tener acceso a los datos de la columna. Los
Antes de poder empezar a consultar un DataSet con tipo se debe generar la clase usando el Diseñador
de DataSet de Visual Studio 2008.
Ejemplo
En el siguiente ejemplo se muestra una consulta sobre un DataSet con tipo:
Dim orders = ds.Tables("SalesOrderHeader")
Dim query = From o In orders Where o.OnlineOrderFlag = True _
Select New {SalesOrderID:=o.SalesOrderID,OrderDate:=o.OrderDate, _
SalesOrderNumber := o.SalesOrderNumber}
For Each Dim onlineOrder In query
Console.WriteLine("{0}\t{1:d}\t{2}", onlineOrder.SalesOrderID, _
onlineOrder.OrderDate, onlineOrder.SalesOrderNumber)
Next
La clase DataRowComparer contiene una implementación de comparación del valor para DataRow, de
modo que esta clase se puede utilizar para operaciones de conjuntos como Distinct. No se puede crear
una instancia directamente de esta clase. En su lugar, debe utilizarse la propiedad Default para devolver
una instancia de DataRowComparer. Entonces, se llama al método Equals(DataRow, DataRow) y los dos
objetos DataRow que se van a comparar se pasan como parámetros de entrada. El método
Equals(DataRow, DataRow) devuelve true si los conjuntos ordenados de valores de columna de ambos
objetos DataRow son iguales; de lo contrario, devuelve false.
Ejemplo
Este ejemplo usa Intersect para devolver contactos que aparecen en ambas tablas.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contactTable As DataTable = ds.Tables("Contact")
El método CopyToDataTable utiliza el siguiente proceso para crear un DataTable a partir de una consulta:
1. El método CopyToDataTable clona un DataTable a partir de la tabla origen (un objeto DataTable
que implementa la interfaz IQueryable<(Of <(T>)>)). El origen de IEnumerable se ha originado de
una expresión LINQ a DataSet o una consulta de método.
2. El esquema del DataTable clonado se genera a partir de las columnas del primer objeto DataRow
enumerado en la tabla origen; el nombre de la tabla clonada es el nombre de la tabla origen con la
palabra "query" anexada.
3. En cada fila de la tabla origen, el contenido de la fila se copia en un objeto DataRow nuevo, el
cual, a continuación, se inserta en la tabla clonada. Las propiedades RowState y RowError se
mantienen en toda la operación de copia. Si los objetos DataRow de origen pertenecen a tablas
diferentes, se produce un ArgumentException.
4. Cuando todos los objetos DataRow de la tabla de entrada que se puede consultar se han
copiado, se devuelve el DataTable clonado. Si la secuencia origen no contiene objetos DataRow, el
método devuelve un DataTable vacío.
Observe que, si se llama al método CopyToDataTable, la consulta se enlaza a la tabla origen para
ejecutarse.
Cuando el método CopyToDataTable encuentra una referencia NULL o un tipo de valor que admite
valores NULL en una fila de la tabla origen, reemplaza el valor con Value. De este modo, los valores NULL
se controlan correctamente en el DataTable devuelto.
End Class
Ejemplo
En el ejemplo siguiente se consulta una colección de elementos cuyo precio es superior a $ 9,99 y se
crea una tabla a partir de los resultados de la consulta.
Dim book1 As New Book()
book1.Id = 1
book1.Price = 13.5
book1.Genre = "Comedy"
book1.Author = "Gustavo Achong"
Dim book2 As New Book
book2.Id = 2
book2.Price = 8.5
book2.Genre = "Drama"
book2.Author = "Jessie Zeng"
Dim movie1 As New Movie
movie1.Id = 1
movie1.Price = 22.99
movie1.Genre = "Comedy"
movie1.Director = "Marissa Barnes"
Dim movie2 As New Movie
movie2.Id = 1
movie2.Price = 13.4
movie2.Genre = "Action"
movie2.Director = "Emmanuel Fernandez"
Dim items(3) As Item
items(0) = book1
items(1) = book2
items(2) = movie1
items(3) = movie2
' Query for items with price greater than 9.99.
Dim query = From i In items Where i.Price > 9.99 _
Order By i.Price Select New With {i.Price, i.Genre}
Dim table As DataTable
table = query.CopyToDataTable()
Ejemplo
En el ejemplo siguiente se consulta una colección de elementos cuyo precio es superior a $ 9,99 y se
proyectan los resultados. La secuencia de tipos anónimos devuelta se carga en una tabla existente.
' Create a table with a schema that matches that of the query results.
Dim table As DataTable = New DataTable()
table.Columns.Add("Price", GetType(Integer))
table.Columns.Add("Genre", GetType(String))
' Query for items with price greater than 9.99.
Dim query = From i In items Where i.Price > 9.99 Order By i.Price _
Select New With {i.Price, i.Genre}
query.CopyToDataTable(table, LoadOption.PreserveChanges)
Ejemplo
En el ejemplo siguiente se consulta una colección de elementos cuyo precio es superior a $ 9,99 y se
proyectan los resultados. La secuencia de tipos anónimos devuelta se carga en una tabla existente. El
esquema de la tabla se amplía automáticamente porque los tipos Book y Movies se derivan del tipo Item.
ejemplo anterior. Además, ambos son métodos genéricos, lo que significa que no es necesario convertir
el tipo de valor devuelto.
LINQ a DataSet permite a los programadores crear consultas complejas y eficaces en DataSet utilizando
Language-Integrated Query (LINQ). No obstante, una consulta LINQ a DataSet devuelve una
enumeración de objetos DataRow, que no se utiliza con facilidad en un caso de enlace. Para facilitar el
proceso de enlace, se puede crear un objeto DataView a partir de una consulta LINQ a DataSet. Este
objeto DataView usa el filtrado y la ordenación especificados en la consulta, pero es más adecuado para
el enlace de datos. LINQ a DataSet amplía la funcionalidad del objeto DataView proporcionando filtrado y
ordenación basados en expresiones de LINQ, lo que permite operaciones de filtrado y ordenación mucho
más complejas y eficaces que las basadas en cadenas.
Observe que DataView representa la propia consulta y no es una vista encima de la consulta. DataView
se enlaza a un control de la interfaz de usuario, como DataGrid o DataGridView, proporcionando un
modelo de enlace de datos simple. DataView se puede crear también a partir de DataTable,
proporcionando una vista predeterminada de esa tabla.
Cuando se ha creado DataView, puede enlazarlo con un control de la interfaz de usuario en una
aplicación de formularios Windows o en una aplicación ASP.NET, o cambiar la configuración de filtro y
ordenación.
DataView construye un índice, que mejora considerablemente el rendimiento de las operaciones que
pueden utilizarlo, como filtro y ordenación. El índice de DataView se genera cuando se crea DataView y
cuando se modifica cualquier información de filtro u ordenación. La creación de DataView y el posterior
establecimiento de información de filtro y ordenación hace que el índice se genere al menos dos veces:
una cuando se crea DataView y la otra cuando se modifica cualquiera de las propiedades de ordenación y
filtrado.
Nota:
En la mayor parte de los casos, las expresiones utilizadas en el filtrado y la ordenación no deben
tener efectos secundarios y deben ser deterministas. Además, las expresiones no deben tener
ninguna lógica que dependa de un número de conjunto de ejecuciones, ya que las operaciones de
filtrado y ordenación se pueden ejecutar un número ilimitado de veces.
No se admite la creación de DataView a partir de una consulta que devuelve tipos anónimos o consultas
que realizan operaciones de combinación.
Sólo se admiten los siguientes operadores de consulta en una consulta utilizada para crear DataView:
Cast<(Of <(TResult>)>)
OrderBy
OrderByDescending
Select<(Of <(TRow, S>)>)
ThenBy
ThenByDescending
Where<(Of <(TRow>)>)
Tenga en cuenta que, cuando se crea un objeto DataView a partir de una consulta LINQ a DataSet, el
método Select<(Of <(TRow, S>)>) deber ser el método final que se invoca en la consulta. Esto se
muestra en el ejemplo siguiente, en el que se crea un objeto DataView de pedidos en línea ordenados
por el total a pagar:
Dim orders As DataTable = dataSet.Tables("SalesOrderHeader")
Dim query = From order In orders.AsEnumerable() _
Where order.Field(Of Boolean)("OnlineOrderFlag") = True _
Order By order.Field(Of Decimal)("TotalDue") Select order
Dim view As DataView = query.AsDataView()
bindingSource1.DataSource = view
También se pueden utilizar las propiedades basadas en cadena RowFilter y Sort para filtrar y ordenar
DataView después de haber sido creado a partir de una consulta. Observe que con esto se borrará la
información de ordenación y filtro heredada de la consulta. El ejemplo siguiente crea DataView a partir
de una consulta LINQ a DataSet que filtra por apellidos que empiezan por "S". La propiedad basada en
cadena Sort se establece para ordenar por apellidos en orden ascendente y después por nombres en
orden descendente:
Dim contacts As DataTable = dataSet.Tables("Contact")
Dim query = From contact In contacts.AsEnumerable() _
Where contact.Field(Of String)("LastName").StartsWith("S") _
Select contact
Dim view As DataView = query.AsDataView()
bindingSource1.DataSource = view
view.Sort = "LastName desc, FirstName asc"
Crear DataView a partir de DataTable
Además de poderse crear a partir de una consulta LINQ a DataSet, un objeto DataView se puede crear a
partir de DataTable utilizando el método AsDataView.
En el ejemplo siguiente, se crea DataView a partir de la tabla SalesOrderDetail y se establece ese objeto
como origen de datos de un objeto BindingSource: Este objeto actúa como proxy para un control
DataGridView.
Dim orders As DataTable = dataSet.Tables("SalesOrderDetail")
Dim view As DataView = orders.AsDataView()
bindingSource1.DataSource = view
dataGridView1.AutoResizeColumns()
El filtro y la ordenación pueden establecerse en DataView después de que se haya creado a partir de
DataTable. El ejemplo siguiente crea DataView a partir de la tabla Contact y establece la propiedad Sort
para ordenar por apellidos en orden ascendente y luego por nombres en orden descendente:
Dim contacts As DataTable = dataSet.Tables("Contact")
Dim view As DataView = contacts.AsDataView()
view.Sort = "LastName desc, FirstName asc"
bindingSource1.DataSource = view
dataGridView1.AutoResizeColumns()
No obstante, se produce una pérdida de rendimiento cuando se establece la propiedad RowFilter o Sort
después de haberse creado DataView a partir de una consulta, debido a que DataView construye un
índice para admitir operaciones de filtro y ordenación. El establecimiento de la propiedad RowFilter o Sort
hace que se vuelva a generar el índice de los datos, lo que agrega sobrecarga a la aplicación y reduce el
rendimiento. Siempre que sea posible, se recomienda especificar la información sobre filtro y ordenación
cuando se cree por primera vez DataView y evitar modificaciones posteriores.
La capacidad de filtrar datos utilizando criterios específicos y después presentarlos a un cliente mediante
un control de IU es un aspecto importante del enlace de datos. DataView proporciona varias maneras de
filtrar datos y devolver subconjuntos de filas de datos que reúnan determinados criterios. Además de
capacidades de filtro basado en cadena, DataView también proporciona la capacidad de utilizar
expresiones LINQ para los criterios de filtro. Las expresiones LINQ permiten operaciones de filtro más
complejas y eficaces que el filtro basado en cadena.
Los filtros basados en expresión son más eficaces y complejos que los sencillos filtros basados en
cadena. Los filtros basados en cadena y los basados en expresión se excluyen mutuamente. Cuando el
RowFilter basado en cadena se establece después de haber creado DataView desde una consulta, se
borra el filtro basado en expresión inferido a partir de la consulta.
Nota:
En la mayor parte de los casos, las expresiones utilizadas en el filtro no deben tener efectos
secundarios y deben ser deterministas. Además, las expresiones no deben tener ninguna lógica que
dependa de un número de conjunto de ejecuciones, porque las operaciones de filtrado se pueden
ejecutar un número ilimitado de veces.
Ejemplo
El siguiente ejemplo consulta en la tabla SalesOrderDetail los pedidos con una cantidad superior a 2 e
inferior a 6; crea un DataView desde una consulta y enlaza DataView a un BindingSource:
Dim orders As DataTable = dataSet.Tables("SalesOrderDetail")
Dim query = From order In orders.AsEnumerable() Where order.Field(Of
Int16)("OrderQty")>2 And order.Field(Of Int16)("OrderQty")<6 Select order
Dim view As DataView = query.AsDataView()
bindingSource1.DataSource = view
Ejemplo
El siguiente ejemplo crea un DataView desde una consulta de pedidos efectuados con posterioridad al 6
de junio de 2001:
Dim orders As DataTable = dataSet.Tables("SalesOrderHeader")
Dim query = From order In orders.AsEnumerable() Where order.Field(Of
DateTime)("OrderDate") > New DateTime(2002, 6, 1) Select order
Dim view As DataView = query.AsDataView()
bindingSource1.DataSource = view
Ejemplo
El filtro se puede combinar también con la ordenación. El siguiente ejemplo crea un DataView desde una
consulta de contactos cuyos apellidos empiezan por la letra "S", y los ordena primero por el apellido y
después por el nombre:
Dim contacts As DataTable = dataSet.Tables("Contact")
currCode = 1
Case "C", "G", "J", "K", "Q", "S", "X", "Z"
currCode = 2
Case "D", "T"
currCode = 3
Case "L"
currCode = 4
Case "M", "N"
currCode = 5
Case "R"
currCode = 6
End Select
' Check to see if the current code is the same as the last one
If (currCode <> prevCode) Then
' Check to see if the current code is 0 (a vowel); do not process vowels
If (currCode <> 0) Then
buffer.Append(currCode)
End If
End If
' Set the new previous character code
prevCode = currCode
' If the buffer size meets the length limit, then exit the loop
If (buffer.Length = length) Then
Exit For
End If
Next
' Pad the buffer, if required
size = buffer.Length
If (size < length) Then
buffer.Append("0", (length - size))
End If
' Set the value to return
value = buffer.ToString()
End If
' Return the value
Return value
End Function
Utilizar la propiedad RowFilter
La funcionalidad de filtro basado en cadena DataView existente funciona también en el contexto de LINQ
a DataSet.
El siguiente ejemplo crea un DataView desde la tabla Contact y, a continuación, establece la propiedad
RowFilter para que devuelva filas cuando el apellido del contacto sea "Zhu":
Dim contacts As DataTable = dataSet.Tables("Contact")
Dim view As DataView = contacts.AsDataView()
view.RowFilter = "LastName='Zhu'"
bindingSource1.DataSource = view
dataGridView1.AutoResizeColumns()
Después de haberse creado un DataView desde una consulta DataTable o LINQ a DataSet, se puede
utilizar la propiedad RowFilter para especificar subconjuntos de filas basados en sus valores de columna.
Los filtros basados en cadena y los basados en expresión se excluyen mutuamente. Al establecer la
propiedad RowFilter se borrará la expresión de filtro inferida a partir de la consulta LINQ a DataSet, y la
expresión de filtro no se podrá volver a restablecer.
Dim contacts As DataTable = dataSet.Tables("Contact")
Dim query = From contact In contacts.AsEnumerable() Where
contact.Field(Of String)("LastName") = "Hernandez" Select contact
Dim view As DataView = query.AsDataView()
bindingSource1.DataSource = view
dataGridView1.AutoResizeColumns()
view.RowFilter = "LastName='Zhu'"
Si desea devolver los resultados de una consulta determinada en los datos, en lugar de proporcionar una
vista dinámica de un subconjunto de los datos, para conseguir el máximo rendimiento puede utilizar los
métodos Find o FindRows de la DataView, en lugar de establecer la propiedad RowFilter. La propiedad
RowFilter es más idónea en una aplicación enlazada a datos donde un control enlazado muestra
resultados filtrados. El establecimiento de la propiedad RowFilter hace que se vuelva a generar el índice
de los datos, lo que agrega sobrecarga a la aplicación y reduce el rendimiento. Los métodos Find y
FindRows utilizan el índice actual, sin necesidad de volver a generarlo. Si va a llamar a Find o a FindRows
una única vez, entonces debería utilizar el DataView existente. Si va a llamar a Find o a FindRows varias
veces, debería crear un nuevo DataView para volver a generar el índice en la columna en la que desea
buscar y, a continuación, llamar a los métodos Find o FindRows.
Borrar el filtro
Después de haber configurado el filtro de DataView, éste se puede borrar mediante la propiedad
RowFilter. El filtro de DataView se pude borrar de dos maneras:
Establecer la propiedad RowFilter en null.
Establecer la propiedad RowFilter en una cadena vacía.
Ejemplo
El siguiente ejemplo crea un DataView desde una consulta y, a continuación, borra el filtro estableciendo
la propiedad RowFilter en null:
Dim orders As DataTable = dataSet.Tables("SalesOrderHeader")
Dim query = From order In orders.AsEnumerable() Where order.Field(Of
DateTime)("OrderDate") > New DateTime(2002, 11, 20) And order.Field(Of
Decimal)("TotalDue") < New Decimal(60.0) Select order
Dim view As DataView = query.AsDataView()
bindingSource1.DataSource = view
view.RowFilter = Nothing
Ejemplo
El siguiente ejemplo crea un DataView desde una tabla, establece la propiedad RowFilter y, a
continuación, borra el filtro estableciendo la propiedad RowFilter en una cadena vacía:
Dim contacts As DataTable = dataSet.Tables("Contact")
Dim view As DataView = contacts.AsDataView()
view.RowFilter = "LastName='Zhu'"
bindingSource1.DataSource = view
dataGridView1.AutoResizeColumns()
' Clear the row filter.
view.RowFilter = ""
La ordenación basada en expresión es más eficaz y compleja que la simple ordenación basada en
cadena. Observe que la ordenación basada en cadena y expresión se excluyen mutuamente. Cuando el
Sort basado en cadena se establece después de haber creado DataView desde una consulta, el filtro
basado en expresión inferido a partir de la consulta se borra y no se puede volver a restablecer.
El índice de DataView se genera cuando se crea DataView y cuando se modifica cualquier información de
filtro u ordenación. Obtendrá un rendimiento óptimo si suministra criterios de ordenación en la consulta
LINQ a DataSet a partir de la cual se crea DataView y no modifica posteriormente la información de
ordenación.
Nota:
En la mayor parte de los casos, las expresiones utilizadas en la ordenación no deben tener efectos
secundarios y deben ser deterministas. Además, las expresiones no deben tener ninguna lógica que
dependa de un número de conjunto de ejecuciones, porque las operaciones de ordenación se pueden
ejecutar un número ilimitado de veces.
Ejemplo
El siguiente ejemplo consulta en la tabla SalesOrderHeader y ordena las filas devueltas por fecha de
pedido; crea un DataView desde una consulta y enlaza DataView a un BindingSource:
Dim orders As DataTable = dataSet.Tables("SalesOrderHeader")
Dim query = From order In orders.AsEnumerable() Order By order.Field(Of
DateTime)("OrderDate") Select order
Dim view As DataView = query.AsDataView()
bindingSource1.DataSource = view
Ejemplo
El siguiente ejemplo consulta en la tabla SalesOrderHeader y ordena la fila devuelta por el importe total
a pagar; crea un DataView desde una consulta y enlaza DataView a un BindingSource:
Dim orders As DataTable = dataSet.Tables("SalesOrderHeader")
Dim query = From order In orders.AsEnumerable() Order By order.Field(Of
Decimal)("TotalDue") Select order
Dim view As DataView = query.AsDataView()
bindingSource1.DataSource = view
Ejemplo
El siguiente ejemplo consulta en la tabla SalesOrderDetail y ordena las filas devueltas por cantidad de
pedido y, a continuación, por id. del pedido de ventas; crea un DataView desde una consulta y enlaza
DataView a un BindingSource:
Ejemplo
El ejemplo siguiente crea DataView a partir de la tabla Contact y ordena las filas por apellido en orden
descendente y luego por nombres en orden ascendente:
Dim contacts As DataTable = dataSet.Tables("Contact")
Dim view As DataView = contacts.AsDataView()
view.Sort = "LastName desc, FirstName asc"
bindingSource1.DataSource = view
dataGridView1.AutoResizeColumns()
Ejemplo
En el siguiente ejemplo se consultan en la tabla Contact los apellidos que empiezan por la letra “S”. Se
crea DataView a partir de dicha consulta y se enlaza a un objeto BindingSource.
Dim contacts As DataTable = dataSet.Tables("Contact")
Dim query = From contact In contacts.AsEnumerable() Where
contact.Field(Of String)("LastName").StartsWith("S") Select contact
Dim view As DataView = query.AsDataView()
bindingSource1.DataSource = view
view.Sort = "LastName desc, FirstName asc"
Borrar la ordenación
Se puede borrar la información de ordenación de DataView después de haberla establecido mediante la
propiedad Sort. Hay dos formas de borrar información de ordenación en DataView:
Establecer la propiedad Sort en null.
Establecer la propiedad Sort en una cadena vacía.
Ejemplo
El siguiente ejemplo crea un DataView desde una consulta y borra la ordenación mediante el
establecimiento de la propiedad Sort en una cadena vacía:
Dim orders As DataTable = dataSet.Tables("SalesOrderHeader")
Dim query = From order In orders.AsEnumerable() Order By order.Field(Of
Decimal)("TotalDue") Select order
Dim view As DataView = query.AsDataView()
bindingSource1.DataSource = view
view.Sort = ""
Ejemplo
El siguiente ejemplo crea un DataView desde la tabla Contact y establece la propiedad Sort para ordenar
por apellidos en orden descendente. A continuación, la información de ordenación se borra estableciendo
la propiedad Sort en null:
Find y FindRows
DataView construye un índice. Un índice contiene claves generadas a partir de una o varias columnas de
la tabla o la vista. Dichas claves están almacenadas en una estructura que permite que DataView busque
de forma rápida y eficiente la fila o filas asociadas a los valores de cada clave. Las operaciones que
utilizan el índice, como las de filtro y ordenación, ven aumentar significativamente el rendimiento. El
índice de DataView se genera cuando se crea DataView y cuando se modifica cualquier información de
filtro u ordenación. La creación de DataView y el posterior establecimiento de información de filtro y
ordenación hace que el índice se genere al menos dos veces: una cuando se crea DataView y la otra
cuando se modifica cualquiera de las propiedades de ordenación y filtrado.
Si desea devolver los resultados de una consulta determinada en los datos, en lugar de proporcionar una
vista dinámica de un subconjunto de los datos, para conseguir el máximo rendimiento puede utilizar los
métodos Find o FindRows de la DataView, en lugar de establecer la propiedad RowFilter. La propiedad
RowFilter es más idónea en una aplicación enlazada a datos donde un control enlazado muestra
resultados filtrados. El establecimiento de la propiedad RowFilter hace que se vuelva a generar el índice
de los datos, lo que agrega sobrecarga a la aplicación y reduce el rendimiento. Los métodos Find y
FindRows utilizan el índice actual, sin necesidad de volver a generarlo. Si va a llamar a Find o a FindRows
una única vez, entonces debería utilizar el DataView existente. Si va a llamar a Find o a FindRows varias
veces, debería crear un nuevo DataView para volver a generar el índice en la columna en la que desea
buscar y, a continuación, llamar a los métodos Find o FindRows.
El ejemplo siguiente utiliza el método Find para buscar un contacto con el apellido "Zhu".
Dim contacts As DataTable = dataSet.Tables("Contact")
Dim query = From contact In contacts.AsEnumerable() Order By
contact.Field(Of String)("LastName") Select contact
Dim view As DataView = query.AsDataView()
Dim found As Integer = view.Find("Zhu")
En el ejemplo siguiente se usa el método FindRows para buscar todos los productos de color rojo.
Dim products As DataTable = dataSet.Tables("Product")
Dim query = From product In products.AsEnumerable() Order By
product.Field(Of Decimal)("ListPrice"), product.Field(Of String)("Color")
Select product
Dim view As DataView = query.AsDataView()
view.Sort = "Color"
Dim criteria As Object() = New Object() {"Red"}
Dim foundRowsView As DataRowView() = view.FindRows(criteria)
ASP.NET
En el ejemplo siguiente, DataView se almacena en caché para que los datos no tengan que volver a
ordenarse cuando la página se actualice.
If (Cache("ordersView") = Nothing) Then
Dim dataSet As New DataSet()
FillDataSet(dataSet)
Dim orders As DataTable = dataSet.Tables("SalesOrderHeader")
Dim query = From order In orders.AsEnumerable() Where order.Field(Of
Boolean)("OnlineOrderFlag") = True Order By order.Field(Of
Decimal)("TotalDue") Select order
Dim view As DataView = query.AsDataView()
Cache.Insert("ordersView", view)
End If
Dim ordersView = CType(Cache("ordersView"), DataView)
GridView1.DataSource = ordersView
GridView1.DataBind()
1. Implemente un método que controle los detalles de recuperación de datos desde una base de
datos. En el ejemplo de código siguiente se implementa un método GetData que inicializa un
componente SqlDataAdapter y lo utiliza para rellenar un DataSet. Asegúrese de establecer la
variable connectionString en un valor que sea adecuado para la base de datos. Necesitará tener
acceso a un servidor que tenga la base de datos de ejemplo AdventureWorks de SQL Server
instalada.
Private Sub GetData()
Try
' Initialize the DataSet.
dataSet = New DataSet()
dataSet.Locale = CultureInfo.InvariantCulture
' Create the connection string for the AdventureWorks sample database
Dim connectionString As String = "Data Source=localhost;
Initial Catalog=AdventureWorks;Integrated Security=true;"
' Create the command strings for querying the Contact table.
Dim contactSelectCommand As String = "SELECT ContactID,
Title, FirstName, LastName, EmailAddress, Phone FROM Person.Contact"
' Create the contacts data adapter.
contactsDataAdapter = New SqlDataAdapter( _
contactSelectCommand,connectionString)
' Create a command builder to generate SQL update, insert, and
' delete commands based on the contacts select command. These
' are used to update the database.
Dim contactsCommandBuilder As SqlCommandBuilder = _
New SqlCommandBuilder(contactsDataAdapter)
' Fill the data set with the contact information.
contactsDataAdapter.Fill(dataSet, "Contact")
Catch ex As SqlException
MessageBox.Show(ex.Message)
End Try
End Sub
2. En el controlador de eventos Load del formulario, enlace el control DataGridView al componente
BindingSource y llame al método GetData para recuperar los datos de la base de datos. DataView
se crea a partir de una consulta LINQ a DataSet en DataTable Contact y luego se enlaza al
componente BindingSource.
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles MyBase.Load
' Connect to the database and fill the DataSet.
GetData()
contactDataGridView.DataSource = contactBindingSource
' Create a LinqDataView from a LINQ to DataSet query and bind it
' to the Windows forms control.
Dim contactQuery = From row In dataSet.Tables("Contact").
AsEnumerable Where row.Field(Of String)("EmailAddress")<>Nothing
Order By row.Field(Of String)("LastName") Select row
contactView = contactQuery.AsDataView()
' Bind the DataGridView to the BindingSource.
contactBindingSource.DataSource = contactView
contactDataGridView.AutoResizeColumns()
End Sub
Puede ver el resultado de una instrucción LINQ a DataSet mediante Información sobre datos, ventana
Inspección y cuadro de diálogo Inspección rápida. Al usar una ventana de código fuente, puede pausar el
puntero en una consulta en la ventana de código fuente para que aparezca una información sobre datos.
Puede copiar una variable LINQ a DataSet y pegarla en la ventana Inspección o en el cuadro de diálogo
Inspección rápida. En LINQ a DataSet, una consulta no se evalúa cuando se crea o declara, pero
únicamente cuando se ejecuta. Esto se denomina ejecución aplazada. Por consiguiente, la variable de
consulta no tiene un valor hasta que se evalúe.
Para mostrar el resultado de una consulta, el depurador debe evaluarla. Esta evaluación implícita se
produce cuando aparece un resultado de la consulta LINQ a DataSet en el depurador y tiene algunos
efectos que debería considerar. Cada evaluación de la consulta lleva tiempo. Expandir el nodo de
resultados lleva tiempo. Para algunas consultas, la evaluación repetida podría producir una reducción
notable del rendimiento. Evaluar una consulta también puede producir efectos secundarios, que son
cambios en el valor de los datos o en el estado del programa. No todas las consultas tienen los efectos
secundarios. Para determinar si una consulta se puede evaluar sin ningún riesgo ni efectos secundarios,
debe entender el código que implementa la consulta.
Editar y continuar
Editar y continuar no admite los cambios a las consultas LINQ a DataSet. Si agrega, quita o cambia una
instrucción LINQ a DataSet durante una sesión de depuración, aparece un cuadro de diálogo indicando
que Editar y continuar no admite el cambio. En ese momento, puede deshacer los cambios o detener la
sesión de depuración y reiniciar una nueva sesión con el código revisado.
Además, Editar y continuar no permite cambiar el tipo o el valor de una variable que se usa en una
instrucción LINQ a DataSet. De nuevo, puede deshacer los cambios o detener y reiniciar la sesión de
depuración.
En Visual C# 2008, no puede usar Editar y continuar en cualquier código en un método que contiene una
consulta LINQ a DataSet.
En Visual Basic 2008, puede usar Editar y continuar en código sin LINQ a DataSet, incluso en un método
que contiene una consulta LINQ a DataSet. Puede agregar o quitar el código antes de la instrucción LINQ
a DataSet, incluso si los cambios afectan al número de línea de la consulta LINQ a DataSetLINQ. La
experiencia en depuración de Visual Basic para código sin LINQ a DataSet es igual que antes de incluir
LINQ a DataSet. No se puede cambiar, agregar o quitar una consulta LINQ a DataSet, a menos que
desee detener la depuración para aplicar los cambios.
8.2.2.8. Seguridad
En este tema se tratan temas de seguridad en LINQ a DataSet.
Esto implica que, al pasar una referencia a una consulta realizada a otra parte del código, el componente
que recibe la consulta tiene acceso a todos los miembros públicos y privados a los que hace referencia la
consulta. En general, las consultas LINQ a DataSet no se deben pasar a componentes que no sean de
confianza a menos que la consulta se haya construido cuidadosamente para que no muestre la
información que debe ser privada.
Entrada externa
A menudo, las aplicaciones obtienen entradas externas (de un usuario o de otro agente externo) y
realizan acciones basadas en dicha entrada. En el caso de LINQ a DataSet, es posible que la aplicación
construya una consulta de determinada manera en función de la entrada externa o utilice dicha entrada
en la consulta. Las consultas LINQ a DataSet aceptan parámetros siempre que se acepten literales. Los
desarrolladores de aplicaciones deben utilizar consultas parametrizadas en lugar de insertar literales en
la consulta procedentes de un agente externo.
Cualquier entrada derivada directa o indirectamente del usuario o de un agente externo puede inyectar
contenido que aproveche la sintaxis del lenguaje de destino para realizar acciones no autorizadas. Esto
se conoce como ataque de inyección de SQL porque en el modelo de ataque el idioma de destino es
Transact-SQL. La entrada de usuario inyectada directamente en la consulta se utiliza para eliminar una
tabla de base de datos, provocar un ataque de denegación de servicio o cambiar la naturaleza de la
operación que se está realizando. Aunque la composición de consultas es posible en LINQ a DataSet,
ésta se realiza a través de la API del modelo de objetos. Las consultas LINQ a DataSet no se construyen
manipulando ni concatenando cadenas como ocurre en Transact-SQL y no son susceptibles de ataques
de inyección de SQL en el sentido tradicional.
8.2.2.9.1.1. Proyección
Los ejemplos de este tema muestran cómo usar los métodos Select y SelectMany para consultar DataSet
usando la sintaxis de expresión de consultas.
Los ejemplos de este tema usan las tablas Contact, Address, Product, SalesOrderHeader y
SalesOrderDetail en la base de datos de ejemplo de AdventureWorks.
Imports System.Collections.Generic
Imports System.Data
Imports System.Data.SqlClient
Imports System.Data.Common
Imports System.Globalization
Select
Ejemplo
Este ejemplo utiliza Select para devolver todas las filas de la tabla Product y mostrar los nombres de
producto.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim products As DataTable = ds.Tables("Product")
Dim query = From product In products.AsEnumerable() Select product
Console.WriteLine("Product Names:")
For Each p In query
Console.WriteLine(p.Field(Of String)("Name"))
Next
Ejemplo
Este ejemplo utiliza Select para devolver una secuencia de nombres de producto solamente.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim products As DataTable = ds.Tables("Product")
Dim query = From product In products.AsEnumerable() Select product.Field
(Of String)("Name")
Console.WriteLine("Product Names:")
For Each productName In query
Console.WriteLine(productName)
Next
SelectMany
Ejemplo
En este ejemplo se utiliza From …, … (el equivalente del método SelectMany) para seleccionar todos los
pedidos en los que TotalDue es inferior a 500,00.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contacts As DataTable = ds.Tables("Contact")
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = From contact In contacts.AsEnumerable() From order In
orders.AsEnumerable() Where (contact.Field(Of Integer)("ContactID") =
order.Field(Of Integer)("ContactID")) And (order.Field(Of Decimal)
("TotalDue") < 500D) Select New With _
{
.ContactID = contact.Field(Of Integer)("ContactID"), _
.LastName = contact.Field(Of String)("LastName"), _
.FirstName = contact.Field(Of String)("FirstName"), _
.OrderID = order.Field(Of Integer)("SalesOrderID"), _
.TotalDue = order.Field(Of Decimal)("TotalDue")}
For Each smallOrder In query
Console.Write("ContactID: " & smallOrder.ContactID)
Console.Write(" Name: {0}, {1}", smallOrder.LastName, _
smallOrder.FirstName)
Console.Write(" OrderID: " & smallOrder.OrderID)
Console.WriteLine(" TotalDue: $" & smallOrder.TotalDue)
Next
Ejemplo
En este ejemplo se utiliza From …, … (el equivalente del método SelectMany) para seleccionar todos los
pedidos efectuados a partir del 1 de octubre.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contacts As DataTable = ds.Tables("Contact")
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = From contact In contacts.AsEnumerable() From order In
orders.AsEnumerable() Where contact.Field(Of Integer)("ContactID") =
order.Field(Of Integer)("ContactID") And order.Field(Of DateTime)
("OrderDate") >= New DateTime(2002, 10, 1) Select New With _
{ .ContactID = contact.Field(Of Integer)("ContactID"), _
.LastName = contact.Field(Of String)("LastName"), _
.FirstName = contact.Field(Of String)("FirstName"), _
.OrderID = order.Field(Of Integer)("SalesOrderID"), _
.OrderDate = order.Field(Of DateTime)("OrderDate") }
For Each order In query
Console.Write("Contact ID: " & order.ContactID)
Console.Write(" Name: " & order.LastName & ", " & order.FirstName)
Console.Write(" Order ID: " & order.OrderID)
Console.WriteLine(" Order date: {0:d} ", order.OrderDate)
Next
Ejemplo
En este ejemplo se utiliza From …, … (el equivalente al método SelectMany) para seleccionar todos los
pedidos en los que el total del pedido es superior a 10.000,00 y utiliza la asignación From para evitar
que se solicite dos veces el total.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contacts As DataTable = ds.Tables("Contact")
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = From contact In contacts.AsEnumerable() From order In
orders.AsEnumerable() Let total = order.Field(Of Decimal)("TotalDue")
8.2.2.9.1.2. Restricción
Los ejemplos de este tema muestran cómo utilizar el método Where para consultar DataSet utilizando
sintaxis de expresión de consultas.
Where
Ejemplo
Este ejemplo devuelve todos los pedidos en línea.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = From order In orders.AsEnumerable() Where order.Field(Of
Boolean)("OnlineOrderFlag") = True Select New With _
{ .SalesOrderID = order.Field(Of Integer)("SalesOrderID"), _
.OrderDate = order.Field(Of DateTime)("OrderDate"), _
.SalesOrderNumber = order.Field(Of String)("SalesOrderNumber") }
For Each onlineOrder In query
Console.Write("Order ID: " & onlineOrder.SalesOrderID)
Console.Write(" Order date: " & onlineOrder.OrderDate)
Console.WriteLine(" Order number: " & onlineOrder.SalesOrderNumber)
Next
Ejemplo
Este ejemplo devuelve los pedidos en los que la cantidad de pedido es superior a 2 e inferior a 6.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim orders As DataTable = ds.Tables("SalesOrderDetail")
Dim query = From order In orders.AsEnumerable() Where order.Field(Of
Short)("OrderQty") > 2 And order.Field(Of Short)("OrderQty") < 6 _
Select New With _
{ .SalesOrderID = order.Field(Of Integer)("SalesOrderID"), _
Los ejemplos de este tema demuestran cómo usar los métodos Skip<(Of <(TSource>)>) y Take<(Of
<(TSource>)>) para consultar un DataSet usando la sintaxis de expresión de consultas.
Skip
Ejemplo
En este ejemplo se utiliza el método Skip<(Of <(TSource>)>) para obtener las dos primeras direcciones
de Seattle.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim addresses As DataTable = ds.Tables("Address")
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = (From address In addresses.AsEnumerable() From order In
orders.AsEnumerable() Where (address.Field(Of Integer)("AddressID") =
order.Field(Of Integer)("BillToAddressID")) And address.Field(Of
String)("City") = "Seattle" Select New With _
{ .City = address.Field(Of String)("City"), _
.OrderID = order.Field(Of Integer)("SalesOrderID"), _
.OrderDate = order.Field(Of DateTime)("OrderDate") _
}).Skip(2)
Console.WriteLine("All but first 2 orders in Seattle:")
For Each addOrder In query
Console.Write("City: " & addOrder.City)
Console.Write(" Order ID: " & addOrder.OrderID)
Console.WriteLine(" Order date: " & addOrder.OrderDate)
Next
Take
Ejemplo
En este ejemplo se utiliza el método Take<(Of <(TSource>)>) para obtener las tres primeras direcciones
de Seattle.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim addresses As DataTable = ds.Tables("Address")
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = (From address In addresses.AsEnumerable() From order In
orders.AsEnumerable() Where (address.Field(Of Integer)("AddressID") =
order.Field(Of Integer)("BillToAddressID")) And address.Field(Of
String)("City") = "Seattle" Select New With _
{ .City = address.Field(Of String)("City"), _
.OrderID = order.Field(Of Integer)("SalesOrderID"), _
.OrderDate = order.Field(Of DateTime)("OrderDate") _
}).Take(3)
Console.WriteLine("First 3 orders in Seattle:")
For Each order In query
Console.Write("City: " & order.City)
8.2.2.9.1.4. Ordenación
Los ejemplos de este tema muestran cómo se utilizan los métodos OrderBy, OrderByDescending,
Reverse<(Of <(TSource>)>) y ThenByDescending para consultar DataSet y ordenar los resultados
utilizando la sintaxis de expresiones de consultas.
OrderBy
Ejemplo
En este ejemplo se utiliza OrderBy para devolver una lista de contactos organizados por apellido.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contacts As DataTable = ds.Tables("Contact")
Dim query = From contact In contacts.AsEnumerable() Select contact Order
By contact.Field(Of String)("LastName")
Console.WriteLine("The sorted list of last names:")
For Each contact In query
Console.WriteLine(contact.Field(Of String)("LastName"))
Next
Ejemplo
En este ejemplo se utiliza OrderBy para ordenar una lista de contactos por longitud del apellido.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contacts As DataTable = ds.Tables("Contact")
Dim query = From contact In contacts.AsEnumerable() Select contact Order
By contact.Field(Of String)("LastName").Length
Console.WriteLine("The sorted list of last names (by length):")
For Each contact In query
Console.WriteLine(contact.Field(Of String)("LastName"))
Next
OrderByDescending
Ejemplo
En este ejemplo se utiliza orderby… descending (Order By … Descending), equivalente al método
OrderByDescending, para ordenar el precio de venta de mayor a menor.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim products As DataTable = ds.Tables("Product")
ElementAt
Ejemplo
Este ejemplo usa el método ElementAt<(Of <(TSource>)>) para recuperar la quinta dirección donde
PostalCode == "M4B 1V7".
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim addresses As DataTable = ds.Tables("Address")
Dim fifthAddress = (From address In addresses.AsEnumerable() Where
address.Field(Of String)("PostalCode") = "M4B 1V7" Select
address.Field(Of String)("AddressLine1")).ElementAt(5)
Console.WriteLine("5Address where PostalCode='M4B 1V7':" & fifthAddress)
First
Ejemplo
Este ejemplo usar el método First para devolver el primer contacto cuyo nombre es 'Brooke'.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contacts As DataTable = ds.Tables("Contact")
Dim query = (From contact In contacts.AsEnumerable() Where
contact.Field(Of String)("FirstName") = "Brooke" Select contact).First()
Console.WriteLine("ContactID: " & query.Field(Of Integer)("ContactID"))
Console.WriteLine("FirstName: " & query.Field(Of String)("FirstName"))
Console.WriteLine("LastName: " & query.Field(Of String)("LastName"))
Average
Ejemplo
En este ejemplo se utiliza el método Average para encontrar el precio de venta promedio de cada estilo
de productos.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim products As IEnumerable(Of DataRow) = _
ds.Tables("Product").AsEnumerable()
Dim query = From product In products Group product By style =
product.Field(Of String)("Style") Into g = Group Select New With _
{ .Style = style, _
.AverageListPrice = g.Average(Function(product) _
product.Field(Of Decimal)("ListPrice")) }
For Each product In query
Console.WriteLine("Product style: {0} Average list price: {1}", _
product.Style, product.AverageListPrice)
Next
Ejemplo
En este ejemplo se utiliza Average para obtener el importe total a pagar promedio para cada id. de
contacto.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = From order In orders.AsEnumerable() Group order By contactID
= order.Field(Of Int32)("ContactID") Into g = Group Select New With _
{ .Category = contactID, _
.averageTotalDue = g.Average(Function(order) order. _
Field(Of Decimal)("TotalDue")) }
For Each order In query
Console.WriteLine("ContactID = {0} " & vbTab & " Average TotalDue =
{1}", order.Category, order.averageTotalDue)
Next
Ejemplo
En este ejemplo se utiliza Average para obtener los pedidos con el TotalDue promedio para cada de
contacto.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = From order In orders.AsEnumerable() Group order By contactID
= order.Field(Of Int32)("ContactID") Into g = Group Let averageTotalDue =
g.Average(Function(order) order.Field(Of Decimal)("TotalDue"))
Select New With _
{ .Category = contactID, _
.CheapestProducts = g.Where(Function(order) order. _
Field(Of Decimal)("TotalDue") = averageTotalDue) }
For Each orderGroup In query
Console.WriteLine("ContactID: " & orderGroup.Category)
For Each order In orderGroup.CheapestProducts
Console.WriteLine("Average total due for SalesOrderID {1} is: {0}", _
order.Field(Of Decimal)("TotalDue"),order.Field(Of Int32)("SaleOrderID"))
Next
Console.WriteLine("")
Next
Count
Ejemplo
En este ejemplo se utiliza Count para devolver una lista de id. de contactos y el número de pedidos que
tiene cada uno de ellos.
' Fill the DataSet.
Los ejemplos de este tema demuestran cómo usar los métodos GroupJoin y Join para consultar un
DataSet usando la sintaxis de expresión de consultas.
GroupJoin
Ejemplo
Este ejemplo realiza un GroupJoin en las tablas SalesOrderHeader y SalesOrderDetail para buscar el
número de pedidos por cliente. Una combinación de grupo es el equivalente de una combinación externa
izquierda, que devuelve cada elemento del primer origen de datos (izquierdo), incluso si no hay
elementos correlacionados en el otro origen de datos.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim orders = ds.Tables("SalesOrderHeader").AsEnumerable()
Dim details = ds.Tables("SalesOrderDetail").AsEnumerable()
Dim query = From order In orders Group Join detail In details On
order.Field(Of Integer)("SalesOrderID") Equals detail.Field(Of
Integer)("SalesOrderID") Into ords = Group Select New With _
{ .CustomerID = order.Field(Of Integer)("SalesOrderID"), _
.ords = ords.Count() }
For Each order In query
Console.WriteLine("CustomerID: {0} Orders Count: {1}", _
order.CustomerID, order.ords)
Next
Ejemplo
Este ejemplo realiza un GroupJoin en las tablas Contact y SalesOrderHeader. Una combinación de grupo
es el equivalente de una combinación externa izquierda, que devuelve cada elemento del primer origen
de datos (izquierdo), incluso si no hay elementos correlacionados en el otro origen de datos.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contacts As DataTable = ds.Tables("Contact")
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = From contact In contacts.AsEnumerable(), order In
orders.AsEnumerable() Where (contact.Field(Of Integer)("ContactID") =
order.Field(Of Integer)("ContactID")) Select New With _
{ .ContactID = contact.Field(Of Integer)("ContactID"), _
.SalesOrderID = order.Field(Of Integer)("SalesOrderID"), _
.FirstName = contact.Field(Of String)("FirstName"), _
.Lastname = contact.Field(Of String)("Lastname"), _
.TotalDue = order.Field(Of Decimal)("TotalDue") }
For Each contact_order In query
Console.Write("ContactID: " & contact_order.ContactID)
Console.Write(" SalesOrderID: " & contact_order.SalesOrderID)
Console.Write(" FirstName: " & contact_order.FirstName)
Console.Write(" Lastname: " & contact_order.Lastname)
Console.WriteLine(" TotalDue: " & contact_order.TotalDue)
Next
Join
Ejemplo
Este ejemplo realiza una combinación en las tablas SalesOrderHeader y SalesOrderDetail para obtener
pedidos en línea del mes de agosto.
' Fill the DataSet.
8.2.2.9.2.1. Proyección
Los ejemplos de este tema muestran cómo usar los métodos Select y SelectMany para consultar DataSet
usando la sintaxis de consulta basada en métodos.
Select
Ejemplo
En este ejemplo se utiliza el método Select para proyectar las propiedades Name, ProductNumber y
ListPrice en una secuencia de tipos anónimos. El nombre de la propiedad ListPrice se cambia a Price en el
tipo resultante.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim products As DataTable = ds.Tables("Product")
Dim query = products.AsEnumerable() .Select(Function(product As DataRow)
New With _
{ .ProductName = product.Field(Of String)("Name"), _
.ProductNumber = product.Field(Of String)("ProductNumber"), _
.Price = product.Field(Of Decimal)("ListPrice") })
Console.WriteLine("Product Info:")
For Each product In query
Console.Write("Product name: " & product.ProductName)
Console.Write("Product number: " & product.ProductNumber)
Console.WriteLine("List price: $ " & product.Price)
Next
SelectMany
Ejemplo
En este ejemplo se utiliza el método SelectMany para seleccionar todos los pedidos en los que TotalDue
es inferior a 500,00.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contacts = ds.Tables("Contact").AsEnumerable()
Dim orders = ds.Tables("SalesOrderHeader").AsEnumerable()
Dim query = contacts.SelectMany(Function(contact)
orders.Where(Function(order)(contact.Field(Of Int32)("ContactID") =
order.Field(Of Int32)("ContactID")) And order.Field(Of Decimal)
("TotalDue") < 500D) .Select(Function(order) New With _
{ .ContactID = contact.Field(Of Integer)("ContactID"), _
.LastName = contact.Field(Of String)("LastName"), _
.FirstName = contact.Field(Of String)("FirstName"), _
.OrderID = order.Field(Of Integer)("SalesOrderID"), _
.Total = order.Field(Of Decimal)("TotalDue") }))
For Each smallOrder In query
Console.Write("ContactID: " & smallOrder.ContactID)
Console.Write(" Name: " & smallOrder.LastName & ", " &
smallOrder.FirstName)
Console.Write(" Order ID: " & smallOrder.OrderID)
Console.WriteLine(" Total Due: $" & smallOrder.Total)
Next
Ejemplo
En este ejemplo se utiliza el método SelectMany para seleccionar todos los pedidos efectuados a partir
del 1 de octubre.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contacts = ds.Tables("Contact").AsEnumerable()
Dim orders = ds.Tables("SalesOrderHeader").AsEnumerable()
Dim query = contacts.SelectMany(Function(contact)
orders.Where(Function(order)(contact.Field(Of Int32)("ContactID") =
order.Field(Of Int32)("ContactID")) And order.Field(Of DateTime)
("OrderDate")>=New DateTime(2002, 10, 1)) .Select(Function(order) New
With { .ContactID = contact.Field(Of Integer)("ContactID"),
.LastName = contact.Field(Of String)("LastName"), _
Skip
Ejemplo
En este ejemplo se utiliza el método Skip<(Of <(TSource>)>) para obtener todos los contactos menos
los cinco primeros de la tabla Contact.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contacts As DataTable = ds.Tables("Contact")
Dim allButFirst5Contacts = contacts.AsEnumerable().Skip(5)
Console.WriteLine("All but first 5 contacts:")
For Each contact In allButFirst5Contacts
Console.Write("FirstName = {0} ", contact.Field(Of
String)("FirstName"))
Console.WriteLine(vbTab & " LastName = " & contact.Field(Of
String)("LastName"))
Next
Ejemplo
En este ejemplo se utiliza el método Skip<(Of <(TSource>)>) para obtener las dos primeras direcciones
de Seattle.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim addresses As DataTable = ds.Tables("Address")
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = (From address In addresses.AsEnumerable() From order In
orders.AsEnumerable() Where (address.Field(Of Integer)("AddressID") =
order.Field(Of Integer)("BillToAddressID")) And address.Field(Of
String)("City") = "Seattle" Select New With _
{ .City = address.Field(Of String)("City"), _
.OrderID = order.Field(Of Integer)("SalesOrderID"), _
8.2.2.9.2.3. Ordenación
Los ejemplos de este tema muestran cómo se utilizan los métodos OrderBy, Reverse<(Of
<(TSource>)>) y ThenBy para consultar DataSet y ordenar los resultados utilizando la sintaxis de
consulta basada en métodos.
OrderBy
Ejemplo
Este ejemplo utiliza el método OrderBy con un comparador personalizado para realizar una ordenación
con distinción de mayúsculas y minúsculas de apellidos.
' Fill the DataSet.
Distinct
Ejemplo
Este ejemplo usa el método Distinct para quitar elementos duplicados de una secuencia.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim rows As List(Of DataRow) = New List(Of DataRow)
Dim contacts As DataTable = ds.Tables("Contact")
' Get 100 rows from the Contact table.
Dim query = (From c In contacts.AsEnumerable() Select c).Take(100)
Dim contactsTableWith100Rows = query.CopyToDataTable()
' Add 100 rows to the list.
For Each row In contactsTableWith100Rows.Rows
rows.Add(row)
Next
' Create duplicate rows by adding the same 100 rows to the list.
For Each row In contactsTableWith100Rows.Rows
rows.Add(row)
Next
Dim table = _
System.Data.DataTableExtensions.CopyToDataTable(Of DataRow)(rows)
' Find the unique contacts in the table.
Dim uniqueContacts = _
table.AsEnumerable().Distinct(DataRowComparer.Default)
Console.WriteLine("Unique contacts:")
For Each uniqueContact In uniqueContacts
Console.WriteLine(uniqueContact.Field(Of Integer)("ContactID"))
Next
Except
Ejemplo
Este ejemplo usa el método Except para devolver contactos que aparecen en la primera tabla pero no en
la segunda.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contactTable As DataTable = ds.Tables("Contact")
Dim query1 = From contact In contactTable.AsEnumerable() Where
contact.Field(Of String)("Title") = "Ms." Select contact
Dim query2 = From contact In contactTable.AsEnumerable() Where
contact.Field(Of String)("FirstName") = "Sandra" Select contact
Dim contacts1 = query1.CopyToDataTable()
ToArray
Ejemplo
En este ejemplo se utiliza el método ToArray<(Of <(TSource>)>) para evaluar de forma inmediata una
secuencia en una matriz.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim products As DataTable = ds.Tables("Product")
Dim productsArray = products.AsEnumerable().ToArray()
Dim query = From product In productsArray Select product Order By
product.Field(Of Decimal)("ListPrice") Descending
Console.WriteLine("Every price From highest to lowest:")
For Each product In query
Console.WriteLine(product.Field(Of Decimal)("ListPrice"))
Next
ToDictionary
Ejemplo
En este ejemplo se utiliza el método ToDictionary para evaluar de forma inmediata una secuencia y una
expresión de clave relacionada en un diccionario.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim products As DataTable = ds.Tables("Product")
Dim scoreRecordsDict = products.AsEnumerable(). _
ToDictionary(Function(record) record.Field(Of String)("Name"))
Console.WriteLine("Top Tube's ProductID: {0}", _
scoreRecordsDict("Top Tube")("ProductID"))
ToList
Ejemplo
En este ejemplo se utiliza el método ToList<(Of <(TSource>)>) para evaluar inmediatamente una
secuencia en List<(Of <(T>)>), donde T es de tipo DataRow.
' Fill the DataSet.
First
Ejemplo
Este ejemplo usa el método First para buscar la primera dirección de correo electrónico que empieza por
"caroline".
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contacts As DataTable = ds.Tables("Contact")
Dim startsWith As DataRow = contacts.AsEnumerable(). _
First(Function(contact) contact.Field(Of String) _
("EmailAddress").StartsWith("caroline"))
Console.WriteLine("An email address starting with 'caroline': {0}", _
startsWith.Field(Of String)("EmailAddress"))
Aggregate
Ejemplo
En este ejemplo se utiliza el método Aggregate para obtener los cinco primeros contactos de la tabla
Contact y generar una lista de apellidos delimitada por comas.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Ejemplo
En este ejemplo se utiliza el método Average para obtener el importe total a pagar promedio de cada id.
de contacto.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = From order In orders.AsEnumerable() Group order By contactID
= order.Field(Of Int32)("ContactID") Into g = Group Select New With _
{ .Category = contactID, _
.averageTotalDue = g.Average(Function(order) order. _
Field(Of Decimal)("TotalDue")) }
For Each order In query
Console.WriteLine("ContactID = {0} " & vbTab & _
" Average TotalDue = {1}", order.Category,order.averageTotalDue)
Next
Ejemplo
En este ejemplo se utiliza el método Average para obtener los pedidos con el TotalDue promedio de cada
contacto.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = From order In orders.AsEnumerable() Group order By contactID
= order.Field(Of Int32)("ContactID") Into g = Group Let averageTotalDue =
g.Average(Function(order) order.Field(Of Decimal)("TotalDue")) Select New
With { .Category = contactID, _
.CheapestProducts = g.Where(Function(order) order. _
Field(Of Decimal)("TotalDue") = averageTotalDue) }
For Each orderGroup In query
Console.WriteLine("ContactID: " & orderGroup.Category)
For Each order In orderGroup.CheapestProducts
Console.WriteLine("Average total due for SalesOrderID {1} is: {0}", _
order.Field(Of Decimal)("TotalDue"), _
order.Field(Of Int32)("SalesOrderID"))
Next
Console.WriteLine("")
Next
Count
Ejemplo
En este ejemplo se utiliza el método Count para devolver el número de productos de la tabla Product.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim products As DataTable = ds.Tables("Product")
Dim numProducts = products.AsEnumerable().Count()
Console.WriteLine("There are " & numProducts & " products.")
Ejemplo
En este ejemplo se utiliza el método Count para devolver una lista de id. de contactos y el número de
pedidos que tiene cada uno de ellos.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contacts As DataTable = ds.Tables("Contact")
Dim query = From contact In contacts.AsEnumerable() Select New With _
{ .ContactID = contact.Field(Of Integer)("ContactID"), _
.OrderCount = contact.GetChildRows("SalesOrderContact").Count()}
For Each contact In query
Console.Write("CustomerID = " & contact.ContactID)
Console.WriteLine(vbTab & "OrderCount = " & contact.OrderCount)
Next
Ejemplo
En este ejemplo se agrupan los productos por colores y se utiliza el método Count para devolver el
número de productos de cada grupo de color.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim products As DataTable = ds.Tables("Product")
Dim query = From product In products.AsEnumerable() Group product By
color = product.Field(Of String)("Color") Into g = Group Select New With
{.Color = color, .ProductCount = g.Count()}
For Each product In query
Console.WriteLine("Color = {0} " & vbTab & "ProductCount = {1}", _
product.Color, product.ProductCount)
Next
LongCount
Ejemplo
En este ejemplo se obtiene el recuento de contactos como un entero largo.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contacts As DataTable = ds.Tables("Contact")
Dim numberOfContacts = contacts.AsEnumerable().LongCount()
Console.WriteLine("There are {0} Contacts", numberOfContacts)
Max
Ejemplo
En este ejemplo se utiliza el método Max para obtener el mayor importe total a pagar.
Next
Min
Ejemplo
En este ejemplo se utiliza el método Min para obtener el menor importe total a pagar.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim smallestTotalDue As Decimal = orders.AsEnumerable(). _
Min(Function(totalDue) totalDue.Field(Of Decimal)("TotalDue"))
Console.WriteLine("The smallest TotalDue is {0}.", smallestTotalDue)
Ejemplo
En este ejemplo se utiliza el método Min para obtener el menor importe total a pagar de cada id. de
contacto.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = From order In orders.AsEnumerable() Group order By contactID
= order.Field(Of Int32)("ContactID") Into g = Group Select New With _
{ .Category = contactID, _
.smallestTotalDue = g.Min(Function(order) _
order.Field(Of Decimal)("TotalDue")) _
}
For Each order In query
Console.WriteLine("ContactID = {0} " & vbTab & _
"Minimum TotalDue = {1}", order.Category, order.smallestTotalDue)
Next
Ejemplo
En este ejemplo se utiliza el método Min para obtener el pedido con el menor importe total a pagar de
cada contacto.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = From order In orders.AsEnumerable() Group order By contactID
= order.Field(Of Int32)("ContactID") Into g = Group Let minTotalDue =
g.Min(Function(order) order.Field(Of Decimal)("TotalDue")) Select New
With { .Category = contactID, _
.smallestTotalDue = g.Where(Function(order) order. _
Field(Of Decimal)("TotalDue") = minTotalDue) }
For Each orderGroup In query
Console.WriteLine("ContactID: " & orderGroup.Category)
8.2.2.9.2.8. Combinación
La combinación es una operación importante de las consultas dirigidas a orígenes de datos que no tienen
relaciones navegables entre ellos, como las tablas de bases de datos relacionales. Una combinación de
dos orígenes de datos es la asociación de objetos en un origen de datos con objetos que comparten un
atributo común en el otro origen de datos.
Los ejemplos de este tema muestran cómo usar el método Join para consultar DataSet usando la sintaxis
de consulta basada en métodos.
Join
Ejemplo
En este ejemplo se realiza una combinación en las tablas Contact y SalesOrderHeader.
Next
Next
CopyToDataTable
Ejemplo
En este ejemplo se carga un DataTable con resultados de consulta utilizando el método
CopyToDataTable.
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)
Dim contactTable As DataTable = ds.Tables("Contact")
Dim query = From contact In contactTable.AsEnumerable() Where
contact.Field(Of String)("Title") = "Ms." And contact.Field(Of
String)("FirstName") = "Carla" Select contact
Dim contacts = query.CopyToDataTable().AsEnumerable()
For Each contact In contacts
Console.Write("ID: " & contact.Field(Of Integer)("ContactID"))
Console.WriteLine(" Name: " & contact.Field(Of String)("LastName") &
", " & contact.Field(Of String)("FirstName"))
Next
DataRowComparer
Ejemplo
En este ejemplo se comparan dos filas distintas de datos utilizando DataRowComparer.
// Fill the DataSet.
DataSet ds = new DataSet();
ds.Locale = CultureInfo.InvariantCulture;
FillDataSet(ds);
// Get two rows from the SalesOrderHeader table.
DataTable table = ds.Tables["SalesOrderHeader"];
DataRow left = (DataRow)table.Rows[0];
DataRow right = (DataRow)table.Rows[1];
// Compare the two different rows.
IEqualityComparer<DataRow> comparer = DataRowComparer.Default;
bool bEqual = comparer.Equals(left, right);
if (bEqual)
Console.WriteLine("The two rows are equal");
else
Console.WriteLine("The two rows are not equal");
// Get the hash codes of the two rows.
Console.WriteLine("The hashcodes for the two rows are {0}, {1}",
comparer.GetHashCode(left),comparer.GetHashCode(right));
Nota:
Los datos relacionales se muestran como una colección de tablas bidimensionales (relaciones o
archivos sin formato), donde las columnas comunes relacionan las tablas entre sí. Para utilizar LINQ
a SQL con eficacia, debe estar un poco familiarizado con los principios subyacentes de las bases de
datos relacionales.
En LINQ a SQL, el modelo de datos de una base de datos relacional se asigna a un modelo de objetos
expresado en el lenguaje de programación del programador. Cuando la aplicación se ejecuta, LINQ a SQL
convierte a SQL las consultas integradas en el lenguaje en el modelo de objetos y las envía a la base de
datos para su ejecución. Cuando la base de datos devuelve los resultados, LINQ a SQL los vuelve a
convertir en objetos con los que pueda trabajar en su propio lenguaje de programación.
Los programadores de Visual Studio normalmente utilizan el Diseñador relacional de objetos, que
proporciona una interfaz de usuario para implementar muchas de las características de LINQ a SQL.
Por ejemplo, en el código siguiente, se crea el objeto nw para representar la base de datos Northwind, el
destino es la tabla Customers, las filas se filtran para Customers de London y se selecciona para la
recuperación una cadena de CompanyName.
Selección
Para la selección (proyección), basta con que escriba una consulta LINQ en su propio lenguaje de
programación y, después, la ejecute para recuperar los resultados. LINQ a SQL convierte todas las
operaciones necesarias en las operaciones SQL con las que está familiarizado.
En el ejemplo siguiente se recuperan los nombres de las compañías de los clientes de Londres y se
muestran en la ventana de la consola.
' Northwnd inherits from System.Data.Linq.DataContext.
En el ejemplo siguiente se agrega un nuevo cliente, e información sobre el cliente, a la tabla Customers
utilizando el método InsertOnSubmit.
' Northwnd inherits from System.Data.Linq.DataContext.
Dim nw As New Northwnd("c:\northwnd.mdf")
Dim cust As New Customer With {.CompanyName="SomeCompany",.City="London",
.CustomerID = 98128,.PostalCode = 55555, .Phone = "555-555-5555"}
nw.Customers.InsertOnSubmit(cust)
' At this point, the new Customer object is added in the object model.
' In LINQ to SQL, the change is not sent to the database until
' SubmitChanges is called.
nw.SubmitChanges()
Actualización
Para realizar una operación Update en una entrada de base de datos, primero recupere el elemento y
modifíquelo directamente en el modelo de objetos. Después de haber modificado el objeto, llame a
SubmitChanges en DataContext para actualizar la base de datos.
En el ejemplo siguiente se recuperan todos los clientes que son de Londres. A continuación, el nombre de
la ciudad se cambia de "London" a "London - Metro". Finalmente, se llama a SubmitChanges para enviar
los cambios a la base de datos.
Dim nw As New Northwnd("c:\northwnd.mdf")
Dim cityNameQuery=From cust In nw.Customers Where
cust.City.Contains("London") Select cust
For Each customer In cityNameQuery
If customer.City = "London" Then
customer.City = "London - Metro"
End If
Next
nw.SubmitChanges()
Eliminar
Para realizar una operación Delete en un elemento, quite el elemento de la colección a la que pertenece
y, a continuación, llame al método SubmitChanges en DataContext para confirmar el cambio.
Nota:
Para agilizar el proceso, utilice el Diseñador relacional de objetos para crear su modelo de objetos y
poder empezar a codificar sus consultas.
Este diseñador proporciona una interfaz de usuario completa para crear un modelo de objetos a
partir de una base de datos existente. Esta herramienta forma parte del IDE de Visual Studio y
es perfecta para bases de datos pequeñas o medianas.
Editor de código
Puede escribir su propio código utilizando el editor de código de Visual Studio u otro editor. No
recomendamos este enfoque, que puede ser susceptible a errores, cuando se tiene una base de
datos existente y se puede utilizar el Diseñador relacional de objetos o la herramienta SQLMetal.
Sin embargo, el editor de código puede ser muy útil para perfeccionar o modificar el código ya
generado con otras herramientas.
Con este enfoque puede mantener los metadatos de la asignación fuera del código de aplicación.
Nota:
Un archivo DBML, que se puede modificar antes de generar el archivo de código definitivo.
Ahora que tiene un modelo de objetos, debe describir las solicitudes de información y manipular los
datos dentro de ese modelo. Debe pensar en términos de los objetos y las propiedades del modelo de
objetos, y no en términos de las filas y columnas de la base de datos. No tratará directamente con la
base de datos.
Al indicar a LINQ a SQL que ejecute una consulta que ha descrito o que llame a SubmitChanges() en los
datos que ha manipulado, LINQ a SQL se comunica con la base de datos en el lenguaje de la misma.
Si utiliza Visual Studio, puede usar el Diseñador relacional de objetos para crear el modelo de objetos.
Si programa en Visual Studio, puede utilizar el Diseñador relacional de objetos para generar un
modelo de objetos. El Diseñador relacional de objetos proporciona una interfaz de usuario
completa para ayudarle a generar un modelo de objetos LINQ a SQL.
Nota:
Si no tiene una base de datos existente y desear crear una a partir de un modelo de objetos, puede
crear el modelo de objetos mediante el editor de código y CreateDatabase.
La documentación del Diseñador relacional de objetos proporciona ejemplos de cómo generar un modelo
de objetos de Visual Basic o de C# mediante el Diseñador relacional de objetos. La información siguiente
proporciona ejemplos de cómo utilizar la herramienta de línea de comandos SQLMetal.
Ejemplo
La línea de comandos de SQLMetal mostrada en el ejemplo siguiente genera código de Visual Basic como
el modelo de objetos basado en atributos de la base de datos de ejemplo Northwind. Se representan
también los procedimientos almacenados y las funciones.
sqlmetal /code:northwind.vb /language:vb "c:\northwnd.mdf" /sprocs
/functions
La línea de comandos de SQLMetal mostrada en el ejemplo siguiente genera código de C# como el
modelo de objetos basado en atributos de la base de datos de ejemplo Northwind. Se representan
también los procedimientos almacenados y las funciones, y los nombres de tabla se pluralizan
automáticamente.
sqlmetal /code:northwind.cs /language:csharp "c:\northwnd.mdf" /sprocs
/functions /pluralize
Nota:
Ejemplo
El comando siguiente genera un archivo de asignación externo a partir de la base de datos de ejemplo
Northwind.
sqlmetal /server:myserver /database:northwind /map:externalfile.xml
2. Utilice un editor para modificar el archivo .dbml. Tenga en cuenta que el archivo .dbml debe
validarse correctamente con el archivo de definición de esquema (.xsd) de los archivos .dbml de
LINQ a SQL.
Ejemplo
El código siguiente genera un archivo .dbml a partir de la base de datos de ejemplo Northwind. Como
origen de los metadatos de la base de datos, puede utilizar el nombre de la base de datos o el nombre
del archivo .mdf.
sqlmetal /server:myserver /database:northwind /dbml:mymeta.dbml
sqlmetal /dbml:mymeta.dbml mydbfile.mdf
El código siguiente genera un archivo de código fuente de Visual Basic o C# a partir de un archivo .dbml.
sqlmetal /namespace:nwind /code:nwind.vb /language:vb DBMLFile.dbml
sqlmetal /namespace:nwind /code:nwind.cs /language:csharp DBMLFile.dbml
Nota:
Es posible que su equipo muestre nombres o ubicaciones diferentes para algunos de los elementos de
la interfaz de usuario de Visual Studio incluidos en las instrucciones siguientes. La edición de Visual
Studio que se tenga y la configuración que se utilice determinan estos elementos.
1. En el menú Archivo de Visual Studio, elija Abrir y, a continuación, haga clic en Archivo.
2. En el cuadro de diálogo Abrir archivo, haga clic en el archivo de asignación .dbml o XML que
desea validar.
3. Haga clic con el botón secundario del mouse en la ventana y, a continuación, haga clic en
Propiedades.
6. En la columna Uso de la fila de definición de esquema que desee, haga clic para abrir el cuadro
desplegable y, a continuación, haga clic en Usar este esquema.
El archivo de definición de esquema está asociado ahora con su archivo de asignación DBML o XML.
Determine si se han generado errores, advertencias o mensajes. Si no, el archivo XML es válido
respecto a la definición de esquema.
1. Busque el tema de Ayuda que contiene la definición de esquema tal como se ha descrito
anteriormente en este tema.
Nota importante:
Esta selección garantiza que el marcador de orden de bytes Unicode-16 (FFFE) se antepone al
archivo de texto.
Los programadores que utilizan Visual Studio pueden utilizar Diseñador relacional de objetos para este
propósito.
También puede utilizar el editor de código de Visual Studio para escribir su propio código de asignación o
para personalizar código que ya se ha generado.
Ejemplo
El código siguiente establece la clase Customer como una clase de entidad que está asociada a la tabla
de base de datos Customers.
<Table(Name:="Customers")> _
Public Class Customer
' ...
End Class
No tiene que especificar la propiedad Name si se puede deducir el nombre. Si no especifica ningún
nombre, se supone que es el mismo que el de la propiedad o campo.
Ejemplo
El código siguiente asigna el campo CustomerID de la clase Customer a la columna CustomerID de la
tabla de base de datos Customers.
<Table(Name:="Customers")> _
Public Class Customer
<Column(Name:="CustomerID")> _
Public CustomerID As String
' ...
End Class
No tiene que especificar la propiedad Name si se puede deducir el nombre. Si no especifica ningún
nombre, se supone que es el mismo que el de la propiedad o campo.
Nota:
Puede codificar como referencias de propiedad en la clase de entidad cualquier relación de datos que
vaya a ser siempre la misma. En la base de datos de ejemplo Northwind, por ejemplo, dado que los
clientes normalmente realizan pedidos, hay siempre una relación en el modelo entre los clientes y sus
pedidos.
LINQ a SQL define un atributo AssociationAttribute para ayudar a representar tales relaciones. Este
atributo se utiliza junto con los tipos EntitySet<(Of <(TEntity>)>) y EntityRef<(Of <(TEntity>)>) para
representar lo que sería una relación de clave externa en una base de datos.
La mayoría de las relaciones son de uno a varios, como en el ejemplo que se incluye más adelante en
este tema. También puede representar relaciones uno a uno y varios a varios de la manera siguiente:
Uno a uno: para representar este tipo de relación incluya EntitySet<(Of <(TEntity>)>) en
ambos lados.
Por ejemplo, considere una relación Customer-SecurityCode, creada de forma que el código de
seguridad del cliente no se encuentre en la tabla Customer y sólo esté accesible para las
personas autorizadas.
Varios a varios: en las relaciones varios a varios, la clave principal de la tabla de vínculos
(también conocida como tabla de unión ) suele estar formada por un conjunto de claves
externas de las otras dos tablas.
Por ejemplo, considere una relación varios a varios Employee-Project formada mediante la tabla
de vínculos EmployeeProject. LINQ a SQL requiere que tal relación se modele utilizando tres
clases: Employee, Project y EmployeeProject. En este caso, al cambiar la relación entre
Employee y Project puede parecer necesario actualizar la clave principal de EmployeeProject. Sin
embargo, esta situación se modela mejor eliminando un EmployeeProject existente y creando un
nuevo EmployeeProject.
Nota:
Las relaciones en las bases de datos relacionales se modelan normalmente como valores de
clave externa que hacen referencia a claves principales de otras tablas. Para navegar entre
ellas, las dos tablas se asocian explícitamente, utilizando una operación de combinación
relacional.
Por otra parte, las referencias mutuas entre los objetos de LINQ a SQL se realizan mediante
referencias de propiedad o colecciones de referencias por las que se navega mediante la
notación de punto.
Ejemplo
En el siguiente ejemplo de relación uno a varios, la clase Customer tiene una propiedad que declara la
relación entre los clientes y sus pedidos. La propiedad Orders es de tipo EntitySet<(Of <(TEntity>)>).
Este tipo indica que esta relación es de uno a varios (entre un cliente y varios pedidos). La propiedad
OtherKey se utiliza para describir cómo se logra esta asociación, a saber, especificando el nombre de la
propiedad de la clase relacionada que se va a comparar con ésta. En este ejemplo, se compara la
propiedad CustomerID, de igual forma que una combinación de base de datos compararía ese valor de
columna.
Nota:
Si utiliza Visual Studio, puede utilizar el Diseñador relacional de objetos para crear una asociación
entre clases.
<Table(Name:="Customers")> _
Public Class Customer
<Column(IsPrimaryKey:=True)> _
Public CustomerID As String
' ...
Private _Orders As EntitySet(Of Order)
<Association(Storage:="_Orders", OtherKey:="CustomerID")> _
Public Property Orders() As EntitySet(Of Order)
Get
Return Me._Orders
End Get
Set(ByVal value As EntitySet(Of Order))
Me._Orders.Assign(value)
End Set
End Property
End Class
También puede invertir la situación. En lugar de utilizar la clase Customer para describir la asociación
entre los clientes y los pedidos, puede utilizar la clase Order. La clase Order utiliza el tipo EntityRef<(Of
<(TEntity>)>) para describir paso a paso la relación hasta el cliente, como se observa en el ejemplo de
código siguiente.
Nota:
<Table(Name:="Orders")> _
Public Class Order
<Column(IsPrimaryKey:=True)> _
Public OrderID As Integer
<Column()> _
Public CustomerID As String
Private _Customer As EntityRef(Of Customer)
<Association(Storage:="Customer", ThisKey:="CustomerID")> _
Public Property Customer() As Customer
Get
Return Me._Customer.Entity
End Get
Set(ByVal value As Customer)
Me._Customer.Entity = value
End Set
End Property
End Class
Para designar que un campo o propiedad representa una columna generada por
base de datos
Debe especificar la propiedad DbType sólo si piensa utilizar CreateDatabase para crear una instancia de
la base de datos.
Para especificar texto para definir un tipo de datos en una tabla T-SQL
Nota:
Nota:
No se requieren atributos o propiedades especiales en las subclases. Observe sobre todo que las
subclases no tienen el atributo TableAttribute.
Esta propiedad contiene un valor que aparece en la tabla de base de datos, en la columna
IsDiscriminator, para indicar a qué clase o subclase pertenece esta fila de datos.
Esta propiedad contiene un valor que especifica a qué clase o subclases representa el valor de
clave.
Esta propiedad sirve para designar una asignación de reserva cuando el valor de discriminador de
la tabla de base de datos no coincide con ningún valor Code de las asignaciones de herencia.
Esta propiedad indica que ésta es la columna que contiene el valor Code.
Ejemplo
Nota:
Si utiliza Visual Studio, puede usar el Diseñador relacional de objetos para configurar la herencia.
En el ejemplo de código siguiente, Vehicle se define como la clase raíz y se han implementado los pasos
anteriores para describir la jerarquía para LINQ. En la ilustración siguiente se muestran las relaciones.
<Table()> _
<InheritanceMapping(Code:="C", Type:=GetType(Car))> _
<InheritanceMapping(Code:="T", Type:=GetType(Truck))> _
<InheritanceMapping(Code:="V", Type:=GetType(Vehicle),IsDefault:=True)> _
Public Class Vehicle
<Column(IsDiscriminator:=True)> _
Private DiscKey As String
<Column(IsPrimaryKey:=True)> _
Private VIN As String
<Column()> _
Private MfgPlant As String
End Class
Public Class Car
Inherits Vehicle
<Column()> _
Private TrimCode As Integer
<Column()> _
Ejemplo
El código siguiente especifica que el miembro HomePage nunca se debería comprobar durante las
búsquedas de actualizaciones.
<Column(Storage:="_HomePage", DbType:="NText",
UpdateCheck:=UpdateCheck.Never)> _
Public Property HomePage() As String
Get
Return Me._HomePage
End Get
Set(ByVal value As String)
If ((Me._HomePage <> value) = false) Then
Me.OnHomePageChanging(value)
Me.SendPropertyChanging
Me._HomePage = value
Me.SendPropertyChanged("HomePage")
Me.OnHomePageChanged
End If
End Set
End Property
El propósito de DataContext es convertir sus solicitudes de objetos en consultas SQL que se van a
ejecutar en la base de datos y, a continuación, ensamblar los objetos a partir de los resultados.
Nota de seguridad:
Ejemplo
En el ejemplo siguiente, se utiliza DataContext para establecer una conexión con la base de datos de
ejemplo Northwind y recuperar las filas de clientes cuya ciudad es Londres (London).
' DataContext takes a connection string.
Dim db As New DataContext("…\Northwnd.mdf")
' Get a typed table to run queries.
Dim Customers As Table(Of Customer) = db.GetTable(Of Customer)()
' Query for customer from London.
Dim Query = From cust In Customers Where cust.City = "London" Select cust
For Each cust In Query
Console.WriteLine("id=" & cust.CustomerID & ", City=" & cust.City)
Next
Cada tabla de base de datos se representa como una colección Table disponible a través del método
GetTable, utilizando la clase de entidad para su identificación.
Ejemplo
El ejemplo siguiente hace que SQL Server aumente el precio unitario (UnitPrice) en 1,00.
db.ExecuteCommand("UPDATE Products SET UnitPrice = UnitPrice + 1.00")
Ejemplo
En el ejemplo siguiente se muestra cómo reutilizar la misma conexión entre un comando ADO.NET y
DataContext.
Dim conString = "Data
Source=.\SQLEXPRESS;AttachDbFilename=c:\northwind.mdf; Integrated
Security=True;Connect Timeout=30;User Instance=True"
Dim northwindCon = New SqlConnection(conString)
northwindCon.Open()
Dim db = New Northwind("...")
Dim northwindTransaction = northwindCon.BeginTransaction()
Try
Dim cmd = New SqlCommand("UPDATE Products SET QuantityPerUnit =
'single item' " & "WHERE ProductID = 3")
cmd.Connection = northwindCon
cmd.Transaction = northwindTransaction
cmd.ExecuteNonQuery()
db.Transaction = northwindTransaction
Dim prod1 = (From prod In db.Products Where prod.ProductID = 4).First
Dim prod2 = (From prod In db.Products Where prod.ProductID = 5).First
prod1.UnitsInStock -= 3
prod2.UnitsInStock -= 5
db.SubmitChanges()
northwindTransaction.Commit()
Catch e As Exception
Console.WriteLine(e.Message)
Console.WriteLine("Error submitting changes all changes rolled back")
End Try
northwindCon.Close()
LINQ a SQL convierte las consultas que se escriben en consultas SQL equivalentes y las envía al servidor
para su procesamiento.
Algunas características de las consultas LINQ podrían requerir una atención especial en las aplicaciones
LINQ a SQL.
Ejemplo
La consulta siguiente solicita una lista de clientes de Londres. En este ejemplo, Customers es una tabla
de la base de datos de ejemplo Northwind.
Dim db As New Northwnd("c:\northwnd.mdf")
' Query for customers in London.
Dim custQuery=From cus In db.Customers Where cus.City="London" Select cus
Nota:
Ejemplo
El código siguiente recupera una colección de sólo lectura de fechas de contratación de empleados.
Dim db As New Northwnd("c:\northwnd.mdf")
db.ObjectTrackingEnabled = False
Dim hireQuery = From emp In db.Employees Select emp Order By emp.HireDate
For Each empObj As Employee In hireQuery
Console.WriteLine("EmpID = {0}, Date Hired = {1}", _
empObj.EmployeeID, empObj.HireDate)
Next
Nota:
Puede recuperar datos relacionados con el destino principal de la consulta recuperando un producto
cruzado como una gran proyección; por ejemplo, puede recuperar los pedidos cuando el destino de la
consulta son los clientes. Sin embargo, este enfoque a menudo tiene desventajas. Por ejemplo, los
resultados son simples proyecciones, no entidades que se puedan cambiar y conservar mediante
LINQ a SQL. Además, podría recuperar una gran cantidad de datos que no necesita.
Ejemplo
En el siguiente ejemplo, se recuperan todos los Orders de todos los Customers de Londres cuando se
ejecuta la consulta. En consecuencia, el acceso posterior a la propiedad Orders de un objeto Customer no
desencadena una nueva consulta de base de datos.
Dim db As New Northwnd("c:\northwnd.mdf")
Dim dlo As DataLoadOptions = New DataLoadOptions()
dlo.LoadWith(Of Customer)(Function(c As Customer) c.Orders)
db.LoadOptions = dlo
Dim londonCustomers = From cust In db.Customers Where cust.City =
"London" Select cust
For Each custObj In londonCustomers
Console.WriteLine(custObj.CustomerID)
Next
Ejemplo
En el ejemplo siguiente, el método AssociateWith limita los Orders recuperados a los que no se han
enviado hoy. Sin este enfoque, se habrían recuperado todos los Orders aunque sólo se requiere un
subconjunto.
Dim db As New Northwnd("c:\northwnd.mdf")
Dim dlo As DataLoadOptions = New DataLoadOptions()
dlo.AssociateWith(Of Customer)(Function(c As Customer) _
c.Orders.Where(Function(p) p.ShippedDate <> DateTime.Today))
db.LoadOptions = dlo
Dim custOrderQuery = From cust In db.Customers Where cust.City = "London"
Select cust
For Each custObj In custOrderQuery
Console.WriteLine(custObj.CustomerID)
For Each ord In custObj.Orders
Console.WriteLine("{0}{1}", vbTab, ord.OrderDate)
Next
Next
Nota:
Ejemplo
En el ejemplo siguiente se muestra cómo desactivar la carga diferida al establecer
DeferredLoadingEnabled en false.
Dim db As New Northwnd("c:\northwnd.mdf")
db.DeferredLoadingEnabled = False
Dim ds As New DataLoadOptions()
ds.LoadWith(Function(c As Customer) c.Orders)
SQL no puede ejecutar todos los métodos que podrían estar localmente disponibles para una aplicación.
LINQ a SQL intenta convertir estos métodos locales en operaciones y funciones equivalentes que estén
disponibles en el entorno de SQL. La mayoría de los métodos y operadores de los tipos integrados de
.NET Framework tienen comandos SQL que son equivalentes directos. Algunos se pueden generar a
partir de las funciones que están disponibles. Aquéllos que no se pueden generar, inician excepciones en
tiempo de ejecución.
Cuando una consulta LINQ a SQL no es suficiente para una tarea especializada, puede utilizar el método
ExecuteQuery para ejecutar una consulta SQL y después convertir el resultado de la consulta
directamente en objetos.
Ejemplo
En el ejemplo siguiente, supongamos que los datos de la clase Customer ocupan dos tablas (customer1 y
customer2). La consulta devuelve una secuencia de objetos Customer.
Dim db As New Northwnd("c:\northwnd.mdf")
Dim results As IEnumerable(Of Customer) = db.ExecuteQuery(Of Customer) _
("SELECT c1.custID as CustomerID,c2.custName as ContactName" & _
"FROM customer1 AS c1, customer2 as c2 WHERE c1.custid = c2.custid")
Siempre y cuando los nombres de columna de los resultados tabulares coincidan con las propiedades de
columna de la clase de entidad, LINQ a SQL crea objetos a partir de cualquier consulta SQL.
El método ExecuteQuery también permite el uso de parámetros. Utilice código como el siguiente para
ejecutar una consulta parametrizada.
Dim db As New Northwnd("c:\northwnd.mdf")
Dim results As IEnumerable(Of Customer) = db.ExecuteQuery(Of
Customer)("SELECT contactname FROM customers WHERE city = {0}, 'London'")
Los parámetros se expresan en el texto de la consulta con la misma notación con llaves que
Console.WriteLine() y String.Format(). De hecho, se llama a String.Format() en la cadena de consulta
proporcionada, sustituyendo los parámetros entre llaves por nombres de parámetro generados, como
@p0, @p1 …, @p(n).
Nota:
Este modelo de utilización representa el uso más común de las consultas compiladas, pero también
son posibles otros enfoques. Por ejemplo, las consultas compiladas se pueden almacenar como
miembros estáticos en una clase parcial que extiende el código generado por el diseñador.
Ejemplo
En muchos escenarios podrían reutilizarse las consultas entre límites de subprocesos. En tales casos,
almacenar las consultas compiladas en variables estáticas es particularmente eficaz. En el ejemplo de
código siguiente se da por supuesto que hay una clase Queries diseñada para almacenar las consultas
compiladas y que hay una clase Northwind que representa un objeto DataContext con establecimiento
inflexible de tipos.
Class Queries
Public Shared CustomersByCity As Func(Of Northwnd, String,
IQueryable(Of Customer)) = CompiledQuery.Compile(Function(db As
Northwnd, city As String) From c In db.Customers Where c.City = city
Select c)
Public Shared CustomersById As Func(Of Northwnd,String,
IQueryable(Of Customer))=CompiledQuery.Compile(Function(db As
Northwnd,id As String) db.Customers.Where(Function(c) c.CustomerID=id))
End Class
' The following example invokes such a compiled query in the main program
Public Function GetCustomersByCity(ByVal city As String) As _
IEnumerable(Of Customer)
Dim myDb = GetNorthwind()
Return Queries.CustomersByCity(myDb, city)
End Function
En estos momentos, no se pueden almacenar (en variables estáticas) las consultas que devuelven un
tipo anónimo porque el tipo no tiene un nombre que se pueda proporcionar como argumento genérico.
En el ejemplo siguiente se muestra cómo se puede solucionar el problema creando un tipo que pueda
representar el resultado y, a continuación, cómo se utiliza como argumento genérico.
Class SimpleCustomer
Private _ContactName As String
Public Property ContactName() As String
Get
Return _ContactName
End Get
Set(ByVal value As String)
_ContactName = value
End Set
End Property
End Class
Class Queries2
Ejemplo
En el ejemplo siguiente se muestra una consulta que invoca al operador GroupBy, que sólo acepta un
argumento key.
Dim query = From cust In db.Customers Group cust.ContactName By Key = New
With {cust.City, cust.Region} Into Group
For Each grp In query
Console.WriteLine("Location Key: {0}", grp.Key)
For Each listing In grp.Group
Console.WriteLine(vbTab & "0}", listing)
Next
Next
La misma situación se da con las combinaciones, como en el ejemplo siguiente:
Dim query = From ord In db.Orders, prod In db.Products Join det In
db.OrderDetails On New With {ord.OrderID, prod.ProductID} Equals New With
{det.OrderID,det.ProductID} Select ord.OrderID,prod.ProductID,
det.UnitPrice
Ejemplo
El código siguiente utiliza el método LoadWith para recuperar los objetos Customer y Order.
Dim db As New Northwnd("c:\northwnd.mdf")
Dim ds As DataLoadOptions = New DataLoadOptions()
ds.LoadWith(Of Customer)(Function(c) c.Orders)
ds.LoadWith(Of Order)(Function(o) o.OrderDetails)
db.LoadOptions = ds
Dim custQuery = From cust In db.Customers() Where cust.City = "London"
Select cust
For Each custObj In custQuery
Console.WriteLine("Customer ID: " & custObj.CustomerID)
For Each ord In custObj.Orders
Console.WriteLine(vbTab & "Order ID: " & ord.OrderID)
For Each detail In ord.OrderDetails
Console.WriteLine(vbTab & vbTab & _
Ejemplo
En el ejemplo siguiente, se usa DataLoadOptions..::.AssociateWith(LambdaExpression) para filtrar los
pedidos de clientes previamente cargados por ShippedDate.
Dim db As New Northwnd("c:\northwnd.mdf")
' Preload Orders for Customer.
' One directive per relationship to be preloaded.
Dim ds As DataLoadOptions = New DataLoadOptions()
ds.LoadWith(Of Customer)(Function(cust) cust.Orders)
ds.AssociateWith(Of Customer)(Function(cust) From ord In cust.Orders
Where ord.ShippedDate <> DateTime.Today)
db.LoadOptions = ds
Dim custQuery = From cust In db.Customers Where cust.City = "London"
Select cust
For Each custObj In custQuery
Console.WriteLine("Customer ID: " & custObj.CustomerID)
For Each ord In custObj.Orders
Console.WriteLine(vbTab & "Order ID: " & ord.OrderID)
For Each detail In ord.OrderDetails
Console.WriteLine(vbTab & vbTab & _
"Product ID: " & detail.ProductID)
Next
Next
Next
Nota importante:
Los ejemplos de los temas siguientes se derivan de la base de datos de ejemplo Northwind.
Nota:
La conversión de Average de valores enteros en LINQ a SQL se calcula como un entero, no como
double.
Ejemplo
En el ejemplo siguiente se devuelve el promedio de los valores Freight de la tabla Orders.
Observe que este ejemplo requiere el uso de la palabra clave var en C#, porque el tipo de valor devuelto
es anónimo.
Dim priceQuery = From prod In db.Products() Group prod By prod.CategoryID
Into grouping = Group Select CategoryID, ExpensiveProducts = (From prod2
In grouping Where prod2.UnitPrice > grouping.Average(Function(prod3)
prod3.UnitPrice) Select prod2)
For Each grp In priceQuery
Console.WriteLine(grp.CategoryID)
For Each listing In grp.ExpensiveProducts
Console.WriteLine(listing.ProductName)
Next
Next
Al ejecutar esta consulta en la base de datos de ejemplo Northwind, se genera un resultado de 91.
Ejemplo
En el ejemplo siguiente se cuenta el número de Customers en la base de datos.
Dim customerCount = db.Customers.Count()
Console.WriteLine(customerCount)
En el ejemplo siguiente se cuenta el número de productos de la base de datos que no han dejado de
fabricarse.
Al ejecutar este ejemplo en la base de datos de ejemplo Northwind, se genera un resultado de 69.
Dim notDiscontinuedCount = Aggregate prod In db.Products Into Count(Not
prod.Discontinued)
Console.WriteLine(notDiscontinuedCount)
Ejemplo
En el ejemplo siguiente se busca la fecha de la última contratación de cualquier empleado.
Si ejecuta esta consulta en la base de datos de ejemplo Northwind, el resultado es: 11/15/1994
12:00:00 AM.
Dim latestHireDate = Aggregate emp In db.Employees Into Max(emp.HireDate)
Console.WriteLine(latestHireDate)
En el ejemplo siguiente se busca el número máximo de unidades en existencias para cualquier producto.
Si ejecuta este ejemplo en la base de datos de ejemplo Northwind, el resultado es: 125.
Dim maxUnitsInStock=Aggregate prod In db.Products Into Max(prod.UnitsInS)
Console.WriteLine(maxUnitsInStock)
En el ejemplo siguiente se utiliza Max para encontrar los Products que tienen el precio unitario más alto
en cada categoría. En este caso, los resultados se muestran por categoría.
Dim maxQuery = From prod In db.Products() Group prod By prod.CategoryID
Into grouping = Group Select CategoryID, MostExpensiveProducts = (From
prod2 In grouping Where prod2.UnitPrice = grouping.Max(Function(prod3)
prod3.UnitPrice))
For Each grp In maxQuery
Console.WriteLine(grp.CategoryID)
For Each listing In grp.MostExpensiveProducts
Console.WriteLine(listing.ProductName)
Next
Next
Ejemplo
En el ejemplo siguiente se busca el precio unitario más bajo de cualquier producto.
Si ejecuta esta consulta en la base de datos de ejemplo Northwind, el resultado es: 2.5000.
Dim lowestUnitPrice=Aggregate prod In db.Products Into Min(prod.UnitPric)
Console.WriteLine(lowestUnitPrice)
En el ejemplo siguiente se busca el importe con flete más bajo de cualquier pedido.
Si ejecuta esta consulta en la base de datos de ejemplo Northwind, el resultado es: 0.0200.
Dim lowestFreight = Aggregate ord In db.Orders Into Min(ord.Freight)
Console.WriteLine(lowestFreight)
En el ejemplo siguiente se utiliza Min para encontrar los Products que tienen el precio unitario más bajo
en cada categoría. El resultado se organiza por categoría.
Dim minQuery = From prod In db.Products() Group prod By prod.CategoryID
Into grouping = Group Select CategoryID, LeastExpensiveProducts = From
prod2 In grouping Where prod2.UnitPrice = grouping.Min(Function(prod3)
prod3.UnitPrice)
For Each grp In minQuery
Console.WriteLine(grp.CategoryID)
For Each listing In grp.LeastExpensiveProducts
Console.WriteLine(listing.ProductName)
Next
Next
Tenga en cuenta las características siguientes del operador Sum en LINQ a SQL:
El operador de consulta estándar de agregado Sum se evalúa como cero para una secuencia
vacía o una secuencia que sólo contiene valores nulos. En LINQ a SQL, la semántica de SQL se
mantiene invariable. Por esta razón, Sum se evalúa como Null en lugar de cero en el caso de
secuencias vacías o secuencias que sólo contienen valores nulos.
En LINQ a SQL se aplican las restricciones de SQL para los agregados en los resultados
intermedios. La suma de cantidades enteras de 32 bits no se calcula utilizando resultados de 64
bits, y puede producirse un desbordamiento en la conversión que LINQ a SQL realiza de Sum.
Esta posibilidad existe aun cuando la implementación del operador de consulta estándar no
produzca un desbordamiento para la secuencia en memoria correspondiente.
Ejemplo
En el ejemplo siguiente se busca el flete total de todos los pedidos de la tabla Order.
Si ejecuta esta consulta en la base de datos de ejemplo Northwind, el resultado es: 64942.6900.
Dim totalFreight = Aggregate ord In db.Orders Into Sum(ord.Freight)
Console.WriteLine(totalFreight)
En el ejemplo siguiente se busca el número total de unidades pedidas para todos los productos.
Si ejecuta esta consulta en la base de datos de ejemplo Northwind, el resultado es: 780.
Tenga en cuenta que debe convertir los tipos short (por ejemplo, UnitsOnOrder) porque Sum no tiene
una sobrecarga para ellos.
Dim totalUnitsOnOrder = Aggregate prod In db.Products Into
Sum(prod.UnitsOnOrder)
Console.WriteLine(totalUnitsOnOrder)
Nota:
Ejemplo
El código siguiente busca el primer Shipper de una tabla:
Dim shipper As Shipper = db.Shippers.First()
Console.WriteLine("ID = {0}, Company = {1}", shipper.ShipperID, _
shipper.CompanyName)
El código siguiente busca el Customer único cuyo CustomerID es BONAP.
Dim custquery As Customer = (From c In db.Customers Where c.CustomerID =
"BONAP" Select c) .First()
Console.WriteLine("ID = {0}, Contact = {1}", custquery.CustomerID, _
custquery.ContactName)
Utilice el operador Skip<(Of <(TSource>)>) para omitir un número determinado de elementos de una
secuencia y devolver el resto.
Nota:
LINQ a SQL convierte Skip<(Of <(TSource>)>) utilizando una subconsulta con la cláusula NOT EXISTS
de SQL. Esta conversión tiene las limitaciones siguientes:
El argumento debe ser un conjunto. No se admiten los conjuntos múltiples, aunque estén
ordenados.
La consulta generada puede ser mucho más compleja que la consulta generada para la consulta
base en la que se aplica Skip<(Of <(TSource>)>). Esta complejidad puede mermar el
rendimiento o incluso hacer que se agote el tiempo de espera.
Ejemplo
En el ejemplo siguiente se utiliza Take para seleccionar los cinco primeros Employees contratados.
Observe que la colección primero se ordena por HireDate.
Dim firstHiredQuery = From emp In db.Employees Select emp Order By
emp.HireDate Take 5
For Each empObj As Employee In firstHiredQuery
Console.WriteLine("{0}, {1}", empObj.EmployeeID, empObj.HireDate)
Next
En el ejemplo siguiente se utiliza Skip<(Of <(TSource>)>) para seleccionar todos los Products excepto
los 10 más caros.
Dim lessExpensiveQuery = From prod In db.Products Select prod Order By
prod.UnitPrice Descending Skip 10
For Each prodObj As Product In lessExpensiveQuery
Console.WriteLine(prodObj.ProductName)
Next
En el ejemplo siguiente se combinan los métodos Skip<(Of <(TSource>)>) y Take<(Of <(TSource>)>)
para omitir los 50 primeros registros y después devolver los 10 siguientes.
Dim custQuery2 = From cust In db.Customers Order By (cust.ContactName)
Select cust Skip 50 Take 10
For Each custRecord As Customer In custQuery2
Console.WriteLine(custRecord.ContactName)
Next
Las operaciones Take<(Of <(TSource>)>) y Skip<(Of <(TSource>)>) sólo están perfectamente
definidas para los conjuntos ordenados. La semántica para los conjuntos no ordenados o conjuntos
múltiples no está definida.
Debido a las limitaciones de la ordenación en SQL, LINQ a SQL intenta trasladar la ordenación del
argumento del operador Take<(Of <(TSource>)>) o Skip<(Of <(TSource>)>) al resultado del operador.
Nota:
La conversión es diferente para SQL Server 2000 y SQL Server 2005. Si piensa utilizar Skip<(Of
<(TSource>)>) con una consulta de cualquier complejidad, utilice SQL Server 2005.
) AS [t2]
WHERE [t0].[CustomerID] = [t2].[CustomerID]
))) AND ([t0].[City] = @p1)
ORDER BY [t0].[CustomerID]
Cuando se encadenan Take<(Of <(TSource>)>) y Skip<(Of <(TSource>)>), todos los criterios de
ordenación especificados deben ser coherentes. De lo contrario, los resultados no están definidos.
Para los argumentos integrales constantes no negativos basados en la especificación de SQL, tanto
Take<(Of <(TSource>)>) como Skip<(Of <(TSource>)>) están perfectamente definidos.
Nota:
LINQ a SQL está diseñado para permitir la ordenación según tipos primitivos simples, como string,
int, etc. No admite la ordenación de clases complejas con valores múltiples, como los tipos
anónimos. Tampoco admite los tipos de datos byte.
Ejemplo
En el ejemplo siguiente se ordena Employees por fecha de contratación.
Dim hireQuery = From emp In db.Employees Select emp Order By emp.HireDate
For Each empObj As Employee In hireQuery
Console.WriteLine("EmpID = {0}, Date Hired = {1}", _
empObj.EmployeeID, empObj.HireDate)
Next
En el ejemplo siguiente se utiliza where para ordenar los Orders distribuidos a London por flete.
Dim freightQuery = From ord In db.Orders Where ord.ShipCity = "London"
Select ord Order By ord.Freight
For Each ordObj In freightQuery
Console.WriteLine("Order ID = {0}, Freight = {1}", _
ordObj.OrderID, ordObj.Freight)
Next
En el ejemplo siguiente se ordenan los Products por precio unitario, de mayor a menor.
Dim priceQuery = From prod In db.Products Select prod Order By
prod.UnitPrice Descending
For Each prodObj In priceQuery
Console.WriteLine("Product ID = {0}, Unit Price = {1}", _
prodObj.ProductID, prodObj.UnitPrice)
Next
En el ejemplo siguiente se utiliza una cláusula OrderBy compuesta para ordenar los Customers por
ciudad y, después, por nombre de contacto.
Dim custQuery = From cust In db.Customers Select cust Order By cust.City,
cust.ContactName
For Each custObj In custQuery
Console.WriteLine("City = {0}, Name = {1}", custObj.City, _
custObj.ContactName)
Next
En el ejemplo siguiente se ordenan los pedidos de EmployeeID 1 por país de destino y, después, por
flete, de mayor a menor.
Nota:
Los valores de columna nulos de las consultas GroupBy a veces pueden iniciar InvalidOperationException.
Ejemplo
En el ejemplo siguiente se crea una partición de Products por CategoryID.
Dim prodQuery = From prod In db.Products Group prod By prod.CategoryID
Into grouping = Group
For Each grp In prodQuery
Console.WriteLine(vbNewLine & "CategoryID Key={0}:", grp.CategoryID)
For Each listing In grp.grouping
Console.WriteLine(vbTab & listing.ProductName)
Next
Next
En el ejemplo siguiente se utiliza Max para buscar el precio unitario máximo para cada CategoryID.
Dim query = From p In db.Products Group p By p.CategoryID Into g = Group
Select CategoryID, MaxPrice = g.Max(Function(p) p.UnitPrice)
En el ejemplo siguiente se utiliza Average para buscar el UnitPrice promedio para cada CategoryID.
Dim q2 = From p In db.Products Group p By p.CategoryID Into g = Group
Select CategoryID, AveragePrice = g.Average(Function(p) p.UnitPrice)
En el ejemplo siguiente se usa Sum para buscar el UnitPrice total para cada CategoryID.
Dim priceQuery = From prod In db.Products Group prod By prod.CategoryID
Into grouping = Group Select CategoryID, TotalPrice =
grouping.Sum(Function(p) p.UnitPrice)
For Each grp In priceQuery
Console.WriteLine("Category = {0}, Total price = {1}", _
grp.CategoryID, grp.TotalPrice)
Next
En el ejemplo siguiente se utiliza Count para buscar el número de Products que ya no se fabrican en cada
CategoryID.
Dim disconQuery = From prod In db.Products Group prod By prod.CategoryID
Into grouping = Group Select CategoryID, NumProducts =
grouping.Count(Function(p) p.Discontinued)
For Each prodObj In disconQuery
Console.WriteLine("CategoryID = {0}, Discontinued# = {1}", _
prodObj.CategoryID, prodObj.NumProducts)
Next
En el ejemplo siguiente se utiliza una cláusula where posterior para buscar todas las categorías que
tienen al menos 10 productos.
Dim prodCountQuery = From prod In db.Products Group prod By
prod.CategoryID Into grouping = Group Where grouping.Count >= 10 Select
CategoryID, ProductCount = grouping.Count
For Each prodCount In prodCountQuery
Console.WriteLine("CategoryID = {0}, Product count = {1}", _
prodCount.CategoryID, prodCount.ProductCount)
Next
En el ejemplo siguiente se agrupan los productos por CategoryID y SupplierID.
Dim prodQuery = From prod In db.Products Group prod By Key = New With
{prod.CategoryID, prod.SupplierID} Into grouping = Group
For Each grp In prodQuery
Console.WriteLine(vbNewLine & "CategoryID {0}, SupplierID {1}", _
grp.Key.CategoryID, grp.Key.SupplierID)
For Each listing In grp.grouping
Console.WriteLine(vbTab & listing.ProductName)
Next
Next
En el ejemplo siguiente se devuelven dos secuencias de productos. La primera secuencia contiene los
productos con precio unitario menor o igual que 10. La segunda secuencia contiene los productos con
precio unitario mayor que 10.
Dim priceQuery = From prod In db.Products Group prod By Key = New With
{.Criterion = prod.UnitPrice > 10} Into grouping = Group Select Key,
grouping
For Each prodObj In priceQuery
If prodObj.Key.Criterion = False Then
Console.WriteLine("Prices 10 or less:")
Else
Console.WriteLine("\nPrices greater than 10")
For Each listing In prodObj.grouping
Console.WriteLine("{0}, {1}", listing.ProductName, _
listing.UnitPrice)
Next
End If
Next
El operador GroupBy sólo acepta un argumento de clave única. Si necesita agrupar los elementos según
más de una clave, debe crear un tipo anónimo, como en el ejemplo siguiente:
Ejemplo
En el ejemplo siguiente se utiliza Distinct para seleccionar una secuencia de las ciudades que tienen
clientes.
Dim cityQuery = (From cust In db.Customers Select cust.City).Distinct()
For Each cityString In cityQuery
Console.WriteLine(cityString)
Next
El operador Any devuelve true si cualquier elemento de una secuencia satisface una condición.
Ejemplo
En el ejemplo siguiente se devuelve una secuencia de clientes que tienen por lo menos un pedido. La
cláusula Where/where se evalúa como true si el Customer dado tiene cualquier Order.
Dim OrdersQuery = From cust In db.Customers Where cust.Orders.Any()
Select cust
El código de Visual Basic siguiente determina la lista de clientes que no han realizado pedidos y garantiza
que, para cada cliente de esa lista, se proporciona un nombre de contacto.
Public Sub ContactsAvailable()
Dim db As New Northwnd("c:\northwnd.mdf")
Dim result = (From cust In db.Customers Where Not cust.Orders.Any() _
Select cust).All(AddressOf ContactAvailable)
If result Then Console.WriteLine _
("All of the customers who have made no orders have a contact name")
Else
Console.WriteLine _
("Some customers who have made no orders have no contact name")
End If
End Sub
Function ContactAvailable(ByVal contact As Object) As Boolean
El operador Concat<(Of <(TSource>)>) se define para conjuntos múltiples ordenados en los que el
orden del receptor y del argumento son el mismo.
En SQL, la ordenación es el último paso antes de que se generen los resultados. Por esta razón, el
operador Concat<(Of <(TSource>)>) se implementa utilizando UNION ALL y no conserva el orden de
sus argumentos. Para garantizar que el orden de los resultados es correcto, no olvide ordenar los
resultados explícitamente.
Ejemplo
En este ejemplo se utiliza Concat<(Of <(TSource>)>) para devolver una secuencia de todos los números
de teléfono y fax de Customer y Employee.
Dim custQuery = (From c In db.Customers Select c.Phone) .Concat (From c
In db.Customers Select c.Fax) .Concat (From e In db.Employees Select
e.HomePhone)
For Each custData In custQuery
Console.WriteLine(custData)
Next
En este ejemplo se utiliza Concat<(Of <(TSource>)>) para devolver una secuencia de todas las
asignaciones de números de teléfono y nombres de Customer y Employee.
Dim infoQuery = (From cust In db.Customers Select Name =
cust.CompanyName, Phone = cust.Phone) .Concat (From emp In db.Employees
Select Name = emp.FirstName & " " & emp.LastName, Phone = emp.HomePhone)
For Each infoData In infoQuery
Console.WriteLine("Name=" & infoData.Name & ",Phone=" & infoData.Phone)
Next
Ejemplo
En este ejemplo se utiliza Except para devolver una secuencia de todos los países en los que viven
Customers pero no Employees.
Dim infoQuery = (From cust In db.Customers Select cust.Country) .Except
From emp In db.Employees Select emp.Country)
En LINQ a SQL, la operación Except se define correctamente sólo en conjuntos. La semántica de
conjuntos múltiples no está definida.
Ejemplo
En este ejemplo se utiliza Intersect para devolver una secuencia de todos los países en los que viven
Customers y Employees.
Dim infoQuery = (From cust In db.Customers Select cust.Country) Intersect
(From emp In db.Employees Select emp.Country)
En LINQ a SQL, la operación Intersect se define correctamente sólo en conjuntos. La semántica de
conjuntos múltiples no está definida.
Ejemplo
En este ejemplo se utiliza Union para devolver una secuencia de todos los países en los que hay
Customers o Employees.
Dim infoQuery = (From cust In db.Customers Select cust.Country) .Union
(From emp In db.Employees Select emp.Country)
En LINQ a SQL, el operador Union se define para conjuntos múltiples como la concatenación no ordenada
de conjuntos múltiples (de hecho, el resultado de la cláusula UNION ALL en SQL).
Ejemplo
En el ejemplo siguiente se utiliza ToArray<(Of <(TSource>)>) para evaluar inmediatamente una
consulta como una matriz y obtener el tercer elemento.
Ejemplo
En el ejemplo siguiente se utiliza ToList<(Of <(TSource>)>) para evaluar una consulta inmediatamente
como una List<(Of <(T>)>) genérica.
Dim empQuery = From emp In db.Employees Where emp.HireDate.Value >=
#1/1/1994# Select emp
Dim qList As List(Of Employee) = empQuery.ToList()
Ejemplo
En este ejemplo, LINQ a SQL (mediante la Query genérica predeterminada) intentaría convertir la
consulta en SQL y ejecutarla en el servidor. Sin embargo, la cláusula where hace referencia a un
método de cliente definido por el usuario (isValidProduct), que no se puede convertir a SQL.
Ejemplo
En el ejemplo siguiente se utiliza la navegación de clave externa en la cláusula From de Visual Basic
(cláusula from en C#) para seleccionar todos los pedidos de los clientes de Londres.
En el ejemplo siguiente se proyecta una expresión let que es el resultado de una combinación.
Dim q = From c In db.Customers Group Join o In db.Orders On c.CustomerID
Equals o.CustomerID Into ords = Group Let z = c.City + c.Country From o
In ords Select c.ContactName, o.OrderID, z
En el ejemplo siguiente se muestra una join con una clave compuesta.
Dim q = From o In db.Orders From p In db.Products Group Join d In
db.OrderDetails On New With {o.OrderID, p.ProductID} Equals New With
{d.OrderID, d.ProductID} Into details = Group From d In details Select
o.OrderID, p.ProductID, d.UnitPrice
En el ejemplo siguiente se muestra cómo construir una join de tal forma que una parte acepte valores
NULL y la otra no.
Dim q=From o In db.Orders Group Join e In db.Employees On o.EmployeeID
Equals e.EmployeeID Into emps=Group From e In emps Select o.OrderID,
e.FirstName
Ejemplo
En el ejemplo siguiente se utiliza la cláusula Select de Visual Basic (cláusula select en C#) para
devolver una secuencia de nombres de contacto de Customers.
Dim nameQuery = From cust In db.Customers Select cust.ContactName
En el ejemplo siguiente se utilizan la cláusula Select de Visual Basic (cláusula select en C#) y tipos
anónimos para devolver una secuencia de nombres de contacto y números de teléfono de Customers.
Dim infoQuery = From cust In db.Customers Select cust.ContactName,
cust.Phone
En el ejemplo siguiente se utilizan la cláusula Select de Visual Basic (cláusula select en C#) y tipos
anónimos para devolver una secuencia de nombres de contacto y números de teléfono de empleados.
Los campos FirstName y LastName se combinan en un campo único (Name) y se cambia el nombre del
campo HomePhone a Phone en la secuencia resultante.
Dim info2Query = From emp In db.Employees Select Name = emp.FirstName & "
" & emp.LastName, Phone = emp.HomePhone
En el ejemplo siguiente se utiliza la cláusula Select de Visual Basic (cláusula select en C#) y tipos
anónimos para devolver una secuencia de todos los ProductID y un valor calculado denominado
HalfPrice. Este valor se establece en UnitPrice dividido entre 2.
Dim specialQuery = From prod In db.Products Select prod.ProductID,
HalfPrice = CDec(prod.UnitPrice) / 2
En el ejemplo siguiente se utiliza la cláusula Select de Visual Basic (cláusula select en C#) y una
instrucción condicional para devolver una secuencia de nombre de producto y disponibilidad de producto.
Dim prodQuery = From prod In db.Products Select prod.ProductName,
Availability = If(prod.UnitsInStock - prod.UnitsOnOrder < 0, "Out Of
Stock", "In Stock")
En el ejemplo siguiente se utiliza la cláusula Select de Visual Basic (cláusula select en C#) y un tipo
conocido (Name) para devolver una secuencia de nombres de empleados.
Public Class Name
Public FirstName As String
Public LastName As String
End Class
Dim db As New Northwnd("c:\northwnd.mdf")
Dim empQuery = From emp In db.Employees Select New Name With {.FirstName
= emp.FirstName, .LastName = emp.LastName}
En el ejemplo siguiente se utilizan las cláusulas Select y Where de Visual Basic (select y where en C#)
para devolver una secuencia filtrada de los nombres de contacto de los clientes de Londres.
Dim contactQuery = From cust In db.Customers Where cust.City = "London"
Select cust.ContactName
En el ejemplo siguiente se utilizan una cláusula Select de Visual Basic (cláusula select en C#) y tipos
anónimos para devolver un subconjunto con forma de los datos de clientes.
Dim custQuery = From cust In db.Customers Select cust.CustomerID,
CompanyInfo = New With {cust.CompanyName, cust.City, cust.Country},
ContactInfo = New With {cust.ContactName, cust.ContactTitle}
En el ejemplo siguiente se utilizan consultas anidadas para devolver estos resultados:
Una secuencia de todos los pedidos y sus OrderID correspondientes.
Una subsecuencia de los elementos del pedido que tienen descuento.
La cantidad de dinero que se ahorra si no se incluye el envío en el precio.
Dim ordQuery = From ord In db.Orders Select ord.OrderID,
DiscountedProducts = (From od In ord.OrderDetails Where od.Discount > 0.0
Select od), FreeShippingDiscount = ord.Freight
Nota:
Puede invalidar los métodos predeterminados de LINQ a SQL para las operaciones de base de datos
Insert, Update y Delete.
Los programadores de Visual Studio pueden usar el Diseñador relacional de objetos para desarrollar
procedimientos almacenados con el mismo propósito.
En los pasos siguientes se asume que un objeto DataContext válido le conecta a la base de datos
Northwind.
1. Cree un nuevo objeto que incluya los datos de la columna que se van a enviar.
2. Agregue el nuevo objeto a la colección Table de LINQ a SQL que está asociada a la tabla de
destino en la base de datos.
Ejemplo
En el ejemplo de código siguiente se crea un nuevo objeto de tipo Order y se llena con los valores
adecuados. Después, se agrega el nuevo objeto a la colección Order. Finalmente, se envía el cambio a la
base de datos, como una nueva fila de la tabla Orders.
' Create a new Order object.
Dim ord As New Order With {.OrderID = 12000, .ShipCity = "Seattle", _
.OrderDate = DateTime.Now}
' Add the new object to the Orders collection.
db.Orders.InsertOnSubmit(ord)
' Submit the change to the database.
Try
db.SubmitChanges()
Catch e As Exception
Console.WriteLine(e)
' Make some adjustments.
' ...
' Try again.
db.SubmitChanges()
End Try
2. Realice los cambios deseados en los valores de miembro en el objeto LINQ a SQL resultante.
Ejemplo
En el ejemplo siguiente se consulta en la base de datos el pedido #11000 y, a continuación, se cambian
los valores de ShipName y ShipVia en el objeto Order resultante. Finalmente, los cambios en estos
valores de miembro se envían a la base de datos como cambios en las columnas ShipName y ShipVia.
' Query the database for the row to be updated.
Dim ordQuery = From ord In db.Orders Where ord.OrderID = 11000 Select ord
' Execute the query, and change the column values
' you want to change.
For Each ord As Order In ordQuery
ord.ShipName = "Mariner"
ord.ShipVia = 2
' Insert any additional changes to column values.
Next
' Submit the changes to the database.
Try
db.SubmitChanges()
Catch e As Exception
Console.WriteLine(e)
' Make some adjustments.
' ...
' Try again
db.SubmitChanges()
End Try
LINQ a SQL no admite ni reconoce las operaciones de eliminación en cascada. Si desea eliminar una fila
de una tabla que tiene restricciones, deberá realizar una de las siguientes tareas:
Utilice su propio código para eliminar primero los objetos secundarios que impiden que se
elimine el objeto primario.
De lo contrario, se producirá una excepción. Vea el segundo ejemplo de código que se muestra más
adelante en este tema.
Ejemplo
En este primer ejemplo de código se consultan en la base de datos los detalles del pedido #11000, se
marcan dichos detalles para eliminarlos y se envían estos cambios a la base de datos.
' Query the database for the rows to be deleted.
Dim deleteOrderDetails = _
From details In db.OrderDetails() _
Where details.OrderID = 11000 _
Select details
For Each detail As OrderDetail In deleteOrderDetails
db.OrderDetails.DeleteOnSubmit(detail)
Next
Try
db.SubmitChanges()
Catch ex As Exception
Console.WriteLine(ex)
' Provide for exceptions
End Try
En este segundo ejemplo, el objetivo es quitar un pedido (#10250). En primer lugar, el código examina
la tabla OrderDetails para comprobar si el pedido que se va a quitar tiene elementos secundarios en
ésta. Si el pedido tiene elementos secundarios, se marcan primero los elementos secundarios y después
el pedido para eliminarlos. El objeto DataContext coloca las eliminaciones en el orden correcto para que
los comandos de eliminación enviados a la base de datos cumplan las restricciones de la misma.
Dim db As New Northwnd("c:\northwnd.mdf")
db.Log = Console.Out
' Specify order to be removed from database.
Dim reqOrder As Integer = 10252
' Fetch OrderDetails for requested order.
Dim ordDetailQuery = _
From odq In db.OrderDetails _
Where odq.OrderID = reqOrder _
Select odq
For Each selectedDetail As OrderDetail In ordDetailQuery
Console.WriteLine(selectedDetail.Product.ProductID)
db.OrderDetails.DeleteOnSubmit(selectedDetail)
Next
' Display progress.
Console.WriteLine("Detail section finished.")
Console.ReadLine()
' Determine from Detail collection whether parent exists.
If ordDetailQuery.Any Then
Console.WriteLine("The parent is present in the Orders collection.")
' Fetch order.
Try
Dim ordFetch = _
(From ofetch In db.Orders _
Where ofetch.OrderID = reqOrder _
Select ofetch).First()
db.Orders.DeleteOnSubmit(ordFetch)
Console.WriteLine("{0} OrderID is marked for deletion.,",
ordFetch.OrderID)
Catch ex As Exception
Console.WriteLine(ex.Message)
Console.ReadLine()
End Try
Else
Console.WriteLine("There was no parent in the Orders collection.")
End If
' Display progress.
Console.WriteLine("Order section finished.")
Console.ReadLine()
Try
db.SubmitChanges()
Catch ex As Exception
Console.WriteLine(ex.Message)
Console.ReadLine()
End Try
' Display progress.
Console.WriteLine("Submit finished.")
Console.ReadLine()
Al realizar esta llamada, DataContext intenta convertir los cambios a comandos SQL equivalentes. Puede
utilizar su propia lógica personalizada para invalidar estas acciones, pero el orden de envío lo controla un
servicio de DataContext que se conoce como procesador de cambios. Todo ocurre en este orden:
2. Todos los objetos que tienen cambios pendientes se ordenan en una secuencia de objetos de
acuerdo con las dependencias entre ellos. Los objetos con cambios que dependen de otros objetos
se ordenan según sus dependencias.
3. Inmediatamente antes de que se transmitan los verdaderos cambios, LINQ a SQL inicia una
transacción para encapsular la serie de comandos individuales.
4. Los cambios en los objetos se convierten uno a uno en comandos SQL y se envían al servidor.
En este punto, cualquier error detectado por la base de datos hace que se detenga el proceso de envío, y
se inicia una excepción. Todos los cambios de la base de datos se revierten, como si no se hubiese
enviado nada. DataContext mantiene un registro completo de todos los cambios. Por lo tanto, se puede
intentar corregir el problema y llamar de nuevo a SubmitChanges, como en el ejemplo de código
siguiente.
Ejemplo
Cuando la transacción relacionada con el envío se completa correctamente, DataContext recibe los
cambios de los objetos sin tener en cuenta la información de seguimiento de cambios.
Dim db As New Northwnd("c:\northwnd.mdf")
' Make changes here.
Sub MakeChanges()
Try
db.SubmitChanges()
Catch e As ChangeConflictException
Console.WriteLine(e.Message)
' Make some adjustments
'...
' Try again.
db.SubmitChanges()
End Try
End Sub
Ejemplo
El código siguiente incluye el envío de base de datos en TransactionScope.
Dim db As New Northwnd("c:\northwnd.mdf")
Using ts = New TransactionScope()
Try
Dim prod1 = db.Products.First(Function(p) p.ProductID = 4)
Dim prod2 = db.Products.First(Function(p) p.ProductID = 5)
prod1.UnitsInStock -= 3
prod2.UnitsInStock -= 5
db.SubmitChanges()
Catch e As Exception
Console.WriteLine(e.Message)
End Try
End Using
Podría utilizar esta característica en cualquier escenario, sobre todo si está disponible un proveedor de
datos conocido, como SQL Server Express 2005. Entre los escenarios típicos, se pueden citar los
siguientes:
Se genera una aplicación cliente que necesita que una base de datos local guarde su estado sin
conexión.
Nota:
Es posible que los atributos de datos de su modelo de objetos no codifiquen todos los
elementos de la estructura de una base de datos existente. Los atributos no representan el
contenido de las funciones definidas por el usuario, los procedimientos almacenados, los
desencadenadores y las restricciones CHECK. La función CreateDatabase crea una réplica de la
base de datos sólo en lo que respecta a la información codificada en el modelo de objetos.
Este comportamiento es suficiente para varias bases de datos.
También puede utilizar CreateDatabase con SQL Server utilizando un archivo .mdf o simplemente un
nombre de catálogo, dependiendo de su cadena de conexión. LINQ a SQL utiliza la cadena de conexión
para definir la base de datos que se va a crear y en qué servidor será creada.
Ejemplo
El código siguiente proporciona un ejemplo de cómo se crearía una nueva base de datos denominada
MyDVDs.mdf.
Public Class MyDVDs
Inherits DataContext
Public DVDs As Table(Of DVD)
Public Sub New(ByVal connection As String)
MyBase.New(connection)
End Sub
End Class
<Table(Name:="DVDTable")> _
Public Class DVD
<Column(IsPrimaryKey:=True)> _
Public Title As String
<Column()> _
Public Rating As String
End Class
Puede utilizar el modelo de objetos para crear una base de datos de la manera siguiente:
Public Sub CreateDatabase()
Dim db As New MyDVDs("c:\...\mydvds.mdf")
db.CreateDatabase()
End Sub
LINQ a SQL también proporciona una API para quitar una base de datos existente antes de crear una
nueva. Puede modificar el código del escenario 1 (incluido anteriormente en este documento) para
comprobar primero si existe alguna otra versión de la base de datos. Utilice los métodos DatabaseExists
y DeleteDatabase para implementar este enfoque. Después de llamar a CreateDatabase, se habrá creado
la nueva base de datos y aceptará las consultas y comandos habituales.
Ejemplo
En el ejemplo siguiente se muestra un bloque try/catch que detecta una excepción
ChangeConflictException. La información de entidad y miembro de cada conflicto se muestra en la
ventana de la consola.
Nota:
Debe incluir la directiva using System.Reflection (Imports System.Reflection en Visual Basic) para
habilitar la recuperación de la información.
Antes de enviar cambios a la base de datos, puede especificar cuándo se deberían iniciar excepciones de
simultaneidad:
Finalizar todos los intentos de actualización, acumular todos los errores e informar de todos ellos
en la excepción (ContinueOnConflict).
Ejemplo
El código siguiente muestra ejemplos de ambos valores.
Dim db As New Northwnd("...") 'Create, update, delete code.
db.SubmitChanges(ConflictMode.FailOnFirstConflict) 'or
db.SubmitChanges(ConflictMode.ContinueOnConflict)
La propiedad UpdateCheck (asignada en tiempo de diseño) se utiliza junto con las características de
simultaneidad en tiempo de ejecución de LINQ a SQL.
Nota:
Los valores de miembro originales se comparan con el estado de la base de datos actual siempre y
cuando ningún miembro se designe como IsVersion=true. Para obtener más información, consulte
IsVersion.
Para utilizar este miembro para detectar conflictos sólo cuando la aplicación cambia
el valor del miembro
Ejemplo
En el ejemplo siguiente se especifica que nunca deberían probarse los objetos HomePage durante las
comprobaciones de actualización. Para obtener más información, consulte UpdateCheck.
<Column(Storage:="_HomePage", DbType:="NText",
UpdateCheck:=UpdateCheck.Never)> _
Public Property HomePage() As String
Get
Return Me._HomePage
End Get
Set(ByVal value As String)
If ((Me._HomePage <> value) = false) Then
Me.OnHomePageChanging(value)
Me.SendPropertyChanging
Me._HomePage = value
Me.SendPropertyChanged("HomePage")
Me.OnHomePageChanged
End If
End Set
End Property
Ejemplo
En el ejemplo siguiente se recorre en iteración una lista de conflictos acumulados.
Dim db As New Northwnd("...")
Try
db.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch ex As ChangeConflictException
Console.WriteLine("Optimistic concurrency error.")
Console.WriteLine(ex.Message)
For Each occ As ObjectChangeConflict In db.ChangeConflicts
Dim metatable As MetaTable =
db.Mapping.GetTable(occ.Object.GetType())
Dim entityInConflict = occ.Object
Console.WriteLine("Table name: " & metatable.TableName)
Console.Write("Customer ID: ")
Console.WriteLine(entityInConflict.CustomerID)
Console.ReadLine()
Next
End Try
Puede utilizar la clase MemberChangeConflict para recuperar información sobre cada uno de los
miembros en conflicto. En este mismo contexto, puede proporcionar el control personalizado del conflicto
para cualquier miembro.
Ejemplo
El código siguiente procesa una iteración en los objetos ObjectChangeConflict. Para cada objeto, procesa
una iteración en los objetos MemberChangeConflict.
Nota:
Nota:
En todos los casos, al recuperar los datos actualizados de la base de datos se actualiza en primer
lugar el registro en el cliente. Esta acción garantiza que el siguiente intento de actualización no
producirá errores en las mismas comprobaciones de simultaneidad.
Ejemplo
En este escenario, se inicia una excepción ChangeConflictException cuando User1 intenta enviar los
cambios, porque User2 ha cambiado en ese período de tiempo las columnas Assistant y Department. En
la tabla siguiente se muestra la situación.
User1 decide resolver este conflicto haciendo que los valores más nuevos de la base de datos
sobrescriban los valores actuales del modelo de objetos.
El código de ejemplo siguiente muestra cómo sobrescribir los valores actuales del modelo de objetos con
los valores de la base de datos. (No se produce ninguna inspección o control personalizado de los
conflictos entre miembros individuales.)
Dim db As New Northwnd("...")
Try
db.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch ex As ChangeConflictException
Console.WriteLine(ex.Message)
For Each occ As ObjectChangeConflict In db.ChangeConflicts
' All database values overwrite current values.
occ.Resolve(Data.Linq.RefreshMode.OverwriteCurrentValues)
Next
End Try
Ejemplo
En este escenario, se inicia una excepción ChangeConflictException cuando User1 intenta enviar los
cambios, porque User2 ha cambiado en ese período de tiempo las columnas Assistant y Department. En
la tabla siguiente se muestra la situación.
User1 decide resolver este conflicto sobrescribiendo los valores de la base de datos con los valores de
miembro de cliente actuales.
En el siguiente ejemplo de código se muestra cómo sobrescribir los valores de base de datos con los
valores de miembro de cliente actuales. (No se produce ninguna inspección o control personalizado de
los conflictos entre miembros individuales.)
Try
db.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch ex As ChangeConflictException
Console.WriteLine(ex.Message)
For Each occ As ObjectChangeConflict In db.ChangeConflicts
' No database values are merged into current.
occ.Resolve(Data.Linq.RefreshMode.KeepCurrentValues)
Next
End Try
Ejemplo
En este escenario, se inicia una excepción ChangeConflictException cuando User1 intenta enviar los
cambios, porque User2 ha cambiado en ese período de tiempo las columnas Assistant y Department. En
la tabla siguiente se muestra la situación.
User1 decide resolver este conflicto combinando los valores de la base de datos con los valores de
miembro de cliente actuales. El resultado será que los valores de la base de datos se sobrescribirán
cuando el conjunto de cambios actual haya modificado también ese valor.
Cuando User1 resuelve el conflicto utilizando KeepChanges, el resultado en la base de datos es como en
la tabla siguiente:
En el ejemplo siguiente se muestra cómo combinar los valores de la base de datos con los valores de
miembro de cliente actuales (a menos que el cliente también haya cambiado ese valor). No se produce
ninguna inspección o control personalizado de los conflictos entre miembros individuales.
Try
db.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch ex As ChangeConflictException
Console.WriteLine(ex.Message)
For Each occ As ObjectChangeConflict In db.ChangeConflicts
' Automerge database values into current for members
' that client has not modified.
occ.Resolve(Data.Linq.RefreshMode.KeepChanges)
Next
End Try
' Submit succeeds on second try.
db.SubmitChanges(ConflictMode.FailOnFirstConflict)
LINQ a SQL también proporciona herramientas especiales para ver código SQL.
Ejemplo
En el ejemplo siguiente se utiliza la propiedad Log para mostrar el código de SQL en la ventana de la
consola antes de que se ejecute el código. Puede utilizar esta propiedad con comandos de consulta,
inserción, actualización y eliminación.
db.Log = Console.Out
Dim custQuery = From cust In db.Customers Where cust.City = "London"
Select cust
For Each custObj In custQuery
Console.WriteLine(custObj.CustomerID)
Next
Ejemplo
En el ejemplo siguiente se recuperan los clientes cuya ciudad es Londres, se cambia la ciudad a París y
se envían los cambios a la base de datos.
Dim db As New Northwnd("c:\northwnd.mdf")
Dim custQuery = From cust In db.Customers Where (cust.City = "London")
Select cust
For Each custObj As Customer In custQuery
Console.WriteLine("CustomerID: {0}", custObj.CustomerID)
Console.WriteLine(vbTab & "Original value: {0}", custObj.City)
custObj.City = "Paris"
Console.WriteLine(vbTab & "Updated value: {0}", custObj.City)
Next
Dim cs As ChangeSet = db.GetChangeSet()
Console.Write("Total changes: {0}", cs)
' Freeze the console window.
Console.ReadLine()
db.SubmitChanges()
Ejemplo
En el ejemplo siguiente, la ventana de la consola muestra el resultado de la consulta, seguido de los
comandos SQL que se generan, el tipo de los comandos y el tipo de la conexión.
' Imports System.Data.Common
Dim db As New Northwnd("c:\northwnd.mdf")
Dim q = From cust In db.Customers Where cust.City = "London" Select cust
Console.WriteLine("Customers from London:")
For Each z As Customer In q
Console.WriteLine(vbTab & z.ContactName)
Next
Dim dc As DbCommand = db.GetCommand(q)
Console.WriteLine(vbNewLine & "Command Text: " & vbNewLine &
dc.CommandText)
Console.WriteLine(vbNewLine & "Command Type: {0}", dc.CommandType)
Console.WriteLine(vbNewLine & "Connection: {0}", dc.Connection)
Console.ReadLine()
Problemas de memoria
Si una consulta utiliza una colección que reside en memoria y LINQ a SQL Table<(Of <(TEntity>)>), la
consulta se podría ejecutar en memoria, dependiendo del orden en el que se especifiquen las dos
colecciones. Si la consulta se debe ejecutar en memoria, entonces es necesario recuperar los datos de la
tabla de la base de datos.
Este enfoque es ineficiente y podría provocar un uso excesivo del procesador y de la memoria. Intente
evitar esas consultas multidominio.
Eliminación en cascada
LINQ a SQL no admite ni reconoce las operaciones de eliminación en cascada. Si desea eliminar una fila
de una tabla que tiene restringidas las eliminaciones, deberá hacerlo mediante una de las siguientes
opciones:
Utilice su propio código para eliminar primero los objetos secundarios que impiden que se
elimine el objeto primario.
Tiene una directiva Imports (Visual Basic) o using (C#) para System.Linq y System.Data.Linq.
DuplicateKeyException
Durante la depuración de un proyecto de LINQ a SQL, se podrían recorrer las relaciones de una entidad.
Al hacer esto, esos elementos se introducen en la caché, y LINQ a SQL se da cuenta de su presencia. Si,
a continuación, intenta ejecutar Attach o InsertOnSubmit o un método similar que genere varias filas con
que presenten la misma clave, se producirá la excepción DuplicateKeyException.
GroupBy InvalidOperationException
Esta excepción se produce cuando un valor de columna es nulo en una consulta GroupBy que agrupa
mediante una expresión boolean, como group x by (Phone==@phone). Dado que la expresión es
boolean, se deduce que la clave será boolean, no nullableboolean. Cuando la comparación traducida
genera un valor nulo (null), se realizará un intento de asignar un nullableboolean a un boolean, y se
producirá la excepción.
Para evitar esta situación (suponiendo que desea tratar los valores null como falsos), utilice un enfoque
como el siguiente:
Conexiones
Puede proporcionar una conexión de ADO.NET existente al crear un DataContext de LINQ a SQL. Todas
las operaciones que se realicen en DataContext (incluidas las consultas) utilizan la conexión
proporcionada. Si la conexión ya está abierta, LINQ a SQL la deja como estaba cuando haya terminado
de usarla.
Dim conString = "Data Source=.\SQLEXPRESS; AttachDbFilename=
c:\northwind.mdf; Integrated Security=True;Connect Timeout=30;User
Instance=True"
Dim northwindCon = New SqlConnection(conString)
northwindCon.Open()
Dim db = New Northwnd("...")
Dim northwindTransaction = northwindCon.BeginTransaction()
Try
Dim cmd = New SqlCommand("UPDATE Products SET QuantityPerUnit =
'single item' WHERE ProductID = 3")
cmd.Connection = northwindCon
cmd.Transaction = northwindTransaction
cmd.ExecuteNonQuery()
db.Transaction = northwindTransaction
Dim prod1 = (From prod In db.Products Where prod.ProductID = 4).First
Dim prod2 = (From prod In db.Products Where prod.ProductID = 5).First
prod1.UnitsInStock -= 3
prod2.UnitsInStock -= 5
db.SubmitChanges()
northwindTransaction.Commit()
Catch e As Exception
Console.WriteLine(e.Message)
Console.WriteLine("Error submitting changes all changes rolled back")
End Try
northwindCon.Close()
Siempre puede tener acceso a la conexión y cerrarla usted mismo utilizando la propiedad Connection,
como en el código siguiente:
db.Connection.Close()
Transacciones
Puede proporcionar DataContext con su propia transacción de base de datos si la aplicación ya ha
iniciado la transacción y desea incluir en ella su DataContext.
El método preferido para realizar transacciones con .NET Framework es utilizar el objeto
TransactionScope. Con este enfoque, puede realizar transacciones distribuidas que funcionan entre bases
de datos y otros administradores de recursos residentes en memoria. Los ámbitos de transacción
requieren pocos recursos para iniciarse. Se promueven a sí mismos a transacciones distribuidas sólo
cuando hay varias conexiones en el ámbito de la transacción.
Using ts As New TransactionScope()
db.SubmitChanges()
ts.Complete()
End Using
No puede utilizar este enfoque para todas las bases de datos. Por ejemplo, la conexión de SqlClient no
puede promover las transacciones del sistema cuando funciona en un servidor SQL Server 2000. En su
lugar, se da de alta automáticamente en una transacción distribuida completa cada vez que detecta que
se utiliza un ámbito de transacción.
Por ejemplo, supongamos que los datos de la clase Customer ocupan dos tablas (customer1 y
customer2). La consulta siguiente devuelve una secuencia de objetos Customer:
Dim results As IEnumerable(Of Customer) = db.ExecuteQuery(Of Customer)
("SELECT c1.custID as CustomerID,c2.custName as ContactName" & _
"FROM customer1 AS c1, customer2 as c2 WHERE c1.custid = c2.custid")
Siempre y cuando los nombres de columna de los resultados tabulares coincidan con las propiedades de
columna de la clase de entidad, LINQ a SQL crea objetos a partir de cualquier consulta SQL.
Parameters
El método ExecuteQuery acepta parámetros: El código siguiente ejecuta una consulta parametrizada:
Dim results As IEnumerable(Of Customer) = db.ExecuteQuery(Of Customer)
("SELECT contactname FROM customers WHERE city = {0}, 'London'")
End Sub
Nota:
Los parámetros se expresan en el texto de la consulta con la misma notación con llaves utilizada por
Console.WriteLine() y String.Format(). String.Format() toma la cadena de consulta proporcionada y
sustituye los parámetros entre llaves por nombres de parámetros generados, como @p0, @p1 …,
@p(n).
Nota:
Los programadores que usen Visual Studio pueden utilizar el Diseñador relacional de objetos para
generar este código.
2. Utilice la herramienta de línea de comandos SqlMetal para generar un archivo de código fuente
de Visual Basic o C#. Si escribe los comandos siguientes en un símbolo del sistema, puede generar
archivos de código fuente de Visual Basic y C# que incluyan procedimientos almacenados y
funciones:
Nota:
Si utiliza Visual Studio, puede utilizar el Diseñador relacional de objetos para personalizar las
acciones de inserción, actualización y eliminación.
En esta sección de temas se describen las técnicas que LINQ a SQL proporciona para personalizar las
operaciones de inserción, lectura, actualización y eliminación en una aplicación.
Entre las técnicas de LINQ a SQL que están disponibles para personalizar estas operaciones se incluyen
las siguientes.
Opciones de carga
En las consultas es posible controlar la cantidad de datos relacionados con el destino principal que se
recuperan al establecer una conexión con la base de datos. Esta funcionalidad se implementa en gran
medida mediante el uso de DataLoadOptions.
Métodos Partial
En su asignación predeterminada, LINQ a SQL proporciona métodos parciales para ayudar a implementar
lógica empresarial propia.
LINQ a SQL proporciona la máxima flexibilidad para manipular y conservar los cambios realizados en los
objetos. En cuanto están disponibles los objetos de entidad (ya sea recuperándolos a través de una
consulta o construyéndolos nuevamente), puede cambiarlos como los objetos normales de la aplicación.
Es decir, puede cambiar sus valores, agregarlos a las colecciones y quitarlos de las mismas. LINQ a SQL
realiza un seguimiento de los cambios y está listo para volver a transmitirlos a la base de datos cuando
llame al método SubmitChanges.
Nota:
LINQ a SQL no admite ni reconoce las operaciones de eliminación en cascada. Si desea eliminar una
fila de una tabla que tiene restricciones, deberá establecer la regla ON DELETE CASCADE en la
restricción FOREIGN KEY de la base de datos o bien utilizar su propio código para eliminar primero
los objetos secundarios que impiden que se elimine el objeto primario. De lo contrario, se producirá
una excepción.
En los siguientes extractos se utilizan las clases Customer y Order de la base de datos de ejemplo
Northwind. Para no extendernos demasiado, no incluimos las definiciones de clase.
Dim db As New Northwnd("…\Northwnd.mdf")
Dim cust As Customer = (From c In db.Customers Where c.CustomerID =
"ALFKI" Select c) .First()
' Change the name of the contact.
cust.ContactName = "New Contact"
' Create and add a new Order to Orders collection.
Dim ord As New Order With {.OrderDate = DateTime.Now}
cust.Orders.Add(ord)
' Delete an existing Order.
Dim ord0 As Order = cust.Orders(0)
' Removing it from the table also removes it from
' the Customer‟s list.
db.Orders.DeleteOnSubmit(ord0)
' Ask the DataContext to save all the changes.
db.SubmitChanges()
Al llamar a SubmitChanges, LINQ a SQL genera y ejecuta automáticamente los comandos SQL
necesarios para volver a transmitir los cambios a la base de datos.
Nota:
El método de invalidación no debe llamar a SubmitChanges o Attach. LINQ a SQL inicia una
excepción si se llama a estos métodos en un método de invalidación.
No se pueden utilizar métodos de invalidación para iniciar, confirmar o detener una transacción.
La operación SubmitChanges se realiza bajo una transacción. Una transacción anidada interna
puede interferir con la transacción externa. Los métodos de invalidación de carga sólo pueden
iniciar una transacción después de determinar que la operación no se realiza en Transaction.
produzca un conflicto de simultaneidad optimista. LINQ a SQL detecta esta excepción para que
se pueda procesar correctamente la opción SubmitChanges proporcionada en SubmitChanges.
Es el usuario quien debe invocar la API dinámica correcta. Por ejemplo, en el método de
invalidación de actualización, sólo se puede llamar al método ExecuteDynamicUpdate. LINQ a
SQL no detecta ni comprueba si el método dinámico invocado coincide con la operación
aplicable. Los resultados no están definidos si se llama a un método no aplicable (por ejemplo,
ExecuteDynamicDelete para un objeto que se va a actualizar).
Finalmente, se espera que el método de invalidación realice la operación indicada. Las semántica
de las operaciones de LINQ a SQL, como la carga rápida, la carga diferida y SubmitChanges
requieren que los métodos de invalidación proporcionen el servicio indicado. Por ejemplo, una
invalidación de carga que sólo devuelva una colección vacía sin comprobar el contenido en la
base de datos probablemente generará datos incoherentes.
Nota:
Si utiliza Visual Studio, puede utilizar el Diseñador relacional de objetos para agregar funciones de
validación y otras personalizaciones a las clases de entidad.
Por ejemplo, la asignación predeterminada para la clase Customer en la base de datos de ejemplo
Northwind incluye el método parcial siguiente:
Partial Private Sub OnAddressChanged()
End Sub
Puede implementar un método propio agregando código como el siguiente a su propia clase Customer
parcial:
Partial Class Customer
Private Sub OnAddressChanged()
' Insert business logic here.
End Sub
End Class
Este enfoque se suele usar en LINQ a SQL para invalidar los métodos predeterminados para Insert,
Update y Delete, y para validar las propiedades durante los eventos de ciclo de vida de los objetos.
Código:
Descripción
En el ejemplo siguiente se muestra primero ExampleClass tal como se podría definir con una herramienta
que genera código, como SQLMetal, y, a continuación, se muestra cómo se podría implementar sólo uno
de los dos métodos.
Código
' Code-generating tool defines a partial class, including
' two partial methods.
Partial Class ExampleClass
Partial Private Sub OnFindingMaxOutput()
End Sub
Partial Private Sub OnFindingMinOutput()
End Sub
Sub ExportResults()
OnFindingMaxOutput()
OnFindingMinOutput()
End Sub
End Class
' Developer implements one of the partial methods. Compiler
' discards the other method.
Class ExampleClass
Private Sub OnFindingMaxOutput()
Console.WriteLine("Maximum has been found.")
End Sub
End Class
Código:
Descripción
En el ejemplo siguiente se utiliza la relación entre las entidades Shipper y Order. Observe, entre los
métodos, los métodos parciales, InsertShipper y DeleteShipper. Estos métodos invalidan los métodos
parciales predeterminados proporcionados por la asignación de LINQ a SQL.
Código
Public Shared LoadOrdersCalled As Integer = 0
Private Function LoadOrders(ByVal shipper As Shipper) As _
IEnumerable(Of Order)
LoadOrdersCalled += 1
Return Me.Orders.Where(Function(o) o.ShipVia = _
shipper.ShipperID)
End Function
Public Shared LoadShipperCalled As Integer = 0
Private Function LoadShipper(ByVal order As Order) As Shipper
LoadShipperCalled += 1
Return Me.Shippers.Single(Function(s) s.ShipperID = _
order.ShipVia)
End Function
Public Shared InsertShipperCalled As Integer = 0
Private Sub InsertShipper(ByVal instance As Shipper)
InsertShipperCalled += 1
' Call a Web service to perform an insert operation.
InsertShipperService(shpr:=Nothing)
End Sub
Public Shared UpdateShipperCalled As Integer = 0
Private Sub UpdateShipper(ByVal original As Shipper, ByVal current _
As Shipper)
UpdateShipperCalled += 1
' Call a Web service to update shipper.
InsertShipperService(shpr:=Nothing)
End Sub
Public Shared DeleteShipperCalled As Boolean
Private Sub DeleteShipper(ByVal instance As Shipper)
DeleteShipperCalled = True
End Sub
Principio subyacente
LINQ a SQL convierte las consultas LINQ a SQL para su ejecución en una base de datos. Los resultados
son IEnumerable con establecimiento inflexible de tipos. Dado que se trata de objetos CLR (Common
Language Runtime) normales, se puede usar el enlace de datos de objeto normal para mostrar los
resultados. Por otro lado, las operaciones de cambio (inserciones, actualizaciones y eliminaciones)
requieren pasos adicionales.
Operación
El enlace implícito a controles de formularios Windows Forms se logra mediante la implementación de
IListSource. Los elementos genéricos Table<(Of <(TEntity>)>) (Table<T> en C# o Table(Of T) en Visual
Basic) y DataQuery de los orígenes de datos se han actualizado para implementar IListSource. Los
motores de enlace de datos de la interfaz de usuario (formularios Windows Forms y Windows
Presentation Foundation) comprueban si su origen de datos implementa IListSource. Por consiguiente, al
escribir una modificación directa de una consulta en un origen de datos de un control, se llama
implícitamente a la generación de colecciones de LINQ a SQL, como en el ejemplo siguiente:
Dim dataGrid1 As New DataGrid()
Dim dataGrid2 As New DataGrid()
Dim dataGrid3 As New DataGrid()
Dim custQuery = From cust In db.Customers Select cust
dataGrid1.DataSource = custQuery
dataGrid2.DataSource = custQuery
dataGrid2.DataMember = "Orders"
Dim bs = New BindingSource()
bs.DataSource = custQuery
dataGrid3.DataSource = bs
Implementación de IListSource
LINQ a SQL implementa IListSource en dos ubicaciones:
El origen de datos es Table<(Of <(TEntity>)>): LINQ a SQL examina la tabla para rellenar una
colección DataBindingList que mantiene una referencia en la tabla.
Colecciones especializadas
Para muchas de las características antes descritas en este documento, BindingList<(Of <(T>)>) se ha
especializado en algunas clases diferentes. Estas clases son SortableBindingList genérica y
DataBindingList genérica. Ambas se declaran como internas.
SortableBindingList genérica
Esta clase se hereda de BindingList<(Of <(T>)>) y es una versión de BindingList<(Of <(T>)>) que se
puede ordenar. La ordenación es una solución en memoria y nunca entra en contacto con la propia base
de datos. BindingList<(Of <(T>)>) implementa IBindingList, pero no admite la ordenación de forma
predeterminada. Sin embargo, BindingList<(Of <(T>)>) implementa IBindingList con los métodos
básicos virtuales. Puede invalidar estos métodos con facilidad. La clase SortableBindingList genérica
invalida SupportsSortingCore, SortPropertyCore, SortDirectionCore y ApplySortCore. ApplySort llama a
ApplySortCore y ordena la lista de elementos T de una propiedad dada.
seguimiento de las operaciones de agregar o quitar elementos. Esos dos métodos se pueden utilizar para
operaciones básicas, como la ordenación, pero realmente serán implementados por clases de nivel
superior, como la clase DataBindingList genérica.
Enlace a EntitySets
El enlace a EntitySet es un caso especial, porque EntitySet ya es una colección que implementa
IBindingList. LINQ a SQL agrega compatibilidad con las operaciones de ordenación y cancelación
(ICancelAddNew). Una clase EntitySet utiliza una lista interna para almacenar las entidades. Esta lista
es una colección de nivel inferior basada en una matriz genérica, la clase ItemList genérica.
Almacenamiento en caché
Las consultas LINQ a SQL implementan GetList. Cuando la clase BindingSource de formularios Windows
Forms se encuentra con esta interfaz, llama a GetList() tres veces para la misma conexión. Para evitar
esta situación, LINQ a SQL implementa una caché por cada instancia para almacenar y devolver siempre
la misma colección generada.
Cancelación
IBindingList define un método AddNew que los controles utilizan para crear un nuevo elemento a partir
de una colección enlazada. El control DataGridView muestra esta característica perfectamente,
incluyendo un asterisco en el encabezado de la última fila visible. El asterisco indica que se puede
agregar un nuevo elemento.
Además de esta característica, una colección también puede implementar ICancelAddNew. Esta
característica permite a los controles cancelar o verificar la validación o no validación del nuevo elemento
editado.
No realiza un seguimiento de los cambios hasta que la interfaz de usuario confirma la edición.
Solución de problemas
En esta sección se describen varios elementos que podrían ayudarle a solucionar los problemas de las
aplicaciones de enlace de datos de LINQ a SQL.
Debe utilizar propiedades; utilizar sólo campos no es suficiente. Los formularios Windows Forms
requieren que se utilicen.
Un miembro de clase asignado a una clave principal tiene un establecedor, pero LINQ a SQL no
admite el cambio de identidad del objeto. Por consiguiente, la clave principal/única que se usa
en la asignación no se puede actualizar en la base de datos. Un cambio en la cuadrícula produce
una excepción al llamar a SubmitChanges.
Si una entidad está enlazada en dos cuadrículas independientes (por ejemplo, una maestra y
otra de detalle), Delete en la cuadrícula maestra no se propaga a la cuadrícula de detalle.
Para implementar esta asignación en LINQ a SQL, debe especificar los atributos y las propiedades de los
atributos en la clase raíz de la jerarquía de herencia.
Los programadores que usen Visual Studio también pueden utilizar el Diseñador relacional de objetos
para asignar jerarquías de herencia.
Ejemplo 1
En el ejemplo siguiente, se asigna una clase Order a la tabla Orders de la base de datos de ejemplo
Northwind. Se ha agregado a la clase un método de instancia local.
La capa de presentación o de cliente llama a los métodos en la interfaz remota de nivel intermedio, y la
capa de acceso a datos (DAL) en ese nivel ejecutará consultas o procedimientos almacenados asignados
a métodos DataContext. El nivel intermedio devuelve los datos a los clientes generalmente como
representaciones XML de entidades u objetos proxy.
En el nivel intermedio, las entidades son creadas por el contexto de los datos, el cual realiza el
seguimiento de su estado, y administra la carga diferida desde, y el envío de los cambios a, la base de
datos. Estas entidades están "asociadas" al DataContext. Sin embargo, una vez que las entidades se
han enviado a otro nivel a través de la serialización, quedan desasociadas, lo cual significa que el
DataContext ya no realiza el seguimiento de su estado. Las entidades que el cliente devuelve para las
actualizaciones se deben volver a asociar al contexto de los datos antes de que LINQ a SQL pueda enviar
los cambios a la base de datos. El cliente es responsable de devolver valores originales y/o marcas de
tiempo al nivel intermedio si se requieren para las comprobaciones de simultaneidad optimista.
La siguiente ilustración muestra la arquitectura básica de una aplicación de n niveles que utiliza LINQ a
SQL en la capa de acceso a datos.
relacional de objetos, como se describe en otra parte de la documentación. En tiempo de diseño, tiene la
opción de hacer las clases de entidad serializables. Otra opción consiste en crear un conjunto
independiente de clases que encapsulan los datos que se van a serializar y, a continuación, proyectar en
esos tipos serializables cuando se devuelvan datos en las consultas de LINQ.
A continuación, se define la interfaz con los métodos a los que los clientes llamarán para recuperar,
insertar y actualizar datos. Los métodos de interfaz envuelven las consultas de LINQ. Puede utilizar
cualquier tipo de mecanismo de serialización para administrar las llamadas a métodos remotos y la
serialización de datos. El único requisito es que, si tiene relaciones cíclicas o bidireccionales en su modelo
de objetos, como las que existen entre Clientes y Pedidos en el modelo de objetos estándar de
Northwind, deberá utilizar un serializador que admita esa situación. Windows Communication Foundation
(WCF) DataContractSerializer admite relaciones bidireccionales, pero no así el serializador XmlSerializer
que se utiliza con servicios web que no son de WCF. Si selecciona el serializador XmlSerializer, deberá
asegurarse de que su modelo de objetos no contenga relaciones cíclicas.
Implemente sus reglas de empresa u otra lógica específica del dominio utilizando las clases parciales y
métodos de DataContext y las clases de entidad para conectar con eventos de tiempo de ejecución de
LINQ a SQL.
Al insertar datos, el nivel de presentación puede construir un nuevo objeto y enviarlo al nivel intermedio,
o puede hacer que el nivel intermedio construya el objeto en función de los valores que proporciona. En
general, la recuperación e inserción de datos en aplicaciones de n niveles no difiere mucho del proceso
para aplicaciones de 2 niveles.
Cuanto mayor es el número de cambios que realiza el nivel de presentación, más complejo se hace
realizar el seguimiento de esos cambios y empaquetarlos de vuelta hacia el nivel intermedio. La
implementación de un mecanismo para comunicar los cambios depende completamente de la aplicación.
El único requisito es que se deben dar a LINQ a SQL aquellos valores originales que se requieren para las
comprobaciones de la simultaneidad optimista.
LINQ a SQL en Visual Studio 2008 no proporciona ninguna infraestructura para realizar un seguimiento
del estado de las entidades una vez serializadas hacia un cliente. LINQ a SQL permite arquitecturas
orientadas a servicios donde las interacciones entre los datos y las capas de presentación son pequeñas
y relativamente atómicas, pero no realiza ninguna conversión de ida y vuelta de valores originales. Por
consiguiente, si desea utilizar un cliente inteligente fuertemente acoplado con LINQ a SQL, y una base de
datos utiliza simultaneidad optimista con valores originales, tendrá que implementar su propio
mecanismo para comunicar los cambios entre el nivel de presentación y el nivel intermedio. Depende del
diseñador del sistema decidir si tiene sentido hacer este pequeño trabajo adicional para obtener las
ventajas que proporciona LINQ a SQL en el nivel intermedio. Por otro lado, si la base de datos tiene
marcas de tiempo, no es necesario utilizar lógica personalizada para el seguimiento de los cambios.
La lógica empresarial puede ser tan simple como una validación del esquema para asegurarse de que el
tipo del campo es compatible con el tipo de la columna de la tabla. Por el contrario, también puede estar
compuesta por un conjunto de objetos que interactúan con diversos grados de complejidad. Las reglas se
pueden implementar como procedimientos almacenados en la base de datos o como objetos en
memoria. Independientemente de cómo se implemente la lógica empresarial, LINQ a SQL permite utilizar
clases parciales y métodos parciales para separar la lógica empresarial del código de acceso a datos. El
siguiente diagrama muestra la relación de la lógica empresarial con las clases de LINQ a SQL:
Las clases parciales que definen las entidades y el DataContext contienen métodos parciales. Éstos son
los puntos de extensibilidad que puede utilizar para aplicar su lógica empresarial antes y después de
cualquier actualización, inserción o eliminación para una entidad o propiedad de entidad. Los métodos
parciales se pueden ver como eventos de tiempo de compilación. El generador de código define una
firma de método y llama a los métodos de los descriptores de acceso get y set de la propiedad, al
constructor DataContext y, en algunos casos, en segundo plano, se llama a SubmitChanges. Sin
embargo, si no implementa un método parcial particular, entonces su definición y todas las referencias a
él se eliminan en tiempo de compilación.
Me._CustomerID = value
Me.SendPropertyChanged("CustomerID")
Me.OnCustomerIDChanged()
End If
End Set
End Property
En su parte de la clase, escriba una definición de implementación del método. En Visual Studio, después
de escribir partial, aparecerá información de IntelliSense para las definiciones de método de la otra parte
de la clase.
Partial Public Class Customer
Private Sub OnCustomerIDChanging(value As String)
' Perform custom validation logic here.
End Sub
End Class
Las filas de una tabla de base de datos relacional no tienen identidades únicas. Dado que cada fila tiene
una clave principal única, dos filas no comparten el mismo valor de clave. Sin embargo, este hecho
restringe sólo el contenido de la tabla de base de datos.
En realidad, la mayoría de las veces los datos se extraen de la base de datos y se colocan en un nivel
diferente, donde una aplicación trabaja con ellos. Éste es el modelo que LINQ a SQL admite. Cuando los
datos se extraen de la base de datos como filas, no se espera que dos filas que representan los mismos
datos realmente se correspondan con las mismas instancias de fila. Si consulta un cliente concreto dos
veces, obtiene dos filas de datos. Cada fila contiene la misma información.
En el caso de los objetos, se espera algo muy diferente. Se espera que, si se solicita la misma
información a DataContext varias veces, de hecho se obtendrá la misma instancia de objeto. Este
comportamiento es el esperado porque los objetos tienen un significado especial para una aplicación y se
espera que se comporten como objetos. Se han diseñado como jerarquías o gráficos. Lo que se espera es
que se recuperarán como tales y no que se recibirán múltiples instancias replicadas sólo porque se
solicitó lo mismo más de una vez.
En LINQ a SQL, DataContext administra la identidad de objeto. Siempre que se recupera una nueva fila
de la base de datos, la fila se registra en una tabla de identidad según su clave principal y se crea un
nuevo objeto. Siempre que se recupera esa misma fila, la instancia de objeto original se devuelve a la
aplicación. De esta manera, DataContext convierte el concepto de identidad desde el punto de vista de la
base de datos (es decir, claves principales) en el concepto de identidad desde el punto de vista del
lenguaje (es decir, instancias). La aplicación sólo ve el objeto en el estado en que se recuperó por
primera vez. Los nuevos datos, si son diferentes, se descartan.
LINQ a SQL utiliza este enfoque para administrar la integridad de los objetos locales de manera que se
admitan las actualizaciones optimistas. Dado que los únicos cambios que se producen después de crear
el objeto son los realizados por la aplicación, la intención de la aplicación está clara. Si en ese período de
Nota:
Si el objeto solicitado por la consulta se puede identificar con facilidad como ya recuperado, no se
ejecuta ninguna consulta. La tabla de identidad actúa como caché de todos los objetos previamente
recuperados.
Ejemplos
Primer ejemplo de almacenamiento de objetos en caché
En este ejemplo, si ejecuta la misma consulta dos veces, en cada ocasión recibe una referencia al mismo
objeto en memoria.
Dim cust1 As Customer = (From cust In db.Customers Where cust.CustomerID
= "BONAP" Select cust).First()
Dim cust2 As Customer = (From cust In db.Customers Where cust.CustomerID
= "BONAP" Select cust).First()
Segundo ejemplo de almacenamiento de objetos en caché
En este ejemplo, si ejecuta consultas diferentes que devuelven la misma fila de la base de datos, en cada
ocasión recibe una referencia al mismo objeto en memoria.
Dim cust1 As Customer = (From cust In db.Customers Where cust.CustomerID
= "BONAP" Select cust).First()
Dim cust2 As Customer = (From ord In db.Orders Where
ord.Customer.CustomerID = "BONAP" Select ord).First().Customer
En este escenario, no ejecuta comandos de base de datos (como INSERT) en la base de datos. En su
lugar, cambia los valores y ejecuta los métodos de su modelo de objetos. Si desea consultar la base de
datos o enviar cambios, LINQ a SQL convierte sus solicitudes en los comandos SQL correctos y los envía
a la base de datos.
En la tabla siguiente se resumen los elementos más fundamentales del modelo de objetos de LINQ a SQL
y su relación con los elementos del modelo de datos relacionales:
Nota:
En las líneas siguientes se asume que tiene conocimientos básicos del modelo de datos relacionales y
sus reglas.
Código:
<Table(Name:="Customers")> _
Public Class Customer
Public CustomerID As String
' ...
Public City As String
End Class
Sólo las instancias de clases declaradas como tablas (es decir, clases de entidad) pueden guardarse en la
base de datos.
Código:
<Table(Name:="Customers")> _
Public Class Customer
<Column(IsPrimaryKey:=True)> _
Public CustomerID As String
<Column()> _
Public City As String
End Class
Sólo los campos y las propiedades que estén asignados a columnas se conservan en la base de datos o
se recuperan de ella. Si no se han declarado como columnas, se consideran partes transitorias de la
lógica de aplicación.
El atributo ColumnAttribute tiene varias propiedades que se pueden utilizar para personalizar los
miembros que representan columnas (por ejemplo, para designar un miembro como representativo de
una columna de clave principal).
Un conjunto de resultados devuelto por una llamada a un procedimiento asignado es una colección con
establecimiento inflexible de tipos.
LINQ a SQL asigna los procedimientos almacenados y las funciones a los métodos utilizando los atributos
FunctionAttribute y ParameterAttribute. Los métodos que representan procedimientos almacenados se
distinguen de los que representan funciones definidas por el usuario mediante la propiedad
IsComposable. Si esta propiedad se establece en false (valor predeterminado), el método representa un
procedimiento almacenado. Si se establece en true, el método representa una función de base de datos.
Nota:
Si utiliza Visual Studio, puede utilizar el Diseñador relacional de objetos para crear métodos
asignados a procedimientos almacenados y funciones definidas por el usuario.
Código:
' This is an example of a stored procedure in the Northwind
Estados de objeto
La tabla siguiente enumera los posibles estados de los objetos de LINQ a SQL.
Estado Descripción
Untracked Objeto del que LINQ a SQL no realiza un seguimiento. Algunos ejemplos son:
Un objeto no consultado a través del DataContext actual (como un
objeto recientemente creado).
Un objeto creado a través de la deserialización.
Un objeto consultado a través de un DataContext diferente.
ToBeUpdated Objeto que se sabe que se modificó desde que se recuperó. Esto produce una
operación UPDATE en la base de datos durante la ejecución de
SubmitChanges.
ToBeDeleted Objeto marcado para la eliminación, que produce una operación DELETE en la
base de datos durante la ejecución de SubmitChanges.
Insertar objetos
Puede solicitar Inserts explícitamente mediante el método InsertOnSubmit. Como alternativa, LINQ a
SQL puede deducir los elementos Inserts mediante la búsqueda de los objetos conectados a uno de los
objetos conocidos que deben actualizarse. Por ejemplo, si agrega un objeto Untracked a EntitySet<(Of
Para las clases de una jerarquía de herencia, InsertOnSubmit(o) establece también el valor del miembro
designado como discriminador para que coincida con el tipo del objeto o. Si un tipo que coincide con el
valor de discriminador predeterminado, esta acción hace que dicho valor se sobrescriba con el valor
predeterminado.
Nota importante:
Eliminar objetos
Un objeto o del que se realiza un seguimiento se marca para la eliminación mediante una llamada a
DeleteOnSubmit(o) en el objeto Table<(Of <(TEntity>)>) adecuado. LINQ a SQL considera la eliminación
de un objeto de un EntitySet<(Of <(TEntity>)>) como una operación de actualización y el valor de clave
externa correspondiente se establece en null. El destino de la operación (o) no se elimina de su tabla.
Por ejemplo, cust.Orders.DeleteOnSubmit(ord) indica una actualización donde la relación entre cust y ord
se rompe estableciendo la clave externa ord.CustomerID en null. Esto no produce la eliminación de la fila
que corresponde a ord.
LINQ a SQL realiza el procesamiento siguiente cuando se elimina un objeto (DeleteOnSubmit) de su
tabla:
Cuando se llama a SubmitChanges, se realiza una operación DELETE para ese objeto.
Nota:
Al quitar un objeto de una tabla, se indica a LINQ a SQL que genere un comando SQL DELETE
correspondiente en el momento de ejecutar el método SubmitChanges. Esta acción no quita el objeto
de la memoria caché ni propaga la eliminación a los objetos relacionados.
Para reclamar el id de un objeto eliminado, utilice una nueva instancia de DataContext. Para la
limpieza de los objetos relacionados, puede utilizar la característica de eliminación en cascada de la
base de datos, o eliminar los objetos relacionados manualmente.
Los objetos relacionados no tienen que eliminarse en ningún orden especial (a diferencia de lo que
sucede en la base de datos).
Actualizar objetos
Las Updates se detectan observando las notificaciones de cambios. Las notificaciones se proporcionan a
través del evento PropertyChanging en los establecedores de propiedades. Cuando LINQ a SQL recibe
una notificación del primer cambio de un objeto, crea una copia del mismo y lo considera como candidato
para generar una instrucción Update.
Para los objetos que no implementan INotifyPropertyChanging, LINQ a SQL mantiene una copia de los
valores que tenían los objetos la primera vez que se materializaron. Al llamar a SubmitChanges, LINQ a
SQL compara los valores originales y actuales para decidir si se ha cambiado el objeto.
Para las actualizaciones de relaciones, la autoridad es la referencia del elemento secundario al elemento
primario (es decir, la referencia que corresponde a la clave externa). La referencia en dirección inversa
(es decir, del elemento primario al elemento secundario) es opcional. Las clases de relaciones
(EntitySet<(Of <(TEntity>)>) y EntityRef<(Of <(TEntity>)>)) garantizan que las referencias
bidireccionales son coherentes para las relaciones uno a varios y uno a uno. Si el modelo de objetos no
utiliza EntitySet<(Of <(TEntity>)>) o EntityRef<(Of <(TEntity>)>) y si la referencia inversa está
presente, es su responsabilidad mantenerla coherente con la referencia adelantada cuando se actualice
la relación.
Términos Description
Simultaneidad Situación en la que dos o más usuarios intentan actualizar la misma fila de la
base de datos al mismo tiempo.
Conflicto de Situación en la cual dos o más usuarios intentan enviar valores incompatibles
simultaneidad a una o más columnas de una fila al mismo tiempo.
Control de Técnica que primero investiga si otras transacciones han cambiado los
simultaneidad valores de una fila antes de permitir que se envíen los cambios.
optimista Por otra parte, se encuentra el control de simultaneidad pesimista, que
bloquea el registro para evitar los conflictos de simultaneidad.
El control optimista se denomina así porque considera que son escasas las
posibilidades de que una transacción interfiera con otra.
Para resolver el conflicto, primero se detectan los miembros del objeto que están en conflicto y, después,
se decide qué hacer.
Nota:
Sólo los miembros asignados como Always o WhenChanged participan en las comprobaciones de
simultaneidad optimista. No se realiza ninguna comprobación para los miembros marcados como
Never.
Código:
Por ejemplo, en el escenario siguiente, User1 empieza a preparar una actualización consultando una fila
en la base de datos. User1 recibe una fila con los valores Alfreds, Maria y Sales.
User1 desea cambiar el valor de la columna Manager a Alfred y el valor de la columna Department a
Marketing. Antes de que User1 pudiera enviar los cambios, User2 ha enviado cambios a la base de datos.
Por lo tanto, ahora el valor de la columna Assistant ha cambiado a Mary y el valor de la columna
Department a Service.
Cuando User1 intenta enviar los cambios, se produce un error en el envío y se inicia una excepción
ChangeConflictException. Esto es consecuencia de que los valores de base de datos para la columna
Assistant y la columna Department no son los esperados. Los miembros que representan las columnas
Assistant y Department están en conflicto. En la tabla siguiente se resume la situación.
Determine cuántos detalles del conflicto desea recuperar e incluya el código correspondiente en
el bloque try/catch.
Indique en el código try/catch cómo desea resolver los distintos conflictos que detecte.
LINQ a SQL convierte las consultas que se escriben en consultas SQL equivalentes y las envía al servidor
para su procesamiento. Más específicamente, la aplicación utiliza la API de LINQ a SQL para solicitar la
ejecución de la consulta. Después, el proveedor LINQ a SQL transforma la consulta en texto SQL y
delega la ejecución al proveedor ADO. El proveedor ADO devuelve los resultados de la consulta como
DataReader. El proveedor LINQ a SQL convierte los resultados de ADO en una colección IQueryable de
objetos de usuario.
Nota:
La mayoría de los métodos y operadores de los tipos integrados de .NET Framework tienen
equivalentes directos en SQL. Los que LINQ no puede convertir generan excepciones en tiempo de
ejecución.
La tabla siguiente muestra las similitudes y diferencias entre los elementos de las consultas LINQ y LINQ
a SQL.
Por ejemplo, la consulta siguiente navega de pedidos a clientes como una manera de restringir los
resultados a sólo los pedidos de los clientes de Londres.
Dim db As New Northwnd("c:\northwnd.mdf")
Dim londonOrderQuery = From ord In db.Orders Where ord.Customer.City =
"London" Select ord
Si no existieran propiedades de relación, tendría que escribirlas manualmente como combinaciones,
como lo haría en una consulta SQL y como muestra el código siguiente:
Dim db As New Northwnd("c:\northwnd.mdf")
Dim londOrderQuery = From cust In db.Customers Join ord In db.Orders On
cust.CustomerID Equals ord.CustomerID Select ord
Puede utilizar la propiedad de relación para definir esta relación concreta una vez. Después, puede
utilizar la sintaxis de punto más apropiada. Sin embargo, la existencia de las propiedades de relación es
más importante porque los modelos de objetos específicos del dominio se definen normalmente como
jerarquías o gráficos. Los objetos para los que programa tienen referencias a otros objetos. Es una mera
coincidencia que las relaciones de objeto a objeto se correspondan con tipos de relaciones de clave
externa en las bases de datos. Por ello, el acceso de propiedad proporciona una manera apropiada de
escribir combinaciones.
A este respecto, las propiedades de relación son más importantes en el lado de los resultados de una
consulta que como parte de la propia consulta. Una vez que la consulta ha recuperado los datos sobre un
cliente determinado, la definición de clase indica que los clientes tienen pedidos. En otras palabras, se
espera que la propiedad Orders de un cliente determinado sea una colección que se llena con todos los
pedidos de ese cliente. De hecho, esa era su intención cuando definió las clases de esta manera. Espera
ver allí los pedidos aun cuando la consulta no los ha solicitado. Espera que su modelo de objetos refleje
lo que en realidad se encuentra en una extensión en memoria de la base de datos, con los objetos
relacionados inmediatamente disponibles.
Ahora que tiene las relaciones, puede escribir consultas haciendo referencia a las propiedades de relación
definidas en las clases. Estas referencias de relación se corresponden con las relaciones de clave externa
de la base de datos. Las operaciones que utilizan estas relaciones se convierten en combinaciones más
complejas en el código SQL equivalente. Siempre y cuando haya definido una relación (mediante el
atributo AssociationAttribute ), no tiene que codificar una combinación explícita en LINQ a SQL.
Para ayudar a mantener esta ilusión, LINQ a SQL implementa una técnica denominada carga diferida.
Considere la consulta SQL siguiente para proyectar una lista de pares CustomerID-OrderID:
SELECT t0.CustomerID, t1.OrderID
FROM Customers AS t0 INNER JOIN
Orders AS t1 ON t0.CustomerID = t1.CustomerID
WHERE (t0.City = @p0)
La ilustración siguiente muestra gráficamente la relación entre las tablas.
Para obtener los mismos resultados en LINQ a SQL, utilice la referencia de propiedad Orders que ya
existe en la clase Customer. La referencia Orders proporciona la información necesaria para ejecutar la
consulta y proyectar los pares CustomerID-OrderID, como en el código siguiente:
Dim db As New Northwnd("c:\northwnd.mdf")
Dim idQuery = From cust In db.Customers, ord In cust.Orders Where
cust.City = "London" Select cust.CustomerID, ord.OrderID
También puede hacer lo contrario. Es decir, puede consultar Orders y utilizar su referencia de relación
Customer para tener acceso a información sobre el objeto Customer asociado. El código siguiente
proyecta los mismos pares CustomerID-OrderID que antes, pero esta vez se consulta Orders en lugar de
Customers.
Dim db As New Northwnd("c:\northwnd.mdf")
Dim idQuery = From ord In db.Orders Where ord.Customer.City = "London"
Select ord.CustomerID, ord.OrderID
Ejecución remota
Una consulta ejecutada por el motor de base de datos suele ser más eficaz debido a los índices
de la base de datos.
Ejecución local
En otras situaciones, podría desear tener el conjunto completo de entidades relacionadas en la memoria
caché local. Para este propósito, EntitySet<(Of <(TEntity>)>) proporciona el método Load para cargar
explícitamente todos los miembros de EntitySet<(Of <(TEntity>)>).
Si EntitySet<(Of <(TEntity>)>) ya está cargado, las consultas posteriores se ejecutan localmente. Este
enfoque ayuda en dos sentidos:
Si se debe utilizar el conjunto completo localmente o varias veces, puede evitar las consultas
remotas y la latencia asociada a las mismas.
semántica de lista, que normalmente no se puede obtener a través de una consulta remota en un
conjunto no ordenado. Por esta razón, tales métodos cargan implícitamente EntitySet<(Of <(TEntity>)>)
para permitir la ejecución local.
Por ejemplo, quizás desee consultar un conjunto determinado de pedidos y después, sólo en algunas
ocasiones, enviar una notificación por correo electrónico a determinados clientes. Inicialmente, no está
obligado a recuperar todos los datos de clientes con todos los pedidos. Puede utilizar la carga diferida
para aplazar la recuperación de información adicional hasta que ésta no sea realmente necesaria.
Considere el ejemplo siguiente:
Dim db As New Northwnd("c:\northwnd.mdf")
Dim notificationQuery = From ord In db.Orders Where ord.ShipVia = 3
Select ord
For Each ordObj As Order In notificationQuery
If ordObj.Freight > 200 Then
SendCustomerNotification(ordObj.Customer)
ProcessOrder(ordObj)
End If
Next
También podría darse el caso contrario. Es posible que una aplicación necesite ver al mismo tiempo los
datos de los clientes y los pedidos. Sabe que necesita ambos conjuntos de datos. Sabe que su aplicación
necesita información de los pedidos de cada cliente en cuanto obtiene los resultados. Seguramente no
deseará enviar consultas individuales para los pedidos de cada cliente. Lo que realmente desea es
recuperar los datos de pedidos junto con los clientes.
Dim db As New Northwnd("c:\northwnd.mdf")
db.DeferredLoadingEnabled = False
Dim custQuery = From cust In db.Customers Where cust.City = "London"
Select cust
For Each custObj As Customer In custQuery
For Each ordObj As Order In custObj.Orders
ProcessCustomerOrder(ordObj)
Next
Next
También puede combinar clientes y pedidos en una consulta si forma el producto cruzado y recupera
todos los elementos de datos relativos como una gran proyección. Sin embargo, estos resultados no son
entidades. Las entidades son objetos que tienen identidad y que se pueden modificar, mientras que estos
resultados serían proyecciones que no se pueden cambiar ni conservar. Lo que es peor, recuperaría una
gran cantidad de datos redundantes, ya que cada cliente se repite para cada pedido en el resultado
simplificado de la combinación.
Lo que realmente necesita es una manera de recuperar al mismo tiempo un conjunto de objetos
relacionados. El conjunto es una sección delineada de un gráfico, de tal forma que nunca recuperaría
más o menos de lo necesario para el uso previsto. Con este fin, LINQ a SQL proporciona
DataLoadOptions para la carga inmediata de una región de un modelo de objetos. Entre los métodos, se
incluyen:
El método LoadWith, para cargar inmediatamente los datos relacionados con el destino principal.
El método AssociateWith, para filtrar los objetos recuperados para una relación determinada.
Para minimizar tales riesgos, utilice la seguridad integrada para realizar una conexión confiable con SQL
Server. Con este enfoque no es necesario almacenar una contraseña en la cadena de conexión.
8.3.2.6.14. Serialización
En este tema se describe la funcionalidad de serialización de LINQ a SQL. En los párrafos siguientes se
proporciona información sobre cómo agregar la característica de serialización durante la generación de
código en tiempo de diseño y el comportamiento de serialización en tiempo de ejecución de las clases
LINQ a SQL.
Puede agregar código de serialización en tiempo de diseño mediante cualquiera de estos métodos:
Información general
El código generado por LINQ a SQL proporciona funcionalidad de carga diferida de forma
predeterminada. La carga diferida es muy apropiada en el nivel intermedio, para una carga de datos
transparente a petición. Sin embargo, da problemas para la serialización, porque el serializador activa la
carga diferida esté prevista o no. De hecho, cuando se serializa un objeto, se serializa su cierre transitivo
en todas las referencias con carga diferida salientes.
La característica de serialización de LINQ a SQL resuelve este problema, principalmente a través de dos
mecanismos:
Definitions
Serializador DataContract: serializador predeterminado utilizado por el componente WCF de .NET
Framework 3.0 o versiones posteriores.
Serialización unidireccional: versión serializada de una clase que contiene sólo una propiedad de
asociación unidireccional (para evitar un ciclo). Por convención, se marca para la serialización la
propiedad que se encuentra en el lado primario de una relación de clave externa y clave
principal. No se serializa el otro lado en una asociación bidireccional.
Ejemplo de código
El código siguiente utiliza las clases Customer y Order tradicionales de la base de datos de ejemplo
Northwind y muestra cómo estas clases se decoran con atributos de serialización.
' The class is decorated with the DataContract attribute.
<Table(Name:="dbo.Customers"), DataContract()> _
Partial Public Class Customer
Implements System.ComponentModel.INotifyPropertyChanging,
System.ComponentModel.INotifyPropertyChanged
' Private fields are not decorated with any attributes,
' and are elided.
Private _CustomerID As String
' Public properties are decorated with the DataMember
' attribute and the Order property specifying the
' serial number. See the Order class later in this topic
' for exceptions
<Column(Storage:="_CustomerID", DbType:="NChar(5) NOT NULL",
CanBeNull:=false, IsPrimaryKey:=true),DataMember(Order:=1)> _
Public Property CustomerID() As String
Get
Return Me._CustomerID
End Get
Set
If ((Me._CustomerID = value) = false) Then
Me.OnCustomerIDChanging(value)
Me.SendPropertyChanging
Me._CustomerID = value
Me.SendPropertyChanged("CustomerID")
Me.OnCustomerIDChanged
End If
End Set
End Property
' The following Association property is decorated with
' DataMember because it is the parent side of the
' relationship. The reverse property in the Order
' class does not have a DataMember attribute. This
' factor prevents a 'cycle.'
<Association(Name:="FK_Orders_Customers", Storage:="_Orders",
OtherKey:="CustomerID", DeleteRule:="NO ACTION"), _
DataMember(Order:=13)> _
Public Property Orders() As EntitySet(Of [Order])
Get
Return Me._Orders
End Get
LINQ a SQL utiliza métodos del modelo de objetos para representar procedimientos almacenados en la
base de datos. Los métodos se designan como procedimientos almacenados aplicando el atributo
FunctionAttribute y, si es necesario, el atributo ParameterAttribute.
Los programadores de Visual Studio normalmente usarán el Diseñador relacional de objetos para asignar
procedimientos almacenados. Los temas de esta sección muestran cómo formar estos métodos y cómo
llamarlos en una aplicación si es usted quien escribe el código.
Al ejecutar un procedimiento almacenado que devuelve un conjunto de filas, se utiliza una clase de
resultado que almacena los resultados devueltos del procedimiento almacenado.
Ejemplo
El ejemplo siguiente representa un procedimiento almacenado que devuelve filas de clientes y utiliza un
parámetro de entrada para devolver sólo aquellas filas en las que figura "London" como la ciudad del
cliente. En el ejemplo se supone que hay una clase CustomersByCityResult enumerable.
CREATE PROCEDURE [dbo].[Customers By City]
(@param1 NVARCHAR(20))
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT CustomerID, ContactName, CompanyName, City from Customers
as c where c.City=@param1
END
<FunctionAttribute(Name:="dbo.Customers By City")> _
Public Function CustomersByCity(<Parameter(DbType:= "NVarChar(20)")>
ByVal param1 As String) As ISingleResult(Of CustomersByCityResult)
Dim result As IExecuteResult = Me.ExecuteMethodCall(Me,
CType(MethodInfo.GetCurrentMethod, MethodInfo), param1)
Return CType(result.ReturnValue, ISingleResult(Of
CustomersByCityResult))
End Function
Sub ReturnRowset()
' Call the stored procedure.
Dim db As New Northwnd("c:\northwnd.mdf")
Dim result As IEnumerable(Of CustomersByCityResult) = _
db.CustomersByCity("London")
For Each cust As CustomersByCityResult In result
Console.WriteLine("CustID={0};City={1}",cust.CustomerID,cust.City)
Next
End Sub
Ejemplo
En el ejemplo siguiente se utiliza un parámetro de entrada único (el identificador de cliente) y se
devuelve un parámetro de salida (las ventas totales para ese cliente).
CREATE PROCEDURE [dbo].[CustOrderTotal]
@CustomerID nchar(5),
@TotalSales money OUTPUT
AS
SELECT @TotalSales = SUM(OD.UNITPRICE*(1-OD.DISCOUNT) * OD.QUANTITY)
FROM ORDERS O, "ORDER DETAILS" OD
where O.CUSTOMERID = @CustomerID AND O.ORDERID = OD.ORDERID
<FunctionAttribute(Name:="dbo.CustOrderTotal")> _
Public Function CustOrderTotal(<Parameter(Name:="CustomerID",
DbType:="NChar(5)")> ByVal customerID As String,
<Parameter(Name:="TotalSales", DbType:="Money")> ByRef totalSales As
System.Nullable(Of Decimal)) As <Parameter(DbType:="Int")> Integer
Dim result As IExecuteResult = Me.ExecuteMethodCall(Me,
CType(MethodInfo.GetCurrentMethod, MethodInfo), customerID, totalSales)
totalSales = CType(result.GetParameterValue(1), System.Nullable(Of
Decimal))
Return CType(result.ReturnValue, Integer)
End Function
La llamada a este procedimiento almacenado sería como sigue:
Dim db As New Northwnd("C:\...\northwnd.mdf")
Dim totalSales As Decimal? = 0
db.CustOrderTotal("alfki", totalSales)
Console.WriteLine(totalSales)
Compare este escenario con los procedimientos almacenados que generan varias formas de resultados
secuencialmente.
El atributo ResultTypeAttribute se aplica a los procedimientos almacenados que devuelven varios tipos de
resultados para especificar el conjunto de tipos que el procedimiento puede devolver.
Ejemplo
En el siguiente ejemplo de código SQL, la forma del resultado depende de la entrada (shape =1 o shape
= 2). No se sabe qué proyección se devolverá primero.
CREATE PROCEDURE VariableResultShapes(@shape int)
AS
if(@shape = 1)
select CustomerID, ContactTitle, CompanyName from customers
else if(@shape = 2)
select OrderID, ShipName from orders
<FunctionAttribute(Name:="dbo.VariableResultShapes"), _
ResultType(GetType(VariableResultShapesResult1)), _
ResultType(GetType(VariableResultShapesResult2))> _
Public Function VariableResultShapes(<Parameter(DbType:="Int")> ByVal
shape As System.Nullable(Of Integer)) As IMultipleResults
Dim result As IExecuteResult = Me.ExecuteMethodCall(Me,
CType(MethodInfo.GetCurrentMethod, MethodInfo), shape)
Return CType(result.ReturnValue, IMultipleResults)
End Function
Usaría código similar al siguiente para ejecutar este procedimiento almacenado.
Nota:
Debe utilizar el modelo GetResult<(Of <(TElement>)>) para obtener un enumerador del tipo
correcto, basado en su conocimiento del procedimiento almacenado.
Este tipo de procedimiento almacenado puede generar más de una forma de resultado, pero se sabe en
qué orden se devuelven los resultados. Compare este escenario con el escenario donde no se sabe el
orden de los resultados devueltos.
Ejemplo
A continuación se muestra el código T-SQL de un procedimiento almacenado que devuelve varias formas
de resultados secuencialmente:
CREATE PROCEDURE MultipleResultTypesSequentially
AS
select * from products
select * from customers
<FunctionAttribute(Name:="dbo.MultipleResultTypesSequentially"), _
ResultType(GetType(MultipleResultTypesSequentiallyResult1)), _
ResultType(GetType(MultipleResultTypesSequentiallyResult2))> _
Public Function MultipleResultTypesSequentially() As IMultipleResults
Dim result As IExecuteResult = Me.ExecuteMethodCall(Me,
CType(MethodInfo.GetCurrentMethod, MethodInfo))
Return CType(result.ReturnValue, IMultipleResults)
End Function
Usaría código similar al siguiente para ejecutar este procedimiento almacenado.
Dim db As New Northwnd("c:\northwnd.mdf")
Dim sprocResults As IMultipleResults = db.MultipleResultTypesSequentially
' First read products.
For Each prod As Product In sprocResults.GetResult(Of Product)()
Console.WriteLine(prod.ProductID)
Next
' Next read customers.
For Each cust As Customer In sprocResults.GetResult(Of Customer)()
Console.WriteLine(cust.CustomerID)
Next
Si utiliza Visual Studio, puede usar el Diseñador relacional de objetos para asignar los procedimientos
almacenados para realizar operaciones de inserción, actualización y eliminación.
Nota:
Para volver a leer los valores generados por la base de datos, utilice parámetros de salida en los
procedimientos almacenados. Si no puede usar parámetros de salida, escriba una implementación de
método parcial en lugar de confiar en las invalidaciones generadas por el Diseñador relacional de
objetos. Los miembros asignados a los valores generados por la base de datos deben establecerse en
los valores adecuados una vez que se ha completado correctamente una operación INSERT o
UPDATE.
Código:
Description
En el ejemplo siguiente se da por hecho que la clase Northwind contiene dos métodos para llamar a los
procedimientos almacenados que se utilizan para las invalidaciones en una clase derivada.
Código
<[Function]()> _
Public Function CustomerOrders(<Parameter(Name:="CustomerID",
DbType:="NChar(5)")> ByVal customerID As String) As IEnumerable(Of Order)
Dim result As IExecuteResult = Me.ExecuteMethodCall(Me, _
(CType(MethodInfo.GetCurrentMethod(), MethodInfo)), _
customerID)
Return CType(result.ReturnValue, IEnumerable(Of Order))
End Function
<[Function]()> _
Public Function CustomerById(<Parameter(Name:="CustomerID",
DbType:="NChar(5)")> ByVal customerID As String) As IEnumerable(Of
Customer)
Dim result As IExecuteResult = Me.ExecuteMethodCall(Me, _
CType(MethodInfo.GetCurrentMethod(), MethodInfo), customerID)
Return CType(result.ReturnValue, IEnumerable(Of Customer))
End Function
Código:
Description
La clase siguiente utiliza estos métodos para la invalidación.
Código
Public Class NorthwindThroughSprocs : Inherits Northwnd
Sub New()
MyBase.New("")
End Sub
' Override loading of Customer.Orders by using method wrapper.
Private Function LoadOrders(ByVal customer As Customer) As _
IEnumerable(Of Order)
Return Me.CustomerOrders(customer.CustomerID)
End Function
' Override loading of Order.Customer by using method wrapper.
Private Function LoadCustomer(ByVal order As Order) As Customer
Return Me.CustomerById(order.CustomerID).Single()
End Function
' Override INSERT operation on Customer by calling the
' stored procedure directly.
Private Sub InsertCustomer(ByVal customer As Customer)
' Call the INSERT stored procedure directly.
Me.ExecuteCommand("exec sp_insert_customer …")
End Sub
' The UPDATE override works similarly, that is, by
' calling the stored procedure directly.
Private Sub UpdateCustomer(ByVal original As Customer, ByVal _
current As Customer)
Código
Dim db As New NorthwindThroughSprocs()
Dim custQuery = From cust In db.Customers Where cust.City = "London"
Select cust
For Each custObj In custQuery
' Deferred loading of cust.Orders uses the override LoadOrders.
For Each ord In custObj.Orders
' ...
' Make some changes to customers/orders.
' Overrides for Customer are called during the execution
' of the following:
db.SubmitChanges()
Next
Next
Código:
Description
Puede modificar el ejemplo proporcionado en Personalizar las operaciones mediante procedimientos
almacenados (LINQ a SQL) reemplazando incluso la primera consulta (que provoca la ejecución de SQL
dinámico) con una llamada a método que incluya un procedimiento almacenado.
Código
<[Function]()> _
Public Function CustomersByCity(<Parameter(Name:="City",
DbType:="NVarChar(15)")> ByVal city As String) As IEnumerable(Of
Customer)
Dim result = Me.ExecuteMethodCall(Me, _
(CType(MethodInfo.GetCurrentMethod(), IEnumerable(Of _
Customer))), city)
8.3.2.6.16. Transacciones
LINQ a SQL admite tres modelos de transacción distintos. A continuación se enumeran estos modelos por
orden de comprobaciones realizadas.
Transacción implícita
Al llamar a SubmitChanges, LINQ a SQL comprueba si la llamada se encuentra en el ámbito de una
Transaction o si la propiedad Transaction (IDbTransaction) está establecida en una transacción local
iniciada por el usuario. Si no encuentra ninguna de estas transacciones, LINQ a SQL inicia una
transacción local (IDbTransaction) y la usa para ejecutar los comandos SQL generados. Cuando se han
completado todos los comandos SQL correctamente, LINQ a SQL confirma la transacción local y devuelve
un valor.
El propósito de este tema es resumir las discordancias respecto al sistema de tipos de clave entre los
tipos CTS (Common Type System) y los tipos de base de datos.
Sistemas de tipos
Los dos sistemas de tipos que se analizarán son:
Este sistema de tipos se define mediante una combinación de una versión de un lenguaje CLS
(Common Language Specification), como Visual Basic o C#, que se implementa en Common
Language Runtime (CLR), y los tipos principales que se definen en el espacio de nombres
System de .NET Framework. Entre los primeros, se incluyen Int32, Int64 y String; entre los
últimos, se incluyen tipos derivados como DateTime, TimeSpan, etc.
Para el tema que nos ocupa, es el sistema de tipos implementado por la implementación de T-
SQL de SQL Server en SQL Server 2000 y SQL Server 2005.
Tipos de datos
Para la conversión de valores deben tenerse en cuenta las diferencias básicas de los tipos de datos. La
materialización de la proyección en una consulta hace que se conviertan los valores de los tipos de base
de datos a los tipos CTS correspondientes especificados por el programador de la consulta. De igual
forma, el uso de parámetros requiere una conversión en la otra dirección.
Nota:
Los tipos de .NET Framework que son específicos de SQL Server en el espacio de nombres
System.Data.SqlTypes no se incluyen en la comparación.
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).
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 un equivalente a las expresiones booleanas.
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
Los tipos siguientes tienen asignaciones múltiples. Esta cuestión puede considerarse una generalización
del problema de la ausencia de tipos equivalentes, ya que cada tipo que no tiene ningún equivalente
puede asignarse a uno o más tipos en el otro sistema de tipos, con valores y semántica de expresión
diferentes. En todas las conversiones de valores se debe considerar la asignación que se utiliza y la
posible necesidad de viajes de ida y vuelta. En todas las conversiones de expresiones se debe considerar
la semántica específica del tipo en el dominio asignado.
Tipos CTS
String. Se puede asignar a uno de seis tipos de caracteres diferentes que utilizan
codificaciones (ASCII o Unicode), tamaños (8k o más) y relleno (fijo o de longitud variable)
diferentes.
Integer. Los tipos enteros se pueden asignar (en función de las restricciones CHECK que
limitan el intervalo de valores) a tipos integrales de CTS con o sin signo. El tipo sin signo
puede ser del mismo tamaño o del siguiente tamaño más bajo (por ejemplo, int se puede
asignar a Int32 o UInt32 o UInt16).
Decimal. Decimal permite 29 dígitos como máximo, mientras que el tipo decimal de T-SQL
puede tener hasta 38 dígitos. Para conservar la magnitud, el tipo decimal de T-SQL se debe
asignar a Double. En este tipo de asignación se pierde precisión, porque Double no tiene una
precisión equivalente. Por otro lado, sin la restricción de precisión del tipo decimal de T-SQL, una
conversión a Decimal podría producir una pérdida de magnitud, que no es aceptable.
Números de punto flotante. Tienen una semántica peculiarmente diferente. Los números de
punto flotante de CTS admiten valores de cero positivo y negativo, infinito positivo y negativo y
NaN (no es un número). No sucede lo mismo con real y float de SQL. SQL también admite
números de punto flotante de precisión variable (dígitos de mantisa), mientras que CTS utiliza
una precisión preestablecida (mantisa).
Asignación no predeterminada
Algunas asignaciones son interesantes debido a los esquemas de base de datos heredados que utilizan
tipos que no se corresponden con el uso previsto en un programa CLS. Considere el lenguaje de
definición de datos (DDL) siguiente y las clases correspondientes en Visual Basic :
create table T1 (
Col1 nchar(10),
Col2 nchar(10))
Class C1
Dim x As Integer ' Map to T1.Col1
Dim y As Integer ' Map to T1.Col2
End Class
En el ejemplo anterior, los miembros C1 de la clase se asignan a un tipo bastante diferente (entero a
cadena de caracteres). Hay diferencias obvias en cuanto al dominio de los valores. Los enteros mayores
que 231-1 producirán un desbordamiento durante la lectura. Los enteros negativos de 10 dígitos no se
pueden representar si se utiliza la representación decimal. Lo que es aún más interesante, debe
considerarse la asignación al convertir las expresiones, porque la asignación afecta a la cláusula where,
no sólo a la proyección.
Otro ejemplo común es asignar una enumeración definida por el usuario a una columna de tipo integral.
Para los datos y esquemas heredados en los que no se usa una enumeración definida por el usuario en la
base de datos, esta asignación no predeterminada requiere una validación adicional.
Semántica de expresión
Además de la discordancia en pares entre los tipos de CTS 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
podrían generarse expresiones SQL semánticamente equivalentes a una expresión CLS dada. Sin
embargo, no está claro si las diferencias semánticas entre expresiones aparentemente similares son
evidentes para un usuario de CLS y, por tanto, si se han previsto o no los cambios requeridos 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.
-- 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
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 a 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 a 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 a SQL no cambia la intercalación.
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.
De igual forma, la prioridad de los tipos en T-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. CTS no proporciona
ninguna promoción equivalente.
En los casos más sencillos, estas diferencias generan expresiones CLS con conversiones de tipos que son
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
T-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
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 generar í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.
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:
Nota:
Este comportamiento del operador Like sólo se aplica en C#; la palabra clave de Visual Basic
Like se mantiene invariable.
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 una cadena de CTS. 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!
El índice de inicio de las cadenas es 0 en CTS, pero 1 en SQL. Por consiguiente, cualquier función
que tenga un índice, requiere la conversión del índice.
CTS admite el operador de módulo ('%') para los números de punto flotante, pero SQL no.
Para resumir, podría requerirse una conversión compleja para las expresiones de CLS, 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
Para complicar el problema, algunos síntomas pueden depender de los datos. Si no se incluyen en las
pruebas valores de datos concretos, la discordancia puede aparecer por sorpresa en tiempo de ejecución
o, lo que sería peor, pasar desapercibida y dañar los datos.
Las expresiones CTS con dos valores se deben convertir en una expresión equivalente de tres
valores. Consulte la sección 4.1.
Asignación no estándar
Diferencias de intercalación
Expresiones CTS válidas pueden generar errores en tiempo de ejecución porque se asume una
semántica de operador específica.
Por ejemplo, el segundo predicado de una expresión conjuntiva (AND lógica) podría producir un
error a menos que la expresión se convierta a SQL con la semántica de cortocircuito. Consulte el
ejemplo 1 de la sección 4.4.
Limitaciones de la asignación
Es posible restringir los casos sujetos a discordancia de tipos si se limita el conjunto de tipos CTS que se
asignan y limitando además los tipos correspondientes para cada tipo CTS determinado en el conjunto
restringido. Sin embargo, esto deja al usuario como responsable de la tarea de conversión. Por ejemplo,
si no se permite la asignación de nvarchar a System..::.String en el ejemplo de la sección 4.5, el
programador de la consulta tiene que realizar la conversión de los valores y expresiones de la consulta
para lograr las mismas tareas.
Problemas de rendimiento
Además de las diferencias semánticas enumeradas anteriormente, un problema práctico común es la
significativa reducción del rendimiento. Entre los ejemplos se incluyen los siguientes:
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 CLS 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
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.
Para los conjuntos de datos grandes, estos problemas de rendimiento pueden determinar si una
aplicación se puede implementar o no. Además de las diferencias semánticas, los problemas de
rendimiento también pasan a ser críticos.
Para evitar que se inicie InvalidOperationException, las funciones definidas por el usuario en LINQ a SQL
deben presentarse de una de las formas siguientes:
Una función ajustada como llamada a método que tiene los atributos de asignación correctos.
Los temas de esta sección muestran cómo formar estos métodos y cómo llamarlos en una aplicación si
es usted quien escribe el código. Los programadores de Visual Studio normalmente usarían el Diseñador
relacional de objetos para asignar las funciones definidas por el usuario.
Nota:
Ejemplo
El código de SQL siguiente presenta una función ReverseCustName() definida por el usuario con valores
escalares.
CREATE FUNCTION ReverseCustName(@string varchar(100))
RETURNS varchar(100)
AS
BEGIN
DECLARE @custName varchar(100)
-- Implementation left as exercise for users.
RETURN @custName
END
Para este código, asignaría un método de cliente como el siguiente:
<FunctionAttribute(Name:="dbo.ReverseCustName", IsComposable:=True)> _
Public Function ReverseCustName(<Parameter(Name:="string", _
DbType:="VarChar(100)")> ByVal [string] As String) As _
<Parameter(DbType:="VarChar(100)")> String
Return CType(Me.ExecuteMethodCall(Me, _
CType(MethodInfo.GetCurrentMethod, MethodInfo), _
[string]).ReturnValue, String)
End Function
Ejemplo
La función de SQL siguiente declara explícitamente que devuelve TABLE. Por lo tanto, la estructura de
conjunto de filas devuelta se define implícitamente.
CREATE FUNCTION ProductsCostingMoreThan(@cost money)
RETURNS TABLE
AS
RETURN
SELECT ProductID, UnitPrice
FROM Products
WHERE UnitPrice > @cost
LINQ a SQL asigna la función de la manera siguiente:
<FunctionAttribute(Name:="dbo.ProductsCostingMoreThan",
IsComposable:=True)> _
Public Function ProductsCostingMoreThan(<Parameter(DbType:="Money")>
ByVal cost As System.Nullable(Of Decimal)) As IQueryable(Of
ProductsCostingMoreThanResult)
Return Me.CreateMethodCallQuery(Of ProductsCostingMoreThanResult)
(Me, CType(MethodInfo.GetCurrentMethod, MethodInfo), cost)
End Function
El código de SQL siguiente muestra cómo se puede unir a la tabla devuelta por la función y, si no,
tratarla como lo haría con cualquier otra tabla:
SELECT p2.ProductName, p1.UnitPrice
FROM dbo.ProductsCostingMoreThan(80.50)
AS p1 INNER JOIN Products AS p2 ON p1.ProductID = p2.ProductID
En LINQ a SQL, la consulta se representaría de la siguiente manera:
Dim q = From p In db.ProductsCostingMoreThan(80.5), p1 In db.Products
Where p.ProductID = p1.ProductID Select p.ProductID, p1.UnitPrice
Al llamar a la misma función fuera de una consulta, LINQ a SQL crea una consulta simple a partir de la
expresión de llamada al método. A continuación se muestra la sintaxis de SQL (el parámetro @p0 se
enlaza a la constante pasada):
SELECT dbo.ReverseCustName(@p0)
LINQ a SQL crea lo siguiente:
Dim str As String = db.ReverseCustName("LINQ to SQL")
Ejemplo
En la siguiente consulta LINQ a SQL se puede ver una llamada inline al método de la función definida por
el usuario ReverseCustName que se ha generado. La función no se ejecuta inmediatamente, porque la
ejecución de la consulta está diferida. El código SQL generado para esta consulta realiza la conversión a
una llamada a la función definida por el usuario en la base de datos (vea el código SQL que se encuentra
después de la consulta).
Dim custQuery = From cust In db.Customers Select cust.ContactName, Title
= db.ReverseCustName(cust.ContactTitle)
SELECT [t0].[ContactName],
dbo.ReverseCustName([t0].[ContactTitle]) AS [Title]
FROM [Customers] AS [t0]
8.3.3. Referencia
En esta sección se proporciona información de referencia para programadores de LINQ a SQL.
Sin embargo, puede utilizar los métodos incompatibles en las consultas si se da alguna de estas
condiciones:
El método se puede evaluar como valor convertible antes de que tenga lugar el proceso de
conversión.
En otras palabras, el método no debe depender de variables lambda que no se enlazan hasta
que se produce la ejecución de la consulta.
El método se puede aplicar a los resultados una vez recuperado de la base de datos.
En este tema se proporciona una matriz de asignaciones completa y alguna información específica sobre
lo siguiente:
Asignación de Enum, DateTime y XML.
Tipos de moneda de SQL Server y conversiones en CLR.
Tipos de punto flotante.
Serialización binaria y de cadena.
Asignación de enumeración
LINQ a SQL admite la asignación de tipos Enum de CLR de dos maneras:
Al asignar un tipo Enum de CLR a un tipo numérico de SQL, el valor entero subyacente del tipo
Enum de CLR se asigna al valor del campo de la base de datos de SQL. El valor del campo en
SQL se recupera directamente como el valor integral subyacente del tipo Enum. Al cambiar el
valor de Enum y guardar los datos en la base de datos, el valor integral subyacente de Enum se
almacena en el campo de la base de datos.
Al asignar un tipo Enum de CLR a un tipo de texto SQL, el valor de SQL de la base de datos se
asigna a los nombres de los miembros Enum de CLR. Por ejemplo, si un tipo Enum denominado
DaysOfWeek contiene un miembro denominado Tue, ese miembro se asigna a un valor de base
de datos de Tue. Puede lograr esta asignación utilizando la reflexión en el tipo Enum.
La asignación predeterminada de SQL para un tipo Enum de CLR es el equivalente de SQL de su tipo
integral subyacente.
Nota:
XDocument no puede utilizarse para asignar tipos de datos XML porque esta clase no es serializable
(no implementa la interfaz IXmlSerializable).
SQL Server
DECIMAL(precision,scale)
Nota:
Técnicamente, SQL Server puede almacenar valores NaN, infinitos positivos/negativos y cero
positivo/negativo en las consultas. Estos valores no siempre se comportan como se espera, incluso
en las consultas SQL directas.
Si una clase implementa .Parse() (con una firma similar a DateTime, por ejemplo), puede
serializarlo en cualquier campo de texto SQL (CHAR, NCHAR, VARCHAR, NVARCHAR, TEXT,
NTEXT, XML). Si serializa el objeto en una cadena, el valor devuelto de ToString() se guarda en
la base de datos. Si deserializa una cadena almacenada, .Parse() se invoca en la cadena para
devolver el objeto construido.
Si una clase implementa ISerializable, se puede serializar en cualquier campo binario de SQL
(BINARY, VARBINARY, IMAGE). Para serializar y deserializar el objeto, se sigue el
comportamiento estándar de ISerializable.
IXmlSerializable
Operador Is
Conversiones de tipo
Se habilitan las conversiones implícitas o explícitas de tipos CLR de origen a tipos CLR de destino
si hay una conversión similar válida en SQL Server. Después de la conversión, las conversiones
de tipos cambian el comportamiento de las operaciones realizadas en una expresión CLR para
coincidir con el comportamiento de otras expresiones CLR que tienen una correspondencia
natural con el tipo de destino. Las conversiones de tipos también se pueden convertir en el
contexto de la asignación de herencia. Los tipos de los objetos se pueden convertir a subtipos de
entidad más específicos de forma que se pueda tener acceso a los datos específicos de su
subtipo.
Visual Basic C#
Topico Descripción
traducción de operadores La sección relativa a la semántica de valores Null de este tema describe
de consulta estándar la semántica de comparación de valores Null en LINQ a SQL.
System.String (métodos - La sección "Diferencias respecto a .NET" de este tema describe cómo un
LINQ a SQL) valor 0 devuelto de LastIndexOf podría significar que la cadena es nula
o que la posición encontrada es 0.
Cómo: Calcular la suma Describe cómo el operador Sum se evalúa como null (Nothing en
de los valores de una Visual Basic) en lugar de 0 para una secuencia que sólo contiene valores
secuencia numérica nulos o para una secuencia vacía.
Operadores compatibles
LINQ a SQL admite los operadores siguientes.
Operadores aritméticos básicos:
+
- (resta)
*
/
División de enteros en Visual Basic (\)
% (Visual Basic Mod)
<<
>>
- (negación unaria)
Operadores de comparación básicos:
= en Visual Basic y == en C#
<> en Visual Basic y != en C#
Is/IsNot en Visual Basic
<
<=
>
>=
8.3.3.1.7. System.Convert
LINQ a SQL no admite los siguientes métodos Convert.
Versiones con un parámetro IFormatProvider.
Métodos que incluyen matrices de caracteres o matrices de bytes:
FromBase64CharArray
ToBase64CharArray
FromBase64String
ToBase64String
Los métodos siguientes:
public static <Type2> To <Type2>(<Type1> value); donde
Type1 y Type2 son sbyte, uint, ulong o ushort.
C#:
int To<int type>(string value, int fromBase),
ToString(... value, int toBase)
Visual Basic:
Function To(Of [Numeric])(value as String, fromBase As Integer)
As [Numeric], ToString( value As …, toBase As Integer)
IsDBNull
GetTypeCode
ChangeType
8.3.3.1.8. System.DateTime
LINQ a SQL no admite los métodos DateTime siguientes.
IsDaylightSavingTime IsLeapYear
DaysInMonth ToBinary
ToFileTime ToFileTimeUtc
ToLongDateString ToLongTimeString
ToOADate ToShortDateString
ToShortTimeString ToUniversalTime
FromBinary UtcNow
FromFileTime FromFileTimeUtc
FromOADate GetDateTimeFormats
El intervalo y la precisión del tipo DateTime de CLR son mayores que el intervalo y la precisión
de los tipos de SQL Server. Así, los datos de SQL Server nunca pierden magnitud o precisión
cuando se expresan con un tipo de CLR. Sin embargo, a la inversa, el intervalo y la precisión
pueden estar comprometidos.
Las fechas de SQL Server no reconocen el concepto de TimeZone, una característica totalmente
admitida en CLR.
Los usuarios deben decidir cómo desean almacenar las fechas en sus bases de datos (como hora local,
hora UTC o invariable) y realizar las conversiones necesarias antes y después de las consultas LINQ a
SQL.
Por el contrario, en SQL, la función ROUND siempre aplica un redondeo desde 0. Por consiguiente, 2,5
se redondea a 3, mientras que se redondea a 2 en .NET Framework.
LINQ a SQL admite directamente la semántica de ROUND en SQL y no intenta implementar el redondeo
bancario.
8.3.3.1.10. System.Object
LINQ a SQL admite los siguientes métodos Object.
Object..::.ToString()()()
Object..::.GetHashCode()()() Object..::.ReferenceEquals(Object,
Object)
Object..::.MemberwiseClone()()() Object..::.GetType()()()
8.3.3.1.11. System.String
LINQ a SQL no admite los métodos String siguientes.
String..::.Copy(String)
String..::.CompareOrdinal(String, String)
String..::.Format
String..::.Join
String..::.IndexOfAny(array<Char>[]()[])
String..::.Split
String..::.ToCharArray()()()
String..::.ToUpper(CultureInfo)
String..::.TrimEnd(array<Char>[]()[])
String..::.TrimStart(array<Char>[]()[])
Dado que muchos métodos, como Replace, ToLower, ToUpper, y el indizador de carácter, no
tienen ningún equivalente válido para las columnas TEXT o NTEXT y XML, se producirán
SqlExceptions si se convierten de la forma habitual. Este comportamiento se considera que es
aceptable para estos tipos. Sin embargo, todas las operaciones de cadena deben coincidir con la
8.3.3.1.12. System.TimeSpan
En LINQ a SQL, no puede asignar campos de base de datos a TimeSpan. Sin embargo, se admiten las
operaciones en TimeSpan porque se pueden devolver valores TimeSpan a partir de la sustracción de
DateTime o se pueden incluir en una expresión como literales o variables enlazadas.
En su forma más elemental, LINQ a SQL asigna una base de datos a un DataContext, una tabla a una
clase y las columnas y relaciones a las propiedades de esas clases. También puede utilizar atributos para
asignar una jerarquía de herencia en su modelo de objetos.
Los programadores que utilizan Visual Studio normalmente realizan la asignación basada en atributos
mediante el Diseñador relacional de objetos. También puede utilizar la herramienta de línea de comandos
SQLMetal o puede incluir los atributos en el código usted mismo.
Nota:
En las secciones siguientes se describe con más detalle la asignación basada en atributos.
Atributo DatabaseAttribute
Utilice este atributo para especificar el nombre predeterminado de la base de datos cuando la conexión
no proporciona ningún nombre. Este atributo es opcional, pero, si lo utiliza, debe aplicar la propiedad
Name, como se indica en la tabla siguiente.
Name Cadena Vea Cuando se usa con su propiedad Name, especifica el nombre de
Name. la base de datos.
Atributo TableAttribute
Utilice este atributo para designar una clase como una clase de entidad que está asociada a una tabla o
vista de base de datos. LINQ a SQL trata las clases que tienen este atributo como clases persistentes. En
la tabla siguiente se describe la propiedad Name.
Name Cadena La misma cadena que Designa una clase como una clase de entidad que
el nombre de clase está asociada a una tabla de base de datos.
Atributo ColumnAttribute
Utilice este atributo para designar un miembro de una clase de entidad para que represente una columna
de una tabla de base de datos. Este atributo se puede aplicar cualquier campo o propiedad.
Sólo los miembros que identifique como columnas se recuperarán y conservarán cuando LINQ a SQL
guarde los cambios en la base de datos. Se supone que los miembros que no tienen este atributo no son
persistentes y no se envían para operaciones de inserción o actualización.
Atributo AssociationAttribute
Utilice este atributo para designar una propiedad que represente una asociación en la base de datos,
como una relación entre clave externa y clave principal.
Atributo InheritanceMappingAttribute
Utilice este atributo para asignar una jerarquía de herencia.
Atributo FunctionAttribute
Utilice este atributo para designar un método para representar un procedimiento almacenado o una
función definida por el usuario en la base de datos.
Atributo ParameterAttribute
Utilice este atributo para asignar parámetros de entrada en métodos de procedimiento almacenado.
Name Cadena La misma cadena que el nombre del Especifica un nombre para el
parámetro en la base de datos parámetro.
Atributo ResultTypeAttribute
Utilice este atributo para especificar un tipo de resultado.
Atributo DataAttribute
Utilice este atributo para especificar nombres y campos de almacenamiento privados.
1. El Extractor de DBML extrae información del esquema de la base de datos y vuelve a ensamblar
la información en un archivo DBML con formato XML.
Extractor de DBML
El Extractor de DBML es un componente de LINQ a SQL que acepta metadatos de una base de datos
como entrada y genera un archivo DBML como resultado. La siguiente ilustración muestra la secuencia
de operaciones.
Generador de código
El Generador de código es un componente de LINQ a SQL que traduce archivos DBML a Visual Basic, C#
o archivos de asignación XML. La siguiente ilustración muestra la secuencia de operaciones.
Distinga este archivo de definición de esquema del archivo de definición de esquema que se utiliza para
validar un archivo de asignación externa.
Nota:
Los usuarios de Visual Studio también encontrarán este archivo XSD en el cuadro de diálogo de
Esquemas XML como "DbmlSchema.xsd".
Puede separar el código de la asignación del código de la aplicación. Este enfoque reduce el
desorden en el código de la aplicación.
Requisitos
El archivo de asignación debe ser un archivo XML, y se debe validar contra un archivo de definición de
esquema (.xsd) de LINQ a SQL.
El archivo de asignación XML debe ser válido según el archivo de definición de esquema XML.
LINQ a SQL no admite el uso híbrido de los dos enfoques de asignación (basado en atributos y
externo).
La asignación externa puede ser específica de un proveedor de base de datos. Puede asignar la
misma clase utilizando asignaciones externas independientes para proveedores independientes.
Ésta es una característica que la asignación basada en atributos no admite.
Distinga este archivo de definición de esquema del archivo de definición de esquema que se utiliza para
validar un archivo DBML.
Nota:
Los usuarios de Visual Studio también encontrarán este archivo XSD en el cuadro de diálogo
Esquemas XML como "LinqToSqlMapping.xsd".
No se puede conectar
P. No puedo conectarme a mi base de datos.
R. Asegúrese de que su cadena de conexión es correcta y que su instancia de SQL Server se está
ejecutando. También tenga en cuenta que LINQ a SQL requiere que el protocolo Canalizaciones con
nombre esté habilitado.
R. Asegúrese de que llama a SubmitChanges para guardar los resultados en la base de datos.
R. Normalmente, una conexión permanece abierta hasta que se utilizan los resultados de la consulta. Si
espera que los resultados tarden tiempo en procesarse, y no se opone a que se almacenen en memoria
caché, aplique ToList<(Of <(TSource>)>) a la consulta. En escenarios habituales donde cada objeto se
procesa sólo una vez, el modelo de transmisión por secuencias es superior tanto en DataReader como
en LINQ a SQL.
R. Aunque LINQ a SQL no posee comandos de actualización basados en conjuntos, puede utilizar
cualquiera de las técnicas siguientes para actualizar sin consultar primero:
Cree una nueva instancia del objeto e inicialice todos los valores (campos) actuales que afectan
a la actualización. A continuación, asocie el objeto al DataContext utilizando Attach y modifique
el campo que desee cambiar.
R. LINQ a SQL proporciona varias herramientas para inspeccionar el código SQL que genera. Una de las
más importantes es Log.
R. LINQ a SQL proporciona dos maneras de devolver los valores generados por la base de datos a través
de procedimientos almacenados:
El siguiente es un ejemplo de resultado incorrecto. Dado que LINQ a SQL no puede asignar los
resultados, siempre devuelve 0:
Evitar el establecimiento explícito de valores generados por la base de datos al insertar o actualizar
P. Tengo una tabla de base de datos con una columna DateCreated que tiene como valor predeterminado
Getdate() de SQL. Cuando intento insertar un nuevo registro utilizando LINQ a SQL, el valor queda
establecido en NULL. Lo que esperaba es que tomara el valor predeterminado de la base de datos.
P. LINQ a SQL administra automáticamente esta situación para la identidad (incremento automático) y
rowguidcol (GUID generado por base de datos) y para las columnas con marca de tiempo. En otros
casos, debería establecer manualmente IsDbGenerated=true y AutoSync=Always/OnInsert/OnUpdate.
Múltiples DataLoadOptions
P. ¿Puedo especificar opciones de carga adicionales sin sobrescribir la primera?
R. El Diseñador relacional de objetos no admite SQL Server Compact 3.5, aunque el motor de ejecución
de LINQ a SQL sí lo hace. En esta situación, debe crear sus propias clases de entidad y agregar los
atributos adecuados.
R. Crear una relación no es suficiente. Debe proporcionar información tal como la columna de
discriminador, el valor de discriminador de la clase base y el valor de discriminador de la clase derivada.
Modelo de proveedor
P. ¿Existe un modelo de proveedor público disponible?
R. No existe ningún modelo de proveedor público disponible. En este momento, LINQ a SQL sólo admite
SQL Server y SQL Server Compact 3.5.
R. La inserción de SQL ha sido un riesgo significativo para las consultas SQL tradicionales formadas
mediante concatenación de los datos proporcionados por el usuario. LINQ a SQL evita esa inserción
mediante el uso de SqlParameter en las consultas. Los datos proporcionados por el usuario se convierten
en valores de parámetro. Este enfoque impide que se utilicen comandos malintencionados en los datos
proporcionados por el cliente.
2. Agregue una clase parcial. Cree un constructor con parámetros para los miembros de sólo
lectura.
Precaución:
Si está utilizando el Diseñador relacional de objetos en Visual Studio, sus cambios podrían resultar
sobrescritos.
APTCA
P. ¿Está System.Data.Linq marcado para que el código de confianza parcial pueda utilizarlo?
El principal escenario de LINQ a SQL para permitir llamadores que no son de plena confianza es hacer
que se pueda obtener acceso al ensamblado LINQ a SQL desde aplicaciones web, cuya configuración de
confianza es Media.
R. Puede crear una vista en una base de datos y asignar la entidad a la vista. LINQ a SQL genera el
mismo código SQL para vistas que para tablas.
Nota:
El uso de vistas en este escenario presenta limitaciones. Este enfoque funciona de forma más segura
cuando las operaciones realizadas sobre Table<(Of <(TEntity>)>) se admiten en la vista subyacente.
Sólo usted puede saber qué operaciones son las deseadas. Por ejemplo, la mayoría de las
aplicaciones son de sólo lectura, y otro número considerable realiza operaciones
Agrupar conexiones
P. ¿Existe una construcción que pueda ayudar con el agrupamiento de DataContext?
R. No intente reutilizar instancias de DataContext. Cada DataContext mantiene el estado (incluida una
caché de identidad) para una sesión de edición o consulta particular. Para obtener nuevas instancias
según el estado actual de la base de datos, utilice un nuevo DataContext.
R. Este comportamiento está diseñado así. LINQ a SQL continúa devolviendo los mismos valores o
instancias que aparecen en la primera instancia. Cuando se realizan actualizaciones, se utiliza
simultaneidad optimista. Los datos originales se utilizan para realizar una comprobación contra el estado
de la base de datos actual a fin de comprobar que, de hecho, permanecen sin modificar. Si han
cambiado, se produce un conflicto, y su aplicación deberá resolverlo. Una opción para su aplicación es
restablecer el estado original al estado actual de la base de datos e intentar de nuevo la actualización.
R. El modo de sólo lectura desactiva la capacidad del contexto de realizar seguimiento de cambios.
En este tema se describen las diferencias fundamentales en cuanto a uso, configuración, conjuntos de
características y ámbito de compatibilidad con LINQ a SQL.
SQL Server Compact 3.5 se empaqueta como una DLL que se puede utilizar directamente en
archivos de base de datos (extensión .sdf).
SQL Server Compact 3.5 se ejecuta en el mismo proceso que la aplicación cliente. Por lo tanto,
la eficacia de la comunicación con SQL Server Compact 3.5 puede ser significativamente mayor
que con SQL Server. Por otro lado, SQL Server Compact 3.5 requiere interoperabilidad entre el
código administrado y no administrado, con el costo que ello implica.
El tamaño de la DLL de SQL Server Compact 3.5 es pequeño. Esta característica reduce el
tamaño total de la aplicación.
Conjunto de características
El conjunto de características de SQL Server Compact 3.5 es mucho más sencillo que el de SQL Server
en los aspectos siguientes, que pueden afectar a las aplicaciones LINQ a SQL:
SQL Server Compact 3.5 no admite las vistas ni los procedimientos almacenados.
SQL Server Compact 3.5 admite sólo un subconjunto de tipos de datos y funciones de SQL.
SQL Server Compact 3.5 proporciona sólo un optimizador mínimo. Es posible que algunas
consultas agoten el tiempo de espera.
Los operadores de consulta estándar se definen en secuencias. Una secuencia se ordena y se basa en la
identidad de referencia de cada elemento de la secuencia.
SQL trata principalmente con conjuntos de valores no ordenados. La ordenación es normalmente una
operación de procesamiento posterior declarada de forma explícita que se aplica al resultado final de una
consulta en lugar de a los resultados intermedios. La identidad se define con valores. Por esta razón se
entiende que las consultas SQL tratan con conjuntos múltiples (bolsas) en lugar de conjuntos.
En los párrafos siguientes se describen las diferencias entre los operadores de consulta estándar y su
conversión a SQL para el proveedor de SQL Server de LINQ a SQL.
El último paso es ordenar en SQL antes de que se generen los resultados. Concat<(Of <(TSource>)>) no
conserva el orden de sus argumentos. Para garantizar una ordenación correcta, debe ordenar
explícitamente los resultados de Concat<(Of <(TSource>)>).
El método Union se define para los conjuntos múltiples como la concatenación no ordenada de los
conjuntos múltiples (de hecho, el resultado de la cláusula UNION ALL en SQL).
Take, Skip
Los métodos Take<(Of <(TSource>)>) y Skip<(Of <(TSource>)>) están bien definidos sólo en
conjuntos ordenados. La semántica para los conjuntos no ordenados o conjuntos múltiples no está
definida.
Nota:
Debido a las limitaciones de la ordenación en SQL, LINQ a SQL intenta trasladar la ordenación del
argumento de estos métodos al resultado del método. Por ejemplo, considere la siguiente consulta LINQ
a SQL:
Dim custQuery = From cust In db.Customers Where cust.City = "London"
Order By cust.CustomerID Select cust Skip 1 Take 1
El SQL generado para este código traslada la ordenación al final, como se observa a continuación:
SELECT TOP 1 [t0].[CustomerID], [t0].[CompanyName],
FROM [Customers] AS [t0]
WHERE (NOT (EXISTS(
SELECT NULL AS [EMPTY]
FROM (
SELECT TOP 1 [t1].[CustomerID]
FROM [Customers] AS [t1]
WHERE [t1].[City] = @p0
ORDER BY [t1].[CustomerID]
) AS [t2]
WHERE [t0].[CustomerID] = [t2].[CustomerID]
))) AND ([t0].[City] = @p1)
ORDER BY [t0].[CustomerID]
Parece obvio que toda la ordenación especificada debe ser coherente cuando se encadenan Take<(Of
<(TSource>)>) y Skip<(Of <(TSource>)>). De lo contrario, los resultados no están definidos.
Tanto Take<(Of <(TSource>)>) como Skip<(Of <(TSource>)>) están bien definidos para los
argumentos integrales de constante no negativos basados en la especificación de operadores de consulta
estándar.
Operadores Razonamiento
TakeWhile, SkipWhile Las consultas SQL funcionan con conjuntos múltiples, no con
secuencias. ORDER BY debe ser la última cláusula aplicada a
los resultados. Por esta razón, no hay ninguna conversión
general para estos dos métodos.
Conversión de expresiones
Semántica de valores null
LINQ a SQL no impone las semántica de comparación de valores null en SQL. Los operadores de
comparación se convierten sintácticamente en sus equivalentes SQL. Por esta razón, la semántica refleja
la semántica de SQL definida según la configuración del servidor o la conexión. Por ejemplo, dos valores
nulos se consideran distintos según la configuración predeterminada de SQL Server, pero puede cambiar
la configuración para cambiar la semántica. LINQ a SQL no tiene en cuenta la configuración del servidor
cuando convierte las consultas.
Una comparación con el literal null se convierte a la versión de SQL correcta (is null o is not null).
SQL Server define el valor null en la intercalación. LINQ a SQL no cambia la intercalación.
Agregados
El método de agregado del operador de consulta estándar Sum se evalúa como cero para una secuencia
vacía o para una secuencia que sólo contiene valores nulos. En LINQ a SQL, la semántica de SQL se
mantiene invariable y Sum se evalúa como null en lugar de cero para una secuencia vacía o para una
secuencia que sólo contiene valores nulos.
En LINQ a SQL se aplican las restricciones de SQL para los agregados en los resultados intermedios. Sum
para cantidades enteras de 32 bits no se calcula utilizando los resultados de 64 bits. Puede producirse un
desbordamiento en la conversión de Sum que realiza LINQ a SQL aun cuando la implementación del
operador de consulta estándar no produce un desbordamiento para la secuencia en memoria
correspondiente.
De igual forma, la conversión que realiza LINQ a SQL de Average de valores enteros se calcula como
integer, no como double.
Argumentos de entidad
LINQ a SQL permite utilizar los tipos de entidad en los métodos GroupBy y OrderBy. En la conversión de
estos operadores, el uso de un argumento de un tipo se considera equivalente a especificar todos los
miembros de ese tipo. Por ejemplo, el código siguiente es equivalente:
db.Customers.GroupBy(Function(c) c)
db.Customers.GroupBy(Function(c) New With {c.CustomerID, _
c.ContactName})
Argumentos equivalentes o comparables
La igualdad de los argumentos se requiere en la implementación de los métodos siguientes:
Contains
Skip<(Of <(TSource>)>)
Union
Intersect
Except
LINQ a SQL admite la igualdad y comparación para los argumentos planos, pero no para los argumentos
que son secuencias o las contienen. Un argumento plano es un tipo que asignarse a una fila de SQL. Una
proyección de uno o más tipos de entidad que se pueden determinar estáticamente que no contiene una
secuencia se considera un argumento plano.
Compatibilidad de la herencia
Herencia en consultas
Las conversión de tipos de C# sólo se admite en la proyección. Las conversiones de tipos que se utilizan
en otra parte no se convierten y se omiten. Además de los nombres de función de SQL, SQL realmente
sólo realiza la operación equivalente a Convert de Common Language Runtime (CLR). Es decir, SQL
puede cambiar el valor de un tipo a otro. No hay ningún equivalente de conversión de tipos de CLR
porque no existe el concepto de reinterpretar los mismos bits que los de otro tipo. Por esa razón una
conversión de tipos de C# sólo funciona localmente. No es remota.
Los operadores, is y as, y el método GetType no están limitados al operador Select. También se pueden
utilizar en otros operadores de consulta.
Cross Apply y Outer Apply se generan para la navegación de relaciones. El conjunto de consultas para
el que son posibles tales operaciones de reescritura no está bien definido. Por esta razón, el conjunto
mínimo de consultas que se admite para SQL Server 2000 es el conjunto que no implica la navegación de
relaciones.
text / ntext
Los tipos de datos text / ntext no se pueden utilizar en ciertas operaciones de consulta con
varchar(max) / nvarchar(max), que son admitidos por Microsoft SQL Server 2005.
Esta limitación no tiene ninguna resolución. Concretamente, no puede utilizar Distinct() en ningún
resultado que contenga miembros que se asignen a columnas text o ntext.
Materialización de objetos
La materialización crea objetos CLR a partir de las filas devueltas por una o más consultas SQL.
Las llamadas siguientes se ejecutan localmente como parte de la materialización:
Constructores
Métodos ToString en las proyecciones
Conversiones de tipos en las proyecciones
Puede utilizar struct como tipo de valor devuelto en el resultado de una consulta o como un
miembro del tipo de resultado. Las entidades deben ser clases. Los tipos anónimos se
materializan como instancias de clase, pero las estructuras con nombre (no entidades) se
pueden utilizar en la proyección.
Un miembro del tipo de valor devuelto en el resultado de una consulta puede ser de tipo
IQueryable<(Of <(T>)>). Se materializa como una colección local.