Herramientas de usuario

Herramientas del sitio


fw:othersnet:linq

¡Esta es una revisión vieja del documento!


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<Person> 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<Person> list = from p in lPerson select p;

Filtro y ordenación

También podemos agregar clausulas where y orderby:

IEnumerable<Person> 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<string> 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<string> 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<AlternatePerson> 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<T> 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<T>, entonces, la consulta nunca es ejecutada. Pero si lo que queremos es ejecutar inmediatamente la consulta (utilizando toList o toArray) haremos:

List<Category> 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<int> 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.
fw/othersnet/linq.1280430564.txt.gz · Última modificación: 2020/05/09 09:24 (editor externo)