====== LINQ ======
===== Sintaxis =====
En ''System.Linq'' existen una serie de métodos de extensión que permiten hacer consultas sobre datos (sobre xml, bases de datos...). \\ \\
Los siguientes son ejemplos de consultas sobre elementos en memoria:
IEnumerable list = lPerson.Where(p => p.firstName.StartsWith("A"));
int maxAge = lPerson.Max(p => p.age);
double avg = lPerson.Average(p => p.age);
Pero en LINQ existen lo que serían las sintaxis de expresiones para realizar este tipo de consultas. Estas son parecidas a las sentencias sql de toda la vida y empiezan siempre por **from** y acaban por **select** siguiendo una estructura como la que sigue:
from elementos_a_consultar in lista select elementos_devueltos
Un ejemplo:
IEnumerable list = from p in lPerson select p;
===== Filtro y ordenación =====
También podemos agregar clausulas where y orderby:
IEnumerable list = from p
in lPerson
where p.firstName.StartsWith("A")
orderby p.age
select p;
En el código de arriba el trozo //from p in lPerson// indica lanzar una consulta LINQ sobre la colección ''lPerson'', y que usaremos el parámetro ''p'' para representar a cada elemento que consultamos. En realidad, el nombre del parámetro ''p'' es irrelevante - podría haberlo llamado ''o'', ''x'', ''person'' o de cualquier otra forma. El trozo //select p// nos indica que queremos devolver una secuencia IEnumerable de objetos Person como resultado de la consulta. Esto esto pasa porque la colección ''lPerson'' contiene objetos de tipo ''Person''. Pero si quisieramos devolver los nombres sería algo así:
IEnumerable list = from p
in lPerson
where p.firstName.StartsWith("A")
orderby p.age
select p.firstName;
También podemos consultar los elementos relacionados o ordendar descendentemente:
IEnumerable categories = from c in Categories
where c.Products.Count > 5
orderby c.CategoryName descending
select c.CategoryName;
Otro ejemplo sería el de devolver un objeto de una clase que no es la de los elementos de la lista:
class AlternatePerson {
string nameAge { get; set; }
}
...
IEnumerable friends = from p in lPeople
where p.LastName.StartsWith("G")
select new AlternatePerson {
nameAge = p.FirstName + p.Age;
}
===== Momento de la consulta =====
Una de las principales características de la interface IEnumerable es que los objetos que las implementen pueden retrasar su ejecución hasta el momento en que se introduzca en un bucle (esto es posible gracias al ''yield''). LINQ usa esta ventaja, retrasando las consultas hasta la primera vez que se itere sobre el resultado. Si nunca se itera sobre una IEnumerable, entonces, la consulta nunca es ejecutada. Pero si lo que queremos es ejecutar inmediatamente la consulta (utilizando toList o toArray) haremos:
List categories = (from c in db.Categories
where c.CategoryName
select c).ToList();
Si luego cambiasemos los datos de la lista inicial y volviesemos a iterarla, se volvería a ejecutar la query. Por ejemplo, en la siguiente aparece Juanjo en la segunda iteración:
var list = from l in lPerson select l;
foreach (Person p in list)
Console.WriteLine(p.firstName);
lPerson.Add(new Person() { firstName = "Juanjo", age = 32 });
foreach (Person p in list)
Console.WriteLine(p.firstName);
===== Ejemplos =====
==== Sencillos ====
=== Where + and ===
var expensiveInStockProducts =
from p in products
where p.UnitsInStock > 0 && p.UnitPrice > 3.00M
select p;
=== Buscar las palabras que tengan más letras que su valor ===
string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
var shortDigits = digits.Where((digit, index) => digit.Length < index);
=== Cambiando el valor ===
int[] numbers = { 5, 4, 1};
var numsPlusOne =
from n in numbers
select n + 1;
Devolvería 6 5 2
=== Combinar dos arrays ===
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
string[] strings = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
var textNums =
from n in numbers
select strings[n];
Devolvería five, four, one...
==== Grouping ====
Coger una agrupación y el tipo de agrupación que es:
var wordGroups =
from w in words
group w by w[0] into g
select new { FirstLetter = g.Key, Words = g };
=== Anidación ===
var customerOrderGroups =
from c in customers
select
new {c.CompanyName,
YearGroups =
from o in c.Orders
group o by o.OrderDate.Year into yg
select
new {Year = yg.Key,
MonthGroups =
from o in yg
group o by o.OrderDate.Month into mg
select new { Month = mg.Key, Orders = mg }
}
};
==== Tipado anónimo ====
string[] words = { "aPPLE", "BlUeBeRrY", "cHeRry" };
var upperLowerWords =
from w in words
select new {Upper = w.ToUpper(), Lower = w.ToLower()};
foreach (var ul in upperLowerWords) {
Console.WriteLine("Uppercase: {0}, Lowercase: {1}", ul.Upper, ul.Lower);
Otro...
var productInfos =
from p in products
select new {p.ProductName, p.Category, Price = p.UnitPrice};
foreach (var productInfo in productInfos) {
Console.WriteLine("{0} is in the category {1} and costs {2} per unit.", productInfo.ProductName, productInfo.Category, productInfo.Price);
Otro...
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var numsInPlace = numbers.Select((num, index) => new {Num = num, InPlace = (num == index)});
Otro... con asignación
var orders =
from c in customers,
o in c.Orders,
total = o.Total
where total >= 2000.0M
select new {c.CustomerID, o.OrderID, total};
==== Relación de tablas ====
var orders =
from c in customers
where c.Region == "WA"
from o in c.Orders
where o.OrderDate >= cutoffDate
select new {c.CustomerID, o.OrderID};
==== Ordenación ====
=== Orderby ===
var sortedWords =
from w in words
orderby w.Length
select w;
=== Orderby Descending ===
var sortedWords =
from w in words
orderby w.Length descending
select w;
=== Cambiar el orden ===
var reversedIDigits = (
from d in digits
where d[1] == 'i'
select d)
.Reverse();
==== Contar elementos ====
=== Count ===
int[] factors = { 2, 2, 3, 5, 5 };
int uniqueFactors = factors.Distinct().Count();
Devolvería 3
=== Contar los impares ===
int oddNumbers = numbers.Count(n => n % 2 == 1);
=== Contar los carácteres de todas las palabras ===
string[] words = { "cherry", "apple", "blueberry" };
double totalChars = words.Sum(w => w.Length);
==== Concatenación de resultados ====
var customerNames =
from c in customers
select c.CompanyName;
var productNames =
from p in products
select p.ProductName;
var allNames = customerNames.Concat(productNames);
==== Selección de elementos ====
=== El primero ===
Product product12 = (
from p in products
where p.ProductID == 12
select p )
.First();
=== El cuarto ===
int fourthLowNum = (
from n in numbers
where n < 5
select n )
.ElementAt(3);
===== Nuevas funciones =====
==== Range ====
Generación de los números de 100 a 150:
from n in Sequence.Range(100, 50)
==== Repeat ====
Repetición del número 7, 10 veces:
var numbers = Sequence.Repeat(7, 10);
==== Distinct ====
Coge sólo uno, esquivando los elementos duplicados
var categoryNames = (
from p in products
select p.Category)
.Distinct();
==== Union ====
Une dos listas del mismo tipo
var uniqueNumbers = numbersA.Union(numbersB);
==== Intersect ====
Coge los elementos que se repiten en las dos listas
var commonNumbers = numbersA.Intersect(numbersB);
==== Except ====
Coge los elementos de la primera lista que no esten en la segunda
IEnumerable aOnlyNumbers = numbersA.Except(numbersB);
==== Take ====
Coge los primeros valores
var first3Numbers = numbers.Take(3);
var first3WAOrders = (
from c in customers
from o in c.Orders
where c.Region == "WA"
select new {c.CustomerID, o.OrderID, o.OrderDate} )
.Take(3);
==== Skip ====
Coge los últimos números saltandose los indicados
var allButFirst4Numbers = numbers.Skip(4);
=== Combinación de skip y take ===
query.Skip(30).Take(10)
===== Otros =====
==== let ====
La clausula ''let'' permite crear una variable dentro del ámbito de la query y utilizarla en la condición.
var lArrivals = from a in db.PIU_ARRIVALS
let t = (DateTime.Now - a.updatedLocalTime)
where (t.Seconds > seconds)
select a;
... O ...
from car in root.Elements("car")
let profiles =
from profile in car.Elements("profile")
select new {
Name = profile.Attribute("name").Value,
Value = profile.Attribute("value").Value
}
let supports =
from support in car.Elements("support")
select new {
Name = support.Attribute("name").Value,
Value = support.Attribute("value").Value
}
select new Car {
Name = car.Attribute("name").Value,
Vendor = profiles.Single(prof => prof.Name == "Vendor").Value,
Model = profiles.Single(prof => prof.Name == "Model").Value,
Doors = int.Parse(profiles.Single(prof => prof.Name == "Doors").Value),
RacingSupport = supports.Single(sup => sup.Name == "Racing").Value == "yes"
};
===== Ejemplos =====
=== Ejemplo 1 ===
SQL:
SELECT SAE_LINEASPIENPANELINFO.INUMEROORDEN, SAE_LINEASPIENPANELINFO.SNOMBRELINEA, SAE_LINEASPIENPANELINFO.SNOMBREDESTINO,
SAE_LINEASPIENPANELINFO.IIDLINEA, SAE_LINEASPIENPANELINFO.IIDTRAYECTO, SAE_LINEASPIENPANELINFO.IIDPARADA
FROM SAE_LINEASPIENPANELINFO, SAE_CONFBIENPROGRAMADAS
WHERE SAE_LINEASPIENPANELINFO.IIDPARADA = {0} AND SAE_LINEASPIENPANELINFO.IIDPANELINFO = {1}
AND SAE_CONFBIENPROGRAMADAS.IIDCONFLINEASACTUAL = SAE_LINEASPIENPANELINFO.IIDCONFIGURACIONLINEASPI
AND SAE_CONFBIENPROGRAMADAS.IIDPANELINFO = {1}
ORDER BY SAE_LINEASPIENPANELINFO.INUMEROORDEN
LINQ:
var dades = from linies in dbdc.SAE_LINEASPIENPANELINFO
from conf in dbdc.SAE_CONFBIENPROGRAMADAS
orderby linies.INUMEROORDEN
where ((linies.IIDPARADA == p.IIDPARADA) &&
(linies.IIDPANELINFO == codi) &&
(conf.IIDCONFLINEASACTUAL == linies.IIDCONFIGURACIONLINEASPI) &&
(conf.IIDPANELINFO == codi))
select new
{
INUMEROORDEN = linies.INUMEROORDEN,
SNOMBRELINEA = linies.SNOMBRELINEA,
SNOMBREDESTINO = linies.SNOMBREDESTINO,
IIDLINEA = linies.IIDLINEA,
IIDTRAYECTO = linies.IIDTRAYECTO,
IIDPARADA = linies.IIDPARADA
};
=== Ejemplo 2 (combinación) ===
Esto...
var id_paradas = (from pt in db.SAE_PARADASTRAYECTO
where (pt.IIDLINEA == idLinia)
select pt.IIDPARADA.GetValueOrDefault(-1)).Distinct();
return (from p in db.SAE_PARADAS
from id in id_paradas
where (p.IIDPARADA == id)
select p).ToList();
Es igual a...
return (from p in db.SAE_PARADAS
from id in ((from pt in db.SAE_PARADASTRAYECTO
where (pt.IIDLINEA == idLinia)
select pt.IIDPARADA.GetValueOrDefault(-1)).Distinct())
where (p.IIDPARADA == id)
select p).ToList();
=== Ejemplo 3 (NOT IN) ===
var query =
from c in dc.Customers
where !(from o in dc.Orders
select o.CustomerID)
.Contains(c.CustomerID)
select c;
foreach (var c in query) Console.WriteLine( c );
=== Ejemplo 4 (FindAll y ForEach) ===
static void DisplayInstalledApplications2()
{
string registryKey =
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using (Microsoft.Win32.RegistryKey key =
Registry.LocalMachine.OpenSubKey(registryKey))
{
(from a in key.GetSubKeyNames()
let r = key.OpenSubKey(a)
select new
{
Application = r.GetValue("DisplayName")
}).ToList()
.FindAll(c => c.Application != null)
.ForEach(c => Console.WriteLine(c.Application));
}
}
=== Cálculo del tamaño de un directorio ===
static long DirectorySize(DirectoryInfo dInfo, bool includeSubDir) {
// Enumerate all the files
long totalSize = dInfo.EnumerateFiles()
.Sum(file => file.Length);
// If Subdirectories are to be included
if (includeSubDir)
{
// Enumerate all sub-directories
totalSize += dInfo.EnumerateDirectories()
.Sum(dir => DirectorySize(dir, true));
}
return totalSize;
}
===== Notas =====
* En las querys LINQ podemos ver el código SQL generado; si se debuga se puede ver el comando sql en la variable a la que se le ha asignado dicha query.
* El resultado de una sentencia LINQ puede ser asignada a un ''DataSource'' de un ''GridView''. Luego tendremos que llamar al método ''DataBind'' para que se muestren.