Curso Linq
Curso Linq
Curso Linq
implemente interfaces como IEnumerable en cuyo interior exista una coleccin que es la
que realmente realiza el trabajo.
En cualquier caso, es un trabajazo, puesto que por cada clase que queramos contemplar
deberamos crear una clase especfica, tal y como se describe en el prrafo anterior.
Y aqu es donde los generics entran en escena. El siguiente cdigo declara una lista de
elementos de tipo AlgoComplejo:
List<AlgoComplejo> lista = new List<AlgoComplejo>();
AlgoComplejo algo = new AlgoComplejo();
lista.Add(algo);
lista.Add(new AlgoComplejo());
lista.Add("blah"); // Error en compilacin!
Con esta declaracin, no ser posible aadir a la lista objetos que no sean de la clase
indicada, ni tampoco ser necesario realizar un cast al obtener los elementos, pues sern
directamente de ese tipo.
Es interesante ver la gran cantidad de clases genricas para el tratamiento de colecciones
que se incorporaron con la versin 2.0 del framework en el espacio de nombres
System.Collections.Generic, y su amplia utilizacin a lo largo del marco de trabajo.
}
}
Como podemos ver, en la propia definicin de la clase indicamos que sta ser una plantilla
que recibir como parmetro genrico un tipo al que hemos llamado T en el ejemplo
anterior.
Se puede apreciar cmo hemos declarado un miembro privado llamado objeto, de tipo T, al
que se puede acceder a travs del correspondiente getter y setter. El tipo concreto sobre el
que actuaremos se definir en el momento de la instanciacin de un objeto, puesto que
habr que indicarlo expresamente:
// Crea un custodiador de strings...
CustodiadorDeObjetos<string> cs = new CustodiadorDeObjetos<string>();
// Creamos un custodiador de ints
CustodiadorDeObjetos<int> ci = new CustodiadorDeObjetos<int>();
// Creamos un custodiador de Cosas
CustodiadorDeObjetos<Cosa> cp = new CustodiadorDeObjetos<Cosa>();
De esta forma, evitamos tener que crear clases especficas para cada tipo de datos con el
que necesitemos trabajar, ahorrndonos muchsimo esfuerzo y manteniendo todas las
ventajas que el tipado fuerte nos ofrece.
Y para que os hagis una idea del potencial de esta tcnica, pensad que si no existiera la
genericidad (como ocurra en versiones anteriores del lenguaje), nos veramos obligados a
implementar clases especficas como las siguientes:
public class CustodiadorDeStrings
{
private string objeto;
public string Objeto
{
get { return objeto; }
set { this.objeto = value; }
}
}
public class CustodiadorDeCosas
{
private Cosa objeto;
public Cosa Objeto
{
Se trata de una clase genrica abierta, cuya nica operacin (Mayor(...)) permite obtener el
objeto mayor de los dos que le pasemos como parmetros. El criterio comparativo lo
pondr la propia clase sobre la que se instancie la plantilla: los enteros sern segn su valor,
las cadenas segn su orden alfabtico, etc.
A continuacin creamos dos instancias partiendo de la plantilla anterior, y concretando el
generic a los tipos que nos hacen falta:
Seleccionador<int> selInt = new Seleccionador<int>();
Seleccionador<string> selStr = new Seleccionador<string>();
Estas dos instanciaciones son totalmente correctas, verdad? Si despus de ellas usamos el
siguiente cdigo:
Console.WriteLine(selInt.Mayor(3, 5));
Console.WriteLine(selStr.Mayor("X", "M"));
Obtendremos por consola un 5 y una X. Todo perfecto; aparece, para cada llamada, la
conversin a cadena del objeto mayor de los dos que le hayamos enviado como parmetros.
El problema aparece cuando instanciamos la clase genrica para un tipo que no implementa
IComparable, que se utiliza en el mtodo Mayor() para determinar el objeto mayor de
ambos. En este caso, se lanza una excepcin en ejecucin indicando que el cast hacia
IComparable no puede ser realizado, abortando el proceso. Por ejemplo:
public class MiClase // No es comparable
{
}
[...]
Seleccionador<MiClase> sel = new Seleccionador<MiClase>();
MiClase x1 = new MiClase();
MiClase x2 = new MiClase();
Console.WriteLine(selString.Mayor(x1, x2)); // Excepcin,
// no son
// comparables!
Una posible solucin sera, antes del cast a IComparable en el mtodo Mayor(), hacer una
comprobacin de tipos y realizar la conversin slo si es posible, pero, y qu hacemos en
caso contrario? devolver un nulo? La pregunta no creo que tenga una respuesta sencilla,
puesto que en cualquier caso, se estaran intentado comparar dos objetos que no pueden ser
comparados.
where T: new(), fuerza a que el tipo T disponga de un constructor pblico sin parmetros; es
til cuando desde dentro de algn mtodo de la clase se pretende instanciar un objeto del
mismo.
where T: nombredeclase, indica que el tipo T debe heredar o ser de dicha clase.
where T1: T2, indica que el argumento T1 debe ser igual o heredar del tipo, tambin
parmetro genrico de la clase, T2.
Estas restricciones pueden combinarse, de forma que si queremos que un parmetro genrico se
cia a varias de ellas, simplemente las separamos por coma en la clusula where:
Pensando en Generics
Dar el salto a los generics de .Net 2.0 supone un cambio en la forma de pensar. Como dicen
en ingls, un "mind shift". En este artculo voy a tratar de explicar los generics mediante
reglas que hagan fcil el proceso de detectar cuando se pueden usar y de qu manera. Si
bien es cierto que generics no es ya nada nuevo, an hay mucha gente que sigue con .Net
1.1. Ahora que estn trabajando en .Net 4.0, es buen momento para irse actualizando.
Me voy a basar en el cdigo fuente de mi propio frameworks DesktopRails y en ejemplos
del framework CSLA.
Qu son los Generics?
Generics es algo as como el equivalente en .Net a las templates de C++, con unas cuantas
diferencias. En realidad son ms potentes puesto que el compilador de C# es ms avanzado.
Cuando vemos un cdigo que usa los smbolos "<>" como el siguiente, sabemos que se
trata de generics:
1.
2.
myList.Add("this is a test");
3.
2.
myList.Add("this is a test");
3.
Si en lugar de declarar firstItem como string hubiesemos puesto int, el compilador hubiese
dado un error. Adems, VisualStudio y MonoDevelop (IDEs) son capaces de usar
autocompletado o intellisense para recordarnos que el tipo es string, con lo cual es posible
que nisiquiera lleguemos a cometer el error.
Forma de detectar el uso de generics: Cada vez que tenemos una coleccin y estamos
haciendo un typecast, podemos darnos cuenta de que se puede reemplazar ese cdigo por
una coleccin generica.
Generics para relaciones fuertemente tipadas
Ahora vamos a ver usos ms complejos de generics, aunque en realidad cuando se adquiere
esta forma de pensar, es fcil de escribir las clases genericas. Usemos como ejemplo una
clase Presenter, que se encarga de pintar vistas en la pantalla. Las vistas son de tipo
IMyView. La relacin entre Presenter y las instancias de IMyView es clara. Si no tuvisemos
generics el cdigo sera algo como esto:
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
get
13.
14.
return _bgcolor;
15.
16.
set
17.
18.
_bgcolor = value;
19.
20.
}
}
21. }
22.
23. public class Presenter
24. {
25.
26.
27.
28.
29.
get
30.
31.
return _view;
32.
33.
34.
}
}
35.
36.
37.
_view = view;
38.
39.
// business logic;
40. }
2.
3.
presenter.View.BackgroundColor = "black";
Si en un momento determinado vamos a usar una clase que implementa IMyView pero que
ademas aade algunas propiedades o funciones que no estn en la interfaz, entonces
necesitamos hacer typecast:
1.
2.
view.SpecialField = "whatever";
Puesto que presenter.View es del tipo IMyView, no podemos hilar ms fino que eso, y desde
que la jerarqua de clases crece empezamos a tener que hacer typecasts. La desventaja es la
misma que en el caso anterior, que el error slo aparece en tiempo de ejecucin. La
transformacin a generics es sencilla:
1.
2.
3.
4.
5.
where T: IMyView
{
private T _view;
6.
7.
public T View
8.
9.
get
10.
11.
return _view;
12.
13.
14.
15.
16.
17.
_view = view;
18.
19.
// business logic
20. }
21.
22. SomeView smView = new SomeView();
23. GPresenter<SomeView> gpresenter = new GPresenter<SomeView>(smView);
24. gpresenter.View.BackgroundColor = "black";
Esto no es ms que una extensin de lo anterior. Supongamos que la clase GPresenter tiene
un mtodo que realiza determinada operacin y como resultado devuelve un objeto.
Podemos saber que el objeto ser de tipo IMyView, y escribir la firma del mtodo as:
1.
Sin embargo, esto nos lleva denuevo al caso del typecast anterior. La solucion es la forma
generica:
1.
public T SomeOperation<T>()
Si este tipo es distinto del que usamos para View, podemos agregar otro tipo genrico a la
definicin de la clase:
1.
2.
3.
where T: IMyView
4.
where Y: IMyOtherInterface
5.
Ms que llamar a los parmetros genricos, T e Y, conviene ponerles nombre, e.j, View y
LoQueSea. La sintaxis de genrics no pone restricciones a los nombres que queramos poner
a los parmetros.
Generics para envolturas (wrappers)
Cmo hacer genricas clases cuyo cdigo fuente no tenemos?. Como ejemplo os propongo
el cdigo fuente del GenericComboBox para WPF de DesktopRails. El control ComboBox
de WPF tiene un campo Items que son los elementos que contiene el combo (dropdown o
desplegable, como le querais llamar). Este campo es una lista de System.Object para que se
pueda meter cualquier tipo de objeto y para que un combo pueda contener diferentes tipos
de objeto. Sin embargo yo necesitaba saber que en determinadas ocasiones los elementos
del combo eran todos del tipo string, o del tipo int para evitar errores en ejecucin. La
solucin es crear una nueva clase que envuelve a la que nos interesa. Dentro de dicha clase
estamos obligados a hacer typecast en algn momento, con lo que aqu no ganamos en
eficiencia, pero ganamos en deteccin de errores en tiempo de compilacin.
1.
2.
3.
4.
5.
// ...
6.
7.
8.
get
9.
10.
return (T)_myWrap.Items;
11.
12.
set
13.
14.
_myWrap.Items = value;
15.
16.
17. }
Una cosa que hay que tener clara es que cuando se usa un tipo genrico, no se est
heredando de l, simplemente se est diciendo al compilador que haga una seria de
sustituciones en la plantilla. Dada la clase MyGenericBase:
1.
2.
3.
Las instancias:
1.
2.
3.
4.
No son hijas de MyGenericBase, solo son sustituciones. Para que haya polimorfismo hay
que extender al definiar la clase:
1.
2.
3.
4.
Por tanto en diseos de clases, normalmente la clase genrica base suele implementar una
interfaz de modo que tanto su jerarqua como las instancias que usen esa plantilla
pertenezcan a la familia de la interfaz:
1.
2.
3.
4.
A primera vista parece una definicin recursiva, imposible de interpretar. Sin embargo el
compilador slo hace la sustitucin una vez y se usara as:
1.
2.
3.
1.
2.
3.
where C : AbstractController
4.
...
5.
6.
where C : AbstractController<V,C>
7.
where V : IView<C>
8.
Modo de uso:
1.
2.
3.
...
4.
5.
En este caso, una instancia de UsersListController no conoce con precision de que tipo es
su View(porque la clase tiene un campo View del tipo genrico IUsersListView), solo sabe
que es de la jerarquia IUsersListView. Sin embargo, la instancia de una clase que
implemente IUsersListView s que sabe con precisin cual es el tipo exacto de Controller
que tiene asociado, es una relacin fuertemente tipada en esta direccin.
Lo que estas clusulas "where" aseguran es que cuando al controlador X, se le pase una
vista Y, esa vista Y debe haber sido definida como vista que puede usar un controlador X.
As podemos descubrir errores como ste en tiempo de compilacin:
1.
2.
3.
...
4.
5.
Lo anterior no compilara. Es til darse cuenta de que la asociacin que se est haciendo es
incorrecta, en tiempo de compilacin y no que falle en tiempo de ejecucin.
Regla fcil:
Cuando vemos una declaracin que parece recursiva, donde la clusula "where" del
parmetro genrico es igual a la propia declaracin, eso es como decir, s mismo. La clase
est diciendo, yo misma. Entonces el modo de uso es repitiendo el nombre:
public class UsersListController : AbstractController<IUsersListView,
UsersListController>
A la hora de escribir una declaracin como AbstractController, sabremos que debemos usar
esta sintaxis de aspecto recursivo cuando queremos hacer referencia a la propia clase que
estamos definiendo. Esto ser cuando queramos hacer una referencia entre varios tipos
genricos y a uno de ellos se le dice que su relacin con otro es el otro mismo.
Otro ejemplo menos complicado contenido en DesktopRails es el AbstractData:
1.
2.
3.
4.
5.
6.
where C : AbstractController
7.
8.
9.
En esta definicin, se pasa el parmetro TBaseView slo para forzar que es padre de
TFinalView en la jerarqua. En verdad no se llega a usar TBaseView sino que slo se usa
para forzar una jerarqua. El sentido a esta relacin se le ve cuando vemos clases derivadas:
1.
2.
3.
where T : IUsersListView
4.
...
5.
6.
Al ver sta ltima clase de la jerarqua puedes entender que estamos forzando a que
TestUsersListView sea una vista que implementa IUsersListView.
La verdad es que cuando estas diseando las clases, inicialmente a uno no se le ocurre
escribir estas clusulas a no se que tengas muuucha experiencia escribiendo clases iguales.
Lo que suelo hacer, al menos yo que no tengo ms materia gris que la media, es ir
modificando las clusulas segn me va haciendo falta e ir dejando que el compilador me
ayude a ver si todo va bien. Suelo pensar en trminos de... "si alguien usa mi clase, quiero
que la use de esta manera, y quiero que si la usa de tal otra forma el compilador le de un
error y no le deje". Ms bin diseo al revs. Pienso en cmo quiero usar la clase y la voy
diseando. Al estilo TDD.
Existen ms clusulas "where" que se pueden especificar y que podeis leer en
documentacin. Por ejemplo si no sabes con exactitud la interfaz que quieres que
implemente el tipo T pero sabes que quieres que sea una clase y no un tipo bsico, puedes
poner esto:
1.
where T : class
Agradezco que las dudas o sugerencias sobre ste artculo se gestionen en forma de
comentarios en el mismo, as como los errores que podais encontrar en l. Espero que os
sea de utilidad.
Mtodos genricos en C#
Los mtodos genricos son interesantes herramientas que estn con nosotros desde los tiempos
del .NET Framework 2.0 y pueden resultarnos muy tiles de cara a la construccin de frameworks o
libreras reutilizables.
Podramos considerar que un mtodo genrico es a un mtodo tradicional lo que una clase genrica
a una tradicional; por tanto, se trata de un mecanismo de definicin de mtodos con tipos
parametrizados, que nos ofrece la potencia del tipado fuerte en sus parmetros y devoluciones aun
sin conocer los tipos concretos que utilizaremos al invocarlos.
Vamos a profundizar en el tema desarrollando un ejemplo, a travs del cual podremos comprender
por qu los mtodos genricos pueden sernos muy tiles para solucionar determinado tipo de
problemas, y describiremos ciertos aspectos, como las restricciones o la inferencia, que nos
ayudarn a sacarles mucho jugo.
Escenario de partida
Como sabemos, los mtodos tradicionales trabajan con parmetros y retornos fuertemente tipados,
es decir, en todo momento conocemos los tipos concretos de los argumentos que recibimos y de los
valores que devolvemos. Por ejemplo, en el siguiente cdigo, vemos que el mtodo Maximo, cuya
misin es obvia, recibe dos valores integer y retorna un valor del mismo tipo:
public int Maximo(int uno, int otro)
{
if (uno > otro) return uno;
return otro;
}
Hasta ah, todo correcto. Sin embargo, est claro que retornar el mximo de dos valores es una
operacin que podra ser aplicada a ms tipos, prcticamente a todos los que pudieran ser
comparados. Si quisiramos generalizar este mtodo y hacerlo accesible para otros tipos, se nos
podran ocurrir al menos dos formas de hacerlo.
La primera sera realizar un buen puado de sobrecargas del mtodo para intentar cubrir todos los
casos que se nos puedan dar:
public int Maximo(int uno, int otro) { ... }
public long Maximo(long uno, long otro) { ... }
public string Maximo(string uno, string otro) { ... }
public float Maximo(float uno, float otro) { ... }
// Hasta que te aburras...
Obviamente, sera un trabajo demasiado duro para nosotros, desarrolladores perezosos como somos.
Adems, segn Murphy, por ms sobrecargas que creramos seguro que siempre nos faltara al
menos una: justo la que vamos a necesitar ;-).
Otra posibilidad sera intentar generalizar utilizando las propiedades de la herencia. Es decir, si
asumimos que tanto los valores de entrada del mtodo como su retorno son del tipo base object,
aparentemente tendramos el tema resuelto. Lamentablemente, al finalizar nuestra implementacin
nos daramos cuenta de que no es posible hacer comparaciones entre dos object's, por lo que, o bien
incluimos en el cuerpo del mtodo cdigo para comprobar que ambos sean comparables
(consultando si implementan IComparable), o bien elevamos el listn de entrada a nuestro mtodo,
as:
public object Maximo(IComparable uno, object otro)
{
if (uno.CompareTo(otro) > 0) return uno;
return otro;
}
Pero efectivamente, como ya habris notado, esto tampoco sera una solucin vlida para nuestro
caso. En primer lugar, el hecho de que ambos parmetros sean object o IComparable no asegura en
ningn momento que sean del mismo tipo, por lo que podra invocar el mtodo envindole, por
ejemplo, un string y un int, lo que provocara un error en tiempo de ejecucin. Y aunque es cierto
que podramos incluir cdigo que comprobara que ambos tipos son compatibles, no tendrais la
sensacin de estar llevando a tiempo de ejecucin problemtica de tipado que bien podra
solucionarse en compilacin?
El mtodo genrico
Fijaos que lo que andamos buscando es simplemente alguna forma de representar en el cdigo una
idea conceptualmente tan sencilla como: "mi mtodo va a recibir dos objetos de un tipo cualquiera
T, que implemente IComparable, y va a retornar el que sea mayor de ellos". En este momento es
cuando los mtodos genricos acuden en nuestro auxilio, permitiendo definir ese concepto como
sigue:
public T Maximo<T>(T uno, T otro) where T: IComparable
{
if (uno.CompareTo(otro) > 0) return uno;
return otro;
}
En el cdigo anterior, podemos distinguir el parmetro genrico T encerrado entre ngulos "<" y
">", justo despus del nombre del mtodo y antes de comenzar a describir los parmetros. Es la
forma de indicar que Maximo es genrico y operar sobre un tipo cualquiera al que llamaremos T; lo
de usar esta letra es pura convencin, podramos llamarlo de cualquier forma (por ejemplo MiTipo
Maximo<MiTipo>(MiTipo uno, MiTipo otro)), aunque ceirse a las convenciones de codificacin es
normalmente una buena idea.
A continuacin, podemos observar que los dos parmetros de entrada son del tipo T, as como el
retorno de la funcin. Si no lo ves claro, sustituye mentalmente la letra T por int (por ejemplo) y
seguro que mejora la cosa.
Vamos a centrarnos ahora en la porcin final de la firma del mtodo anterior, donde encontramos el
cdigo where T: IComparable. Se trata de una restriccin mediante la cual estamos indicando al
compilador que el tipo T podr ser cualquiera, siempre que implementente el interfaz IComparable,
lo que nos permitir realizar la comparacin.
Existen varios tipos de restricciones que podemos utilizar para limitar los tipos permitidos para
nuestros mtodos parametrizables:
where T: new(), fuerza a que el tipo T disponga de un constructor pblico sin parmetros; es
til cuando desde dentro del mtodo se pretende instanciar un objeto del mismo.
where T: nombredeclase, indica que el argumento debe heredar o ser de dicho tipo.
where T1: T2, indica que el argumento T1 debe ser igual o heredar del tipo, tambin
En cualquier caso, las restricciones no son obligatorias. De hecho, slo debemos utilizarlas cuando
necesitemos restringir los tipos permitidos como parmetros genricos, como en el ejemplo del
mtodo Maximo<T>, donde es la nica forma que tenemos de asegurarnos que las instancias que nos
lleguen en los parmetros puedan ser comparables.
El compilador deduce el tipo del mtodo genrico a partir de los que estamos utilizando en la lista
de parmetros. Por ejemplo, en el primer caso, dado que los dos parmetros son string, puede llegar
a la conclusin de que el mtodo tiene una signatura equivalente a string Maximo(string, string), que
coincide con la definicin del genrico.
Si hay algo que veo que cuesta que los desarrolladores entiendan o mejor dicho confen es
en la inferencia de tipos que presenta .net Framework 3.5. Muchos creen que al usar la
palabra var, estn resucitando al costoso tipo Variant de Visual Basic 6.0 o creen que estn
declarando una variable del tipo Object y que en tiempo de ejecucin se resuelve el tipo al
que pertenece dicha variable como los hacen los lenguajes dinmicos.
Afortunadamente no es as, la resolucin del tipo al que pertenece una variable se resuelve
en tiempo de compilacin no en tiempo de ejecucin. Vamos a verlo con un ejemplo:
La inferencia de tipos es un proceso por el cual el compilador determina el tipo de una
variable local que ha sido declarada sin una declaracin explcita de su tipo. El tipo es
inferido a partir del valor inicial provisto a la variable. A continuacin he declarado una
variable llamada inferredType, cuyo tipo ser resuelto por el compilador C# teniendo en
cuenta por supuesto el contenido de la variable. Lo cual revela que para que el algoritmo de
inferencia de tipos funcione es necesaria una entrada, que es el contenido de la variable. Si
no inicializamos la variable a inferir tendremos un error de compilacin.
Este resultado sera el mismo que obtendramos si hubiramos escrito el siguiente cdigo:
Particularmente lo que sucede en este caso, es que el compilador ya infiri que el tipo de la variable
inferredType es System.Int32. Al asignarle contenido de tipo System.String tenemos un error de
conversin de tipos. Me parece importante destacar esta situacin ya que conozco a mucha gente
que pretende que el compilador analice todas las posibles expresiones en las que interviene la
variable y posteriormente generalice al tipo ms adecuado para la misma. Pues les tengo malas
noticias, el algoritmo de inferencia funciona a partir de la primera evaluacin eventual de una
expresin, y esto est bien que sea as ya que la funcin del compilador es resolver el tipo mediante
la reduccin de expresiones al tipo atmico ms implcito que le permita compilar. En el caso de
este ejemplo la expresin analizar es una constante numrica. Si se analizaran todas las expresiones
en las que interviene una variable para posteriormente generalizar al mejor tipo, el compilador
necesitara una instancia privada de SQL Server Analisys Services y tardara bastante tiempo en
generar un ejecutable, lo cual no es la idea.
Ahora si queremos ver el mismo ejemplo con una cadena de texto , el cdigo es el siguiente:
Al ejecutarlo obtenemos:
La inferencia en los tipos por valor generaliza al tipo ms implcito y optimizado del .net
Framework. Como el Framework optimiza la performance para tipos enteros de 32bit(System.Int32 y System.UInt32) un valor de 0,10 100 que perfectamente podra
inferirse como System.Byte se infiere como System.Int32. Incluso se recomienda usar los
tipos enteros para contadores (aunque contemos de 0 a 10) y variables enteras de acceso
frecuentes, ya que la performance en tiempo de ejecucin del tipo entero es preferible al
storage en RAM que ahorramos si declaramos variables como System.SByte, System.Byte
y System.Int16. De la misma manera, con valores de punto flotante si declaramos una
variable con un valor de 3.14 ser inferida al tipo System.Double y no como
System.Single(float) que perfectamente la puede contener. La razn es que las operaciones
con System.Double son optimizadas por hardware. Slo se infiere a un tipo no optimizado
por el Framework (como System.Int64 System.Decimal) si el valor de la variable est
fuera del rango de los tipos optimizados.
Si por ejemplo queremos que se infiera el valor 3.14 como float en vez de double, debemos
proporcionar cierta evidencia que ayude al compilador a inferirlo como float.
var inferredType= (float)3.14; // casting explcito
var inferredType = 3.14f; // notacin sufijo
Entonces, resumiento:
Que es?
Antes de nada, tipos annimos no es lo mismo que inferencia de datos.
Los tipos annimos difieren de la inferencia de datos. La inferencia se usa para crear tipos
de objetos ya existentes, y los tipos annimos, para crear objetos recortados, los creas al
vuelo.
Me explico, lo que puedes crear con los tipos annimos son objetos, de los cuales solo
puedes puedes definir las propiedades.
var persona = new { nombre="Eric", apellido="Torre", diaNacimiento=9};
Vamos a ello
Con el anterior cdigo, hemos creado un objeto con las propiedades de nombre(String),
apellido(String),diaNacimiento(Integer). Lo que hace internamente es lo siguiente:
El objeto se declarara as (internamente):
class f__AnonymousType0<T0, T1, T2>{
public T0 nombre;
public T1 apellido;
public T2 diaNacimiento;
}
Para comparar los objetos annimos, comprobara el contenido de las propiedades, se puede
usar Equals o == , la diferencia es que Equals comprueba tambin el tipo.
Annimos con Arrays
Por supuesto, una propiedad puede interesarnos que una propiedad sea un array, este
ejemplo son de un listado de lenguajes de programacin y otro parmetro, que es el da de
nacimiento:
var persona = new
{
nombre="for",
apellido="Code",
lenguajes= new[]{
new {nombre="Php"},
new {nombre=".Net"},
new {nombre="java"}
},
diaNacimiento=9
};
C#
Cat cat = new Cat { Age = 10, Name = "Fluffy" };
Los tipos annimos habilitan la clusula select de una expresin de consulta de LINQ para
transformar objetos de la secuencia original en objetos cuyo valor y forma pueden ser
distintos de los originales. Esto es muy til si solo desea almacenar una parte de la
informacin de cada objeto de una secuencia. En el ejemplo siguiente se supone que un
objeto de producto (p) contiene muchos campos y mtodos, y el usuario slo est
interesado en crear una secuencia de objetos que contenga el nombre del producto y el
precio por unidad.
C#
var productInfos =
from p in products
Cada objeto del nuevo tipo annimo tiene dos propiedades pblicas que reciben los mismos
nombres que las propiedades o campos del objeto original. Tambin puede cambiar el
nombre de un campo al crear un tipo annimo; en el ejemplo siguiente se cambia el nombre
del campo UnitPrice a Price.
select new {p.ProductName, Price = p.UnitPrice};
Inicializadores de coleccin
Los inicializadores de coleccin permiten especificar uno o ms inicializadores de elemento
cuando se inicializa una clase de coleccin que implementa IEnumerable. Los
inicializadores de elemento pueden ser un valor simple, una expresin o un inicializador de
objeto. Si se utiliza un inicializador de coleccin, no es necesario especificar varias
llamadas al mtodo Add de la clase en el cdigo fuente; el compilador agrega las llamadas.
En los ejemplos siguientes se muestran dos inicializadores de coleccin simples:
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
List<int> digits2 = new List<int> { 0 + 1, 12 % 3, MakeInt() };
Ejemplo
C#
// The following code consolidates examples from the topic.
class ObjInitializers
{
class Cat
{
// Auto-implemented properties.
public int Age { get; set; }
public string Name { get; set; }
}
static void Main()
{
Cat cat = new Cat { Age = 10, Name = "Fluffy" };
List<Cat> cats = new List<Cat>
{
new Cat(){ Name = "Sylvester", Age=8 },
new Cat(){ Name = "Whiskers", Age=2 },
new Cat(){ Name = "Sasha", Age=14 }
};
List<Cat> moreCats = new List<Cat>
{
new Cat(){ Name = "Furrytail", Age=5 },
new Cat(){ Name = "Peaches", Age=4 },
null
};
// Display results.
System.Console.WriteLine(cat.Name);
foreach (Cat c in cats)
System.Console.WriteLine(c.Name);
foreach (Cat c in moreCats)
if (c != null)
System.Console.WriteLine(c.Name);
else
System.Console.WriteLine("List element has null value.");
}
// Output:
//Fluffy
//Sylvester
//Whiskers
//Sasha
//Furrytail
//Peaches
//List element has null value.
}
Mtodos de extensin en C#
Los mtodos de extensin permiten agregar mtodos a los tipos existentes sin
necesidad de crear un nuevo tipo derivado y volver a compilar o sin necesidad de
modificar el tipo original. Los mtodos de extensin constituyen un tipo especial de mtodo
esttico, pero se les llama como si se tratasen de mtodos de instancia en el tipo extendido.
En el caso del cdigo de cliente escrito en C# y Visual Basic, no existe ninguna diferencia
aparente entre llamar a un mtodo de extensin y llamar a los mtodos realmente definidos
en un tipo. MSDN
Ciertamente esta es una descripcin bastante criptica de esta tcnica de desarrollo que se
puede utilizar para una mayor productividad y legibilidad de nuestro cdigo. Por ello quiero
escribir un artculo que, por medio de un ejemplo muy sencillo, pueda ser un paso inicial en
el conocimiento de esta herramienta de programacin.
Bsicamente, y para entendernos de una forma ms sencilla. Un mtodo extensor es una
forma de aadirle mtodos a una clase sin necesidad de hacerlo en la original o en alguna
instancia/copia de la misma. No es un reemplazo de la tcnica formal por medio de
herencia, si no una pequea trampa que nos permite obtener un resultado muy similar.
Preparando el proyecto
Actualmente estoy utilizando como editor principal el Visual Studio 11 Beta, que no cuesta
ni un real (pero lo costar). Pero si quisiera un producto gratuito para programar en C#
podra tambin utilizar el Visual Studio Express 11.
Para este artculo me creo un nuevo proyecto Web, de tipo MVC4 (que tambin es beta)
vaco. He elegido este tipo para no tener ningn tipo de ruido en el ejemplo que quiero
compartir. Por ello me voy a saltar la regla de hacer siempre test unitarios, y no voy a
construir el proyecto de testing.
A continuacin aado una pgina de clases en la carpeta de Modelo, llamada
Comprobaciones, en donde voy a insertar una clase que realice diversas comprobaciones
bsicas y en donde voy a definir mtodos extensores que me haga el cdigo ms legible.
El primer paso de mi ejemplo es enunciarlo. Describir la visin de lo que quiero hacer. Y
esto es realizar una clase que me compruebe si los datos de un objeto Persona son
correctos. La Persona va a tener dos propiedades: el nombre y la edad en aos. Y por ello
lo primero que voy a hacer es construir la estructura del objeto.
Ahora continuo creando una clase de comprobaciones en donde generar el mtodo de entrada que
se llamar esCorrecto, recibiendo como parmetro un objeto del tipo Persona. Como vers, la
primera validacin que debo realizar es que persona no sea un objeto nulo. Para ello hago algo tan
simple como retornar si el objeto no es nulo.
Extendiendo a Persona
Pero mejoremos la legibilidad del cdigo refactorizando con mtodos extensores y creemos un
nuevo mtodo para la clase Persona. Esto se hace de forma muy sencilla creando una nueva clase
esttica llamada extensores en donde voy a dar de alta un mtodo tambin esttico que tiene la
particularidad de extender la clase Persona de la siguiente forma.
Como ves en este ejemplo tan sencillo ganas un poquito de complejidad para ganar un poquito de
legibilidad, quedando nuestro mtodo de comprobaciones as.
}
}
Pero si lo piensas un poco, el saber si una clase es nula o no me puede venir muy bien en toda la
aplicacin en donde tendr muchos ms objetos a los que hacer la misma comprobacin. O sea que
con muy poco esfuerzo puedo extender el propio tipo object y as TODOS mis objetos sern
capaces de devolverme si son nulos o no. Incluyendo todas las clases, estructuras, enumeradores,
etc. Ya que en C# todo hereda de object.
As, con un solo mtodo extensor puedo comprobar si la edad o el nombre son nulos de una forma,
no solamente sencilla si no muy legible.
Pero vamos a darle una pequea vuelta de tuerca extendiendo otra vez a la clase Persona para
realizar la comprobacin de que tiene un nombre vlido. Como todos sabemos, en la ltima versin
del framework de .NET podemos hacer dos comprobaciones diferenciadas que nos digan si la
cadena de texto es nula, esta vaca o es un espacio en blanco. Por lo cual si quiero comprobar si
el nombre es vlido debo hacer dos llamadas diferenciadas.
Voala, la legibilidad de mi cdigo aumenta mucho y adems tengo encapsulado en un solo sitio la
lgica de comprobacin del campo. Es ms, as cumplo a rajatabla el concepto de que mi mtodo
no debe saber cmo se hacen las cosas, solamente esperar la respuesta adecuada; en este caso un
valor booleano.
Conclusiones
Con esta tcnica podemos obtener un cdigo ms legible y mantenible. Pero no es todo oro lo
que reluce y hay que tener los inconvenientes inherentes a esta tcnica.
Primero el cdigo es dependiente del intellisense del Visual Studio, ya que desde el fichero
fuente abierto no puedes saber si el mtodo invocado es parte de la instancia o es un
mtodo extensor. Lo cual puede causar dolores de cabeza para localizar el cdigo si no
ests con el IDE adecuado.
Otro problema es que el ciclo de vida de la clase y de los mtodos que la extiende
pueden ser totalmente diferentes. Por lo cual un extensor que funcione bien en un
momento, por un cambio en el objeto que extiende puede dejar de hacerlo correctamente.
Por ltimo si se realiza un cambio en la clase extendida y se le aade un mtodo de
instancia con el mismo nombre que el mtodo extensor, este dejar de funcionar ya que el
compilador utilizar antes el mtodo de instancia que el extensor. Lo cual es un error muy
difcil de localizar.
A causa de ello, se aconseja darle prioridad al uso de los mtodos de instancia y a la
herencia, antes que a los mtodos extensores. Lo cual no quita que herramientas como
Linq est construida en su totalidad con esta tcnica de desarrollo.
Ahora solo queda, probar y practicar, para tener un conocimiento en ms profundidad que
te permita tomar las decisiones ms productivas.
namespace GenbetaDevMVC4.Models
{
public class Persona
{
public string Nombre { get; set; }
public int Edad { get; set; }
}
public class Comprobaciones
{
public bool esCorrecto(Persona persona)
{
return ! persona.NoTieneNombre();
}
}
public static class Extensores
{
public static Boolean IsNotNull(this Object objeto)
{
return objeto != null;
}
public static Boolean NoTieneNombre(this Persona persona)
{
return string.IsNullOrEmpty(persona.Nombre)
&& string.IsNullOrWhiteSpace(persona.Nombre);
}
}
}
03/05/2012 He aadido un enlace a una biblioteca de mtodos extensores que ser de gran
utilidad.
Delegados en C#
Introduccin
Uno de los temas que ms suelen confundir a los nuevos programadores de la plataforma
.NET, al menos a m, son los delegados. Para los programadores de C++ el concepto es
muy similar al de los punteros a funciones, los programadores de JavaScript tienen una
funcionalidad parecida a travs de objetos Function (que rara vez se utilizan), pero para el
resto es totalmente nuevo.
Lo primero que vamos a hacer es intentar definir que son los delegados. Una buena
definicin seria:
Los delegados son un tipo que representa a una funcin con una determinada
declaracin.
Vamos a analizar esta definicin poco a poco, ya que dice mucho en muy pocas palabras...
Si un delegado es un tipo, entonces puedo crear variables de ese tipo. Tambin hemos dicho
que un delegado representa a una funcin, por lo que una variable creada de un tipo
delegado es la representacin de una determinada funcin. Por litmo hemos dicho que esta
funcin debe tener una determinada declaracin.
Un ejemplo sencillo.
Veamos un ejemplo que es como mejor se ven las cosas:
using System;
namespace Devjoker.Ejemplos
{
delegate int myDelegate(int arg) ;
}
En este caso hemos declarado un tipo delegado, al que hemos llamado myDelegate que
representa a una funcin que devuelve un entero y que recibe como argumento tambin un
entero. Como es un tipo no es necesario definirlo dentro de una clase. Como resultado
vamos a poder declarar variables del tipo myDelegate.
La siguiente pregunta seria: Como declaro variables de tipo myDelegate? Y como asigno
"valores" a esa variable? Los delegados se declaran como cualquier otro objeto en .NET,
recordemos que todo en .NET son objetos y los delegados no son una excepcin. Para
asignar un delegado, no debemos asignar un "valor", sino un mtodo, normalmente una
funcion. El mtodo al que asignemos el delegado no puede ser una funcin cualquiera, debe
cumplir con la declaracin del delegado. La forma de asignarlo es a travs del constructor
del delegado, pasando como argumento la funcin.
A continuacion mostramos dos posibles funciones que cumplen con la declaracin del
delegado:
private int delegateFunction1(int a)
{
Console.WriteLine("Ejecutando ... delegateFuntion1");
return ++a;
}
private int delegateFunction2(int a)
{
Console.WriteLine("Ejecutando ... delegateFuntion2");
return a;
}
Como podemos ver tanto delegateFuntion1 como delegateFunction2 son functiones que
devuelven un tipo int y reciben como parametro un int, que es la forma en la que hemos
declarado el delegado.
La declaracin de una variable con el tipo delegado, es la siguiente, como cualquier otra
variable en C#:
myDelegate variableDelegado;
Ahora vamos a ver como asignar un delegado. Hemos dicho que para asignar un delegado
necesitamos utilizar el constructor del mismo. En el siguiente ejemplo vamos a ver un
pequeo programa completo que declara y ejecuta un delegado:
using System;
namespace Devjoker.Ejemplos
{
delegate int myDelegate(int arg) ;
class MainClass
{
public static void Main(string[] args)
{
MainClass instancia = new MainClass();
myDelegate variableDelegado;
variableDelegado =
new myDelegate(instancia.delegateFunction1);
int a = variableDelegado(5);
System.Console.WriteLine("El resultado obtenido es ... {0}",a);
}
private int delegateFunction1(int a)
{
Console.WriteLine("Ejecutando ... delegateFuntion1");
return ++a;
}
private int delegateFunction2(int a)
{
Console.WriteLine("Ejecutando ... delegateFuntion2");
return a;
}
}
}
Seguro que alguno de vosotros est pensando en que esto mismo podra hacerse invocando
a la funcion sin ms, y no andar con tanto lo. Por supesto, pero este es un ejemplo muy
sencillo y no pretende mostrar los casos complicados, recordemos que queremos aprender
que son y para que sirven los delegados.
Un caso ms complejo podra ser el siguiente: Imaginemos un control creado
dinamicamente por el programa, sobre el que queramos programar su click ... La forma de
hacerlo es con delegados.
La funcin a la que asignamos el delegado no debe pertenecer a una clase en concreto, ni
siquiera a un assemblie en particular, solo debe cumplir la declaracin del delegado.
Vamos ahora a ver un ejemplo algo ms prctico.
Un ejemplo practico.
El siguiente ejemplo, sin pretender entrar en mucha complejidad, es un sencillo programa
que realiza su log de proceso bien por pantalla o en un fichero.
El programa tiene tres clases:
FormasLog, que es la clase que contiene las diferentes formas para realizar el log del
proceso.
El programa tiene adems un delegado, que vamos a utilizar para realizar el log del
proceso. Tiene la siguiente forma:
delegate void logDelegate(string arg);
Veamos cada una de estas clases. Empezaremos con la clase FormasLog, que tiene dos
mtodos (que coinciden con la declaracin del delegado), ConsoleLog y FileLog.
ConsoleLog muestra un mensaje en pantalla y FileLog, escribe el mensaje en un fichero
(ubicado en la misma ruta que el assemblie).
El cdigo de la clase es el siguiente:
class FormasLog
{
private static StreamWriter fw;
public static void ConsoleLog(string texto)
{
System.Console.WriteLine(texto);
}
public static void FileLog(string texto)
{
try
{
bool append = true;
if (fw == null)
{
append = false;
}
fw = new StreamWriter( "ficherolog.log",append);
fw.WriteLine(texto);
fw.Close();
}
catch (IOException e)
{
FormasLog.ConsoleLog(e.Message);
}
}
}
Por ltimo tenemos la clase MainClass que es la clase que contiene el mtodo Main en el
que se inicia la ejecucin del programa.
El mtodo Main crea una instancia de la clase ProgramClass a la que llamamos
"programa", y solicita al usuario que especifique una forma para realizar el log.
Dependiendo de la seleccin del usuario llama al mtodo SetLogMethod del objeto
"programa" pasandole una nueva variable de tipo logDelegate construida con las
funciones que proporciona la clase FormasLog.
El cdigo de la clase es:
Class MainClass
{
public static void Main(string[] args)
{
ProgramClass programa = new ProgramClass ();
string valor = "";
do
{
Console.WriteLine("?Que forma de log quiere?");
Console.WriteLine("1->Consola 2->Fichero");
valor = System.Console.ReadLine();
}
while (valor != "1" && valor!= "2" );
if (valor == "1")
{
programa.SetLogMethod(new logDelegate(FormasLog.ConsoleLog));
}
else if (valor =="2")
{
programa.SetLogMethod(new logDelegate(FormasLog.FileLog));
}
programa.Run ( );
}
}
La gran ventaja de esta forma de trabajar es que podemos cambiar la forma en la que el
programa realiza el log desde el exterior del programa sin ningn tipo de esfuerzo.
Imaginemos que la clase FormasLog y ProgramClass constituyen un componente de
software compilado en un assemblie miComponente.dll. Por otro lado, la clase MainClass
pertenece a otro assemblie completamente diferente, otroAssemblie.dll. En este escenario
es posible que las formas de log que hemos predeterminado no sean suficientes como para
cubrir las necesidades del programa, por ejemplo el log debe guardarse en una base de
datos. Podramos escribir una nueva funcion y decirle al componente que la utilice, solo
tendramos que llamar al metodo SetLogMethod.
El cdigo completo del programa se muestra a continuacin:
namespace Devjoker.Ejemplos
{
delegate void logDelegate(string arg) ;
class MainClass
{
public static void Main(string[] args)
{
ProgramClass programa = new ProgramClass ();
string valor = "";
do
{
Console.WriteLine("Que forma de log quiere?");
Console.WriteLine("1->Consola 2->Fichero");
valor = System.Console.ReadLine();
}
while (valor != "1" && valor!= "2" );
if (valor == "1")
{
programa.SetLogMethod(new logDelegate(FormasLog.ConsoleLog));
}
else if (valor =="2")
{
programa.SetLogMethod(new logDelegate(FormasLog.FileLog ));
}
programa.Run ( );
}
}
class ProgramClass
{
logDelegate logFunction;
public void Run()
{
if (logFunction == null)
{
logFunction = new logDelegate(FormasLog.ConsoleLog);
}
int i = 0;
do
{
logFunction("Este es el log! en la iteracion " + i.ToString());
}
while (i++<100);
}
public void SetLogMethod(logDelegate metodo)
{
logFunction = metodo;
}
private void Log(string texto)
{
logFunction(texto);
}
}
class FormasLog
{
private static StreamWriter fw;
public static void ConsoleLog(string texto)
{
System.Console.WriteLine(texto);
}
public static void FileLog(string texto)
{
try
{
bool append = true;
if (fw == null)
{
append = false;
}
fw = new StreamWriter( "ficherolog.log",append);
fw.WriteLine(texto);
fw.Close();
}
catch (IOException e)
{
FormasLog.ConsoleLog(e.Message);
}
}
}
}
Como hemos podido ver los delegados son una potentisima herramienta que ofrece, no solo
C# sino la plataforma .NET.
Espero que este articulo os haya servido de ayuda para entender un poco mejor los
delegados, y si teneis alguna duda al respecto no dudeis en exponerla en los foros.
Mtodos annimos
Estoy seguro que usted recordar su primera clase de programacin donde le ensearon la
manera de crear mtodos e invocarlos. Pues bien, estos mtodos que usted aprendi tienen
una sintaxis que hacen prosible la invocacin como la especificacin de ciertos parmetros
para este. Por ejemplo:
Public void miMetodo(int nro) {
Console.Write("{0} ", (char)nro);
Console.Write("0x{0:X} ", nro);
Console.Write("{0} ", nro);
}
Hasta ahora todo es muy conocido. Hemos creado un mtodo llamado miMetodo seguido
de un parmetro entero para este. La manera de invocarlo usando un delegado es muy fcil,
y eso lo sabe usted bien. Bueno, as como existe este mtodo con nombre, tambin existe
los recien conocidos Mtodos Annimos, que viene con C# 2.0 como parte de una de sus
novedades de programacin. Pues usted se preguntar y que s un mtodo annimo? Un
mtodo annimo es justamente eso, aquel mtodo que no tiene un nombre especfico. Y
ahora usted se preguntar y cmo invoco los mtodo annimos?, pues buena pregunta, la
respuesta es usando Delegados.
El detalle es que siempre hemos estado acostumbrados a nombrar las mtodos, tanto
aquellos que devuelven valores nulos o los que devuelven un cierto valor para su posterior
uso, y esta novedad de Mtodos annimos en C# 2.0, viene a ser el tema de este post. Es
por eso que paso a explicar lo concerniente a Mtodos Annimos usando Delegados.
Lo importante es que los mtodos annimos, reduce la sobrecarga de codificacin al crear
instancias de delegados sin tener que crear un mtodo independiente. Un mtodo annimo
es declarado con la palabra clave delegate seguido de una lista de parmetros. Las lneas de
cdigo para el mtodo estn dentro del par de llaves {}. Todo esto debe ser referenciado por
un tipo delegate que tenga la misma firma que el mtodo annimo, es as que, la invocacin
de los mtodos annimos se realiza usando esta referencia. Vayamos con un ejemplo:
Lo primero que se debe hacer es declarar el delegado.
Ahora se deben instanciar los delegados y los mtodos annimos. Esto es explicado dentro
de las siguientes lneas de cdigo.
static void Main(string[] args)
{
//creamos los delegados a partir de tipo definido printIntView
printIntView entero, hexa, oChar, EHC,HC, EC;
//otros mtodos annimos
entero=delegate(int nro) { System.Console.Write("{0} ", (char)nro); };
hexa = delegate(int nro) { System.Console.Write("0x{0:X} ", nro); };
oChar = delegate(int nro) { System.Console.Write("{0} ", nro); };
Console.Write("\nentero: "); entero(128);
Console.Write("\n hexa: "); hexa(128);
Console.Write("\nChar: "); oChar(128);
EHC = entero + hexa + oChar; // callbacks
Console.Write("\nEHC: ");
EHC(128);//invocacin
HC= EHC - entero;
Console.Write("\nHC: ");
HC(128);//invocacin
EC = EHC - hexa;
Console.Write("\nEC: ");
HC(128); //invocacin
Console.ReadLine();
}
Ya sabis esto de los mtodos annimos, y espero que nuevamente este post sea de utilidad.
Partiendo de estas definiciones, y de otras muchas aportadas por Google ;-), est claro que las
lambda son funciones, es decir, un conjunto de intrucciones capaces de retornar un valor partiendo
de los parmetros que se les suministra, aunque en determinados casos es posible que no reciba
ningn parmetro, o que realicen una accin sin retornar nada. Igual que una funcin tradicional,
vaya. Y de hecho, en el cuerpo de una expresin lambda puede haber casi de todo: llamadas a otras
funciones, expresiones, bucles, declaraciones de variables...
Sin embargo, a diferencia de los mtodos o funciones habituales, las lambdas no necesitan de un
identificador, puesto que se declaran in situ, justo en el momento en que van a asignarse a una
variable o a utilizarse como parmetro de una funcin, pasando el destinatario de esta asignacin a
actuar como delegado, o puntero, hacia la misma, o a ser el contenedor del rbol de expresin que la
representa. Ein? Chino, eh? No pasa nada, dentro de poco estudiaremos estos dos usos en
profundidad, pero antes vamos a ver cmo se definen las expresiones lambda a nivel de cdigo.
(a, b) => a + b
num => {
int x = new Random().Next();
return num+x;
}
Como se puede observar, cuando slo existe un parmetro no es necesario utilizar parntesis en el
lado izquierdo de la expresin, mientras que hay que hacerlo en todos los dems casos. Tambin es
interesante destacar que las lambda con cuerpo deben utilizar return para retornar el valor deseado,
cuando esto sea necesario.
Y un ltimo dato: fijaos que ni los parmetros ni el retorno de la funcin tienen indicado un tipo.
Aunque puede hacerse, normalmente no ser necesario puesto que el compilador podr inferir
(deducir) el tipo a partir de su contexto, ms adelante veremos cmo es esto posible. Por tanto, no
es necesario escribir cdigo tan extenso como:
(int a, int b) => (int)(a+b)
Y hasta aqu este primer post introductorio. En el siguiente trataremos de explicar el papel de las
expresiones lambda como funciones annimas y facilitadoras del trabajo con delegados.
Las referencias a estas funciones annimas son transformadas en delegados (punteros) a las mismas,
lo que nos permitir, por ejemplo, invocarlas desde el cdigo. En la prctica esto quiere decir que
podemos asignar una lambda a una variable y ejecutarla como muestra el siguiente pseudocdigo:
delegado duplica = x => x * 2;
escribe duplica(2); // Escribe un 4
En este primer acercamiento, fijaos que duplica es el nombre del delegado, la funcin definida en
forma de expresin lambda no tiene nombre, ser el compilador el que se asigne uno.
Veamos cmo se concreta esta idea en C#. En el siguiente cdigo, la variable duplica apunta hacia
una funcin annima definida a travs de la expresin lambda en cuya implementacin lo nico que
se hace es retornar el doble del valor que le llega como parmetro. Vemos tambin cmo podemos
utilizarla de forma directa:
Func<int, int> duplica = x => x * 2;
int result = duplica(7); // result vale 14
Slo con objeto de que podis entender el cdigo anterior, os adelantar que la porcin Func<int, int>
es una forma rpida de tipificar el delegado, indicando que duplica apunta a una funcin que espera
un entero como parmetro de entrada, y que su valor de retorno ser otro entero. Esto lo veremos
dentro de un momento.
De la misma forma que asignamos la expresin lambda a una variable, podemos hacerlo tambin
para indicar el valor de un parmetro a un mtodo que acepte un delegado concreto. Por ejemplo, el
siguiente cdigo muestra un mtodo llamado calcula que recibe un valor entero y una referencia a
una funcin, retornando el resultado de efectuar dicha operacin sobre el entero proporcionado:
// Es mtodo ejecuta la funcin indicada por
// el parmetro operacion, envindole el valor especificado,
// y retorna el resultado obtenido de la misma.
Public int calcula(int valor, Func<int, int> operacion)
{
return operacion(valor); // retorna el resultado de aplicar la expresin indicada al valor.
}
// Usos posibles:
int i = calcula(4, x => x / 2);
<HistoriaDelAbuelete>
Seguro que a los ms viejos del lugar esto le recuerda a los Codeblocks que utilizbamos en Clipper
a principios de los 90 (uuf, cmo pasa el tiempo...). Todava reconocis el siguiente cdigo?
bDuplica := { |n| n*2 }
? EVAL(bDuplica, 7) // Muestra un 14
</HistoriaDelAbuelete>
Una consecuencia directa de que las expresiones lambdas sean referenciadas a travs de delegados
es que podemos utilizarlas en cualquier sitio donde se acepte un delegado, con la nica
precaucin de escribirla teniendo en cuenta el tipo de su retorno y los parmetros que recibe. Un
ejemplo claro lo tenemos en la suscripcin a eventos, donde la tcnica habitual consiste en utilizar
un delegado a un mtodo en el que se implementa la lgica del tratamiento de los mismos, algo
como:
// Nos suscribimos al evento MouseMove:
this.MouseMove += new MouseEventHandler(this.Form1_MouseMove);
[...]
// Tratamiento del evento MouseMove:
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
this.Text = e.X + "," + e.Y;
}
Como sabemos, podemos suscribirnos al evento MouseMove aadindole delegados del tipo
MouseEventHandler, definido en System.Windows.Forms, cuya firma indica que recibe un parmetro
de tipo object, otro de tipo MouseEventArgs y no retorna ningn valor, exactamente igual que sera un
delegado annimo (C# 2.0) escrito as:
this.MouseMove += delegate(object sender, MouseEventArgs args)
{
this.Text = args.X + "," + args.Y;
};
Y dado que las lambdas pueden sustituir de forma directa a cualquier delegado, podemos utilizarlas
para conseguir un cdigo ms compacto:
this.MouseMove += (sender, args) => {
this.Text = args.X + "," + args.Y;
};
Llegados a este punto es conveniente aclarar que las expresiones lambda son caractersticas
introducidas en los lenguajes, y por tanto en sus compiladores, pero no en la plataforma de
ejecucin (CLR) en s. Por tanto, todo lo descrito hasta el momento era posible realizarlo antes que
las lambda aparecieran por el horizonte, aunque de forma un poco ms tediosa, utilizando
mecanismos que la versin 2.0 del framework pona a nuestra disposicin, como los delegados y
mtodos annimos. En este sentido, el uso de expresiones lambda aportan mucha simplicidad,
elegancia y legibilidad al cdigo.
Esto explica, adems, que Visual Studio 2008 sea capaz de generar cdigo para .NET 2.0 a partir de
cdigo fuente C# 3.0.
tipo de medicamento, mejor que leas algo sobre el tema antes de continuar, pues en caso contrario
es posible que te pierdas un poco ;-). Puedes probar leyendo una introduccin a los generics en c#, o
la Gua de programacin de C#.
.NET Framework ofrece en el espacio de nombres System un conjunto de definiciones de genricas
de delegados para que podamos utilizarlos para "apuntar" hacia las funciones definidas mediante
expresiones lambda, llamados Action y Func.
Utilizaremos los tipos Func para definir referencias a expresiones lambda que retornen un valor, o
sea, funciones. De ah su nombre. Los tipos Action, en cambio, estn destinados a referenciar a
lambdas que realicen acciones y que no retornen ningn valor. De ah su nombre tambin. ;-)
Una de estas definiciones es la que habamos usado en un ejemplo anterior:
Func<int, int> duplica = x => x * 2;
Como se puede observar, al tratarse de una referencia a una funcin que retorna un valor, hemos
utilizado un tipo Func con dos parmetros genricos, que corresponde con la siguiente declaracin
existente en el espacio de nombres System:
Public delegate TResult Func<T, TResult>(T arg);
Por ello, cuando declarbamos que la variable duplica era del tipo Func<int, int>, lo que indicbamos
era, en primer lugar que el parmetro que necesitaba la lambda era un int, y que sta nos devolvera
tambin un int, es decir, lo mismo que si hubiramos definido duplica as, utilizando mtodos
annimos de C# 2.0:
// En el rea de declaraciones:
public delegate int Duplicador(int arg);
...
// En el cdigo:
Duplicador duplica = delegate(int k) {return k*2 };
Es importante saber que en el framework estn definidos los delegados Func<tipo1, tipo2...,
tipoResult> para funciones de hasta cuatro parmetros. Si necesitamos ms deberemos definir los
delegados a mano, aunque esto es realmente sencillo utilizando una de las declaraciones existentes
y aadindole el nmero de parmetros que deseemos. Por ejemplo, para seis parmetros la
definicin del genrico sera algo as como:
public delegate
TResult Func<T1, T2, T3, T4, T5, T6, TResult>
(T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6);
Pero ahora aparece un pequeo problema: las funciones sin retorno no pueden referenciarse con
delegados de tipo Func, puesto que el framework .NET no soporta la instanciacin de tipos
genricos utilizando parmetros void (ECMA 335, seccin 9.4, pg. 153). Por tanto, no podramos
declarar un delegado como Func<int, void> para apuntar hacia una funcin que recibe un entero y no
devuelve nada. Si lo pensis un poco, este es el motivo de que no exista ninguna sobrecarga de la
clase Func sin parmetros genricos, pues como mnimo debemos indicar el tipo del valor de
retorno.
La clave para cubrir estos casos se encuentra en el tipo Action. Como comentaba unas lneas ms
arriba, el objeto de estos tipos de delegados es apuntar a expresiones lambda que realicen acciones y
que no retornen ningn valor, por lo que sus parmetros genricos describirn exclusivamente los
tipos de los parmetros de la funcin. En este caso, como es obvio, s existe una clase no
parametrizada Action para apuntar a funciones sin parmetros, adems de disponer de genricos que
cubren las acciones de hasta cuatro parmetros. Veamos unos ejemplos:
// Accin sin parmetros (no genrica):
Action saluda = () => Console.WriteLine("hola");
saluda(); // Muestra "hola";
// Accin que recibe un string
Action<string> apaga = motivo => {
log(motivo);
shutdown();
};
apaga("mantenimiento"); // Apaga el sistema
Por ltimo, me parece interesante recordar algo que haba comentado en el post anterior, que en las
expresiones lambda no era necesario indicar el tipo de los parmetros ni del retorno porque el
compilador los infera del contexto. Como podemos ver, lo tiene bastante fcil, puesto que
simplemente debe tomar la definicin del delegado para conocerlos; por eso no es necesario
introducir redundancias como las siguientes:
Func<int, int> duplica = (int a) => (int)(a * 2); // Redundante!
// Forma ms cmoda:
En cualquier caso, si por algn motivo es necesario utilizar la forma explcita, sabed que no se
permite hacerlo de forma parcial, es decir, o le ponis los tipos a todo, o no se los ponis a nada.
This delegate defines a delegate type named Log that can be used to store references to any
method(s) that satisfies its signature (whether instance, static, lambda expression, etc.).
Delegate instances then can be assigned zero (null) or more methods using the operator =
which replaces the existing delegate chain, or by using the operator += which adds a
method to the end of a delegate chain:
1: // creates a delegate instance named currentLogger defaulted to Console.WriteLine (static method)
2: Log currentLogger = Console.Out.WriteLine;
3:
While delegates give us a lot of power, it can be cumbersome to re-create fairly standard
delegate definitions repeatedly, for this purpose the generic delegates were introduced in
various stages in .NET. These support various method types with particular signatures.
Note: a caveat with generic delegates is that while they can support multiple parameters,
they do not match methods that contains ref or out parameters. If you want to a delegate to
represent methods that takes ref or out parameters, you will need to create a custom
delegate.
Func<TResult> matches a method that takes no arguments, and returns value of type
TResult.
Func<T, TResult> matches a method that takes an argument of type T, and returns value
of type TResult.
Func<T1, T2, TResult> matches a method that takes arguments of type T1 and T2, and
returns value of type TResult.
These are handy because they quickly allow you to be able to specify that a method or class
you design will perform a function to produce a result as long as the method you specify
meets the signature.
For example, lets say you were designing a generic aggregator, and you wanted to allow
the user to define how the values will be aggregated into the result (i.e. Sum, Min, Max,
etc). To do this, we would ask the user of our class to pass in a method that would take
the current total, the next value, and produce a new total.
A class like this could look like:
1: public sealed class Aggregator<TValue, TResult>
2: {
3: // holds method that takes previous result, combines with next value, creates new result
4: private Func<TResult, TValue, TResult> _aggregationMethod;
5:
6: // gets or sets the current result of aggregation
7: public TResult Result { get; private set; }
8:
9: // construct the aggregator given the method to use to aggregate values
10: public Aggregator(Func<TResult, TValue, TResult> aggregationMethod = null)
11: {
12:
if (aggregationMethod == null) throw new ArgumentNullException("aggregationMethod");
13:
14:
_aggregationMethod = aggregationMethod;
15: }
16:
17: // method to add next value
18: public void Aggregate(TValue nextValue)
19: {
20:
// performs the aggregation method function on the current result and next and sets to current result
21:
Result = _aggregationMethod(Result, nextValue);
22: }
23: }
Of course, LINQ already has an Aggregate extension method, but that works on a sequence
of IEnumerable<T>, whereas this is designed to work more with aggregating single
results over time (such as keeping track of a max response time for a service).
We could then use this generic aggregator to find the sum of a series of values over time, or
the max of a series of values over time (among other things):
1: // creates an aggregator that adds the next to the total to sum the values
2: var sumAggregator = new Aggregator<int, int>((total, next) => total + next);
3:
4: // creates an aggregator (using static method) that returns the max of previous result and next
5: var maxAggregator = new Aggregator<int, int>(Math.Max);
So, if we were timing the response time of a web method every time it was called, we could
pass that response time to both of these aggregators to get an idea of the total time spent in
that web method, and the max time spent in any one call to the web method:
1: // total will be 13 and max 13
2: int responseTime = 13;
3: sumAggregator.Aggregate(responseTime);
4: maxAggregator.Aggregate(responseTime);
5:
6: // total will be 20 and max still 13
7: responseTime = 7;
8: sumAggregator.Aggregate(responseTime);
9: maxAggregator.Aggregate(responseTime);
10:
11: // total will be 40 and max now 20
12: responseTime = 20;
13: sumAggregator.Aggregate(responseTime);
14: maxAggregator.Aggregate(responseTime);
The Func delegate family is useful for making generic algorithms and classes, and in
particular allows the caller of the method or user of the class to specify a function to be
performed in order to generate a result.
Well, in .NET if you chain multiple methods in a delegate, they will all get invoked, but the
result of the delegate is the result of the last method invoked in the chain. Thus, this
aggregator would always result in the Math.Max() result. The other chained method (the
sum) gets executed first, but its result is thrown away:
1: // result is 13
2: int responseTime = 13;
3: comboAggregator.Aggregate(responseTime);
4:
5: // result is still 13
6: responseTime = 7;
7: comboAggregator.Aggregate(responseTime);
8:
9: // result is now 20
10: responseTime = 20;
11: comboAggregator.Aggregate(responseTime);
So remember, you can chain multiple Func (or other delegates that return values) together,
but if you do so you will only get the last executed result.
Func<in T, out TResult> matches a method that takes an argument of type T (or
a less derived type), and returns value of type TResult(or a more derived type).
Func<in T1, in T2, out TResult> matches a method that takes arguments of type
T1 and T2 (or less derived types), and returns value of type TResult (or a more
derived type).
Notice the addition of the in and out keywords before each of the generic type
placeholders. As we saw last week, the in keyword is used to specify that a generic type
can be contra-variant -- it can match the given type or a type that is less derived. However,
the out keyword, is used to specify that a generic type can be co-variant -- it can match the
given type or a type that is more derived.
On contra-variance, if you are saying you need an function that will accept a string, you
can just as easily give it an function that accepts an object. In other words, if you say give
me an function that will process dogs, I could pass you a method that will process any
animal, because all dogs are animals.
On the co-variance side, if you are saying you need a function that returns an object, you
can just as easily pass it a function that returns a string because any string returned from
the given method can be accepted by a delegate expecting an object result, since string is
more derived. Once again, in other words, if you say give me a method that creates an
animal, I can pass you a method that will create a dog, because all dogs are animals.
It really all makes sense, you can pass a more specific thing to a less specific parameter,
and you can return a more specific thing as a less specific result. In other words, pay
attention to the direction the item travels (parameters go in, results come out). Keeping
that in mind, you can always pass more specific things in and return more specific things
out.
For example, in the code below, we have a method that takes a Func<object> to generate
an object, but we can pass it a Func<string> because the return type of object can
obviously accept a return value of string as well:
1: // since Func<object> is co-variant, this will access Func<string>, etc...
2: public static string Sequence(int count, Func<object> generator)
3: {
4: var builder = new StringBuilder();
5:
6: for (int i=0; i<count; i++)
7: {
8:
object value = generator();
9:
builder.Append(value);
10: }
11:
12: return builder.ToString();
13: }
Even though the method above takes a Func<object>, we can pass a Func<string>
because the TResult type placeholder is co-variant and accepts types that are more derived
as well:
1: // delegate that's typed to return string.
2: Func<string> stringGenerator = () => DateTime.Now.ToString();
3:
4: // This will work in .NET 4.0, but not in previous versions
5: Sequence(100, stringGenerator);
As of .NET 3.5, predicates are typically represented as Func<T, bool> where T is the type
of the item to examine. Previous to .NET 3.5, there was a Predicate<T> type that tended
to be used (which well discuss next week) and is still supported, but most developers
recommend using Func<T, bool> now, as it prevents confusion with overloads that accept
unary predicates and binary predicates, etc.:
1: // this seems more confusing as an overload set, because of Predicate vs Func
2: public static SomeMethod(Predicate<int> unaryPredicate) { }
3: public static SomeMethod(Func<int, int, bool> binaryPredicate) { }
4:
5: // this seems more consistent as an overload set, since just uses Func
6: public static SomeMethod(Func<int, bool> unaryPredicate) { }
7: public static SomeMethod(Func<int, int, bool> binaryPredicate) { }
Also, even though Predicate<T> and Func<T, bool> match the same signatures, they are
separate types! Thus you cannot assign a Predicate<T> instance to a Func<T, bool>
instance and vice versa:
1: // the same method, lambda expression, etc can be assigned to both
2: Predicate<int> isEven = i => (i % 2) == 0;
3: Func<int, bool> alsoIsEven = i => (i % 2) == 0;
4:
5: // but the delegate instances cannot be directly assigned, strongly typed!
6: // ERROR: cannot convert type...
7: isEven = alsoIsEven;
8:
9: // however, you can assign by wrapping in a new instance:
10: isEven = new Predicate<int>(alsoIsEven);
11: alsoIsEven = new Func<int, bool>(isEven);
So, the general advice that seems to come from most developers is that Predicate<T> is
still supported, but we should use Func<T, bool> for consistency in .NET 3.5 and above.
Then, we can use this construct to unit test our CacheItem<T> without any time
dependencies:
1: var baseTime = DateTime.Now;
2:
3: // start with current time stored above (so doesn't drift)
4: CacheItem<int>.TimeGenerator = () => baseTime;
5:
6: var target = new CacheItem<int>(13);
7:
8: // now add 15 seconds, should still be non-expired
9: CacheItem<int>.TimeGenerator = () => baseTime.AddSeconds(15);
10:
11: Assert.IsFalse(target.IsExpired);
12:
13: // now add 31 seconds, should now be expired
14: CacheItem<int>.TimeGenerator = () => baseTime.AddSeconds(31);
15:
16: Assert.IsTrue(target.IsExpired);
Now we can unit test for 1 second before, 1 second after, 1 millisecond before, 1 day after,
etc. Func delegates can be a handy tool for this type of value generation to support more
testable code.
Summary
Generic delegates give us a lot of power to make truly generic algorithms and classes. The
Func family of delegates is a great way to be able to specify functions to calculate a result
based on 0-16 arguments.
Stay tuned in the weeks that follow for other generic delegates in the .NET Framework!
Crear peticiones LINQ
1. Presentacin
LINQ significa Language INtegrated Query. Desde el punto de vista del desarrollador, se
trata de un lenguaje de consulta que permite realizar peticiones a fuentes de datos de
diferente naturaleza:
Esta peticin permite obtener el identificador y la etiqueta de aquellos procesos que estn
en ejecucin en un ordenador cuya cantidad de memoria asignada es estrictamente superior
a 20 MB. Adems, devuelve los registros por orden alfabtico inverso segn su etiqueta.
Se utilizarn las peticiones LINQ en la implementacin de reglas y servicios y
procesamientos de negocio en nuestras aplicaciones.
Select(): permite realizar una proyeccin de datos sobre los objetos o datos que
contienen.
LINQ
LINQ is Microsofts technology to provide a language-level support mechanism for
querying data of all types. These types include in memory arrays and collections, databases,
XML documents and more, since version 3.5 and Visual Studio 2008. (.NET 4 and Visual
Studio 2010 added support for the Parallel LINQ features).
Here is the issue that we cannot programmatically interact with a database at the native
language level. This means syntax errors often go undetected until runtime.
Differing data types utilized by a particular data domain, such as database or XML data
types versus the native language
XML parsing, iterating, and manipulation can be quite tedious. an XmlDocument must be
created just to perform various operations on the XML fragment
Advantages of LINQ
Query is integrated into the language. Gone are the days of writing a SQL query into a
string and not detecting a syntax error until runtime, for example we forget the name of a
field of the table (or reference) or it has been changed in DB.
In addition to query features, LINQ to XML provides a more powerful and easier-to-use
interface for working with XML data, as though it were a database.
Example:
XElement books = XElement.Parse(
@"<books>
<book>
<title>Pro LINQ: Language Integrated Query in C#2010</title>
<author>Joe Rattz</author>
</book>
<book>
<title>Pro .NET 4.0 Parallel Programming in C#</title>
<author>Adam Freeman</author>
</book>
<book>
<title>Pro VB 2010 and the .NET 4.0 Platform</title>
<author>Andrew Troelsen</author>
</book>
</books>");
//XElement Xbooks = XElement.Parse(@"XMLFile.xml");
var titles =
Not only for querying data but for formatting, validating, and even getting data into the
necessary format : example string array to integer array and sort. can also use select new for
complex classes
Example:
string[] numbers = { "0042", "010", "9", "27" };
int[] nums = numbers.Select(
s => Int32.Parse(s)).OrderBy(s => ).ToArray();
foreach (int num in nums)
Console.WriteLine(num);
Syntax of LINQ
Two syntaxes are available for LINQ queries:
Type of LINQ
LINQ to Objects
LINQ to XML
LINQ to DataSet
LINQ to SQL
LINQ to Entities.
They are queries returning a set of matching objects, a single object, or a subset of fields
from an object or set of objects. In LINQ, this returned set of objects is called a sequence,
and most LINQ sequences are of type IEnumerable<T>.
We can use the "Cast" or "OfType" operators for Legacy Collections to convert them to
IEnumerable to use LINQ.
The LINQ query actually take place the first time a result from it is needed. This is typically
when the query results variable is enumerated.
And this may lead to the situation: deferred query is passed undetected with error.-->
deferred query example (disadvantage)
Example:
string[] strings = { "one", "two", null, "three" };
Console.WriteLine("Before Where() is called.");
IEnumerable<string> ieStrings = strings.Where(s => s.Length == 3);
Console.WriteLine("After Where() is called.");
foreach (string s in ieStrings)
{
Console.WriteLine("Processing " + s);
}
Calling the actual query each time is needless work. It might make more sense to have a
query initialization method that gets called once for the lifetime of the scope and to
construct all the queries there (advantage).
List<string> strings = new List<string>();
strings.Add("one");
strings.Add("two");
strings.Add("three");
IEnumerable<string> ieStrings = strings.Where(s => s.Length == 3);
foreach (string s in ieStrings)
{
Console.WriteLine("Processing " + s);
}
Console.ReadKey();
strings.Add("six");
Console.WriteLine("source enumerable changed but query is not invoked again");
//query is not invoked explicitly, ieStrings is not changes
foreach (string s in ieStrings)
{
Console.WriteLine("Processing " + s);
}
In Linq To Entity, string can be passed in where clause as a condition using it as variable
refenrence.
Example:
VCAB_CMS_DBEntities context = new VCAB_CMS_DBEntities();
string condition = "it.DocName=='Shakuntala Devi'";
int pageCount= context.EDOC.Where(condition).Select(c => c.DocPageCount).First();
This expression refers to an anonymous method with left side as input to the method and
right side as the output.
Expression trees
IEnumerable<int> numsLessThanFour = nums.Where(i => i < 4).OrderBy(i => i);
The Where operator is called first, followed by the OrderBy operator. But an expression
tree allows the simultaneous evaluation and execution of all operators in a query, a single
query can be made instead of a separate query for each operator.
The keyword var, object and collection initialization, and anonymous types
var address = new { address = "105 Elm Street",
city = "Atlanta", state = "GA", postalCode = "30339" };
Extension methods
Used to extend a sealed class like adding factorial method in int class or adding todouble
method in string class.
Extension methods are methods that, although static, can be called on an instance (object)
of a class rather than on the class itself.
Specifying a methods first argument with the this keyword modifier will make that
method an extension method.
}
}
Partial methods
Partial methods are private but must not specify the private modifier
Example: To make the generated entity classes more usable, partial methods have been
added to them. You can add another module that declares the same entity class, implement
these partial methods, and be notified every time a property is about to be changed and after
it is changed
Query expressions
Query expressions allow LINQ queries to be expressed in nearly SQL form, with just a few
minor deviations.
The from statement precedes the select statement, hence intelliSense has the scope of
what variables to offer you for selection.
Published: 3/12/2007
By: Granville Barnett
Introducing LINQ is the first part of a series of articles on Language Integrated Query (LINQ). This
series will cover the core essentials of LINQ and its use with other technologies like ASP.NET, Win
Forms and WPF.
This and the forthcoming articles are designed to be a comprehensive tutorial on LINQ,
having completed the series you will have a thorough understanding of LINQ and C# 3.0.
All code in the series will use the latest publicly available CTP at the time of writing.
In-Memory Collections
The best way to teach new technologies is to just to show you an example and then explain
what the heck is going on! That will be my approach throughout this series; hopefully it is
a wise decision.
For our first example we will compose a query to retrieve all the items in a generic List
collection (Fig. 1).
Figure 1: Selecting all the items in a generic List collection
01.private static List<string> people = new List<string>()
02.{
03."Granville", "John", "Rachel", "Betty",
04."Chandler", "Ross", "Monica"
05.};
06.
07.public static void Example1()
08.{
09.IEnumerable<string> query = from p in people select p;
10.foreach (string person in query)
11.{
12.Console.WriteLine(person);
13.}
14.}
The code example given in Fig. 1 is very basic and its functionality could have been
replicated easier by simply enumerating through the items in the List via a foreach loop.
In Fig.1 we compose a query that will return each of the items in the people List collection by
aliasing the people collection with a variable p and then selecting p (p is of type string
remember as the people List is a collection of immutable string objects).
You may notice that query is of type IEnumerable<string> - this is because we know that query
will hold an enumeration of type string. When we foreach through the query the GetEnumerator
of query is invoked.
At this time it is beneficial to look at exactly what the compiler generated code looks like
(Fig. 2).
Figure 2: Compiler generated code for Fig. 1
01.public static void Example1()
02.{
03.IEnumerable<string> query = people.Select<string, string>(delegate (string p)
04.{
05.return p;
06.});
07.foreach (string person in query)
08.{
09.Console.WriteLine(person);
10.}
11.}
Fig. 2 reveals that our query has actually been converted by the compiler to use an
extension method (in this case just the Select extension method is used) taking a delegate as
its argument.
You will find that queries and lambda expressions are simply a facade that we deal with in
order to make our lives easier under the covers the compiler is generating the appropriate
code using delegates. Be aware of this internal compiler behavior!
Also be aware that a cached anonymous delegate method is generated at compile time as
well (Fig. 3) we will discuss this particular feature in future articles.
Figure 3: Compiler generated cached anonymous delegate method
1.[CompilerGenerated]
2.private static Func<string, string> <>9__CachedAnonymousMethodDelegate1;
We will now take a look at a more complex query of the same collection which retrieves a
sequence of all strings in the List whose length is greater than 5(Fig. 4).
Figure 4: A more complex query
01.public static void Example2()
02.{
The example in Fig. 4 relies on the use of two other standard query operators Where and
orderby to achieve the desired results.
If we examine the code generated by the compiler for the Example2 method you will see
that shown in Fig. 5 notice as well that we now have another two cached anonymous
delegate methods (Fig. 6) each of which having the type signature of their corresponding
delegates (Where delegate and orderby delegate).
Figure 5: Compiler generated code for Fig. 4
01.public static void Example2()
02.{
03.IEnumerable<string> query = people.Where<string>(delegate (string p)
04.{
05.return (p.Length > 5);
06.}).OrderBy<string, string>(delegate (string p)
07.{
08.return p;
09.});
10.foreach (string person in query)
11.{
12.Console.WriteLine(person);
13.}
14.}
Figure 6: Cached anonymous delegate methods for their respective Where and orderby delegates
defined in Fig. 5
1.[CompilerGenerated]
2.private static Func<string, bool> <>9__CachedAnonymousMethodDelegate4;
3.[CompilerGenerated]
4.private static Func<string, string> <>9__CachedAnonymousMethodDelegate5;
The type signature of the Where delegate (Fig. 5) is Func. The delegate takes a string argument
and returns a bool depending on whether the string was greater than 5 characters in length.
Similarly the orderby delegate (Fig. 5) takes a string argument and returns a string.
I encourage you to further explore the querying of collections I have blogged extensively
on the subject (http://gbarnett.org/archive/tags/LINQ/default.aspx).
Select
OrderBy
Where
SelectAll
TakeWhile
Take
Skip
First
SkipWhile
...
There are a tonne of standard query operators and I advise you to explore the use of each to
gain a richer understanding of how to deal with data.
We will cover many of the standard query operators in future parts of this series.
Lambda Expressions
provide a clearer syntax for anonymous delegates in this section we will
replicate the code in Fig. 4 by using lambda expressions and extension methods (Fig. 6).
Lambda expressions
Figure 6: Same example as Fig. 4 but using lambda expression and extension methods
1.public static void Example3()
2.{
3.IEnumerable<string> query = people.Where(x => x.Length > 5).OrderBy(x => x);
4.foreach (string person in query)
5.{
6.Console.WriteLine(person);
7.}
8.}
A lambda expression normally takes the form of arguments => expression, the expression is
always preceded by the => token. If you want to have a lambda expression with more than one
argument you must enclose the arguments in parentheses delimited by a comma.
If you noticed in Figs 2 and 5 the compiler generated code actually uses extension methods not
a query this is purely an implementation detail, much like the use of lambda expressions in
Fig. 6 will be converted to anonymous delegates by the compiler (Fig. 7).
Figure 7: Compiler generated code for Fig. 6
01.public static void Example2()
02.{
03.IEnumerable<string> query = people.Where<string>(delegate (string p)
04.{
05.return (p.Length > 5);
06.}).OrderBy<string, string>(delegate (string p)
07.{
08.return p;
09.});
Summary
In this article we looked at some examples of using LINQ with queries, and extension methods
as well as looking at the compiler generated code.
Please explore the standard query operator library and investigate the code generated by the
compiler as the next wave of language compilers inject an awful lot of code behind the
scenes that you will not be aware of unless you disassemble your code.
From early this year, I have started to write a series of articles to explain LINQ, LINQ to
SQL, Entity Framework, and LINQ to Entities. Followings are the articles I wrote or plan
to write for LINQ, LINQ to SQL, and LINQ to Entities:
And as I said, after finishing these five articles, I will come back to write some more
articles on WCF from my real work experience, which will be definitely helpful to your
real world work, if you are using WCF right now.
Overview
In the previous article (Introducing LINQLanguage Integrated Query), we learned the
new features of C# 3.0 including LINQ. In this article and the next, we will see how to use
LINQ to query a database, or in other words, how to use LINQ to Entities in C#. After
reading these two articles, you will have a good understanding of LINQ to Entities, so that
you can write the data access layer of your WCF service with LINQ to Entities, to securely,
and reliably communicate with the underlying database.
In this article, we will cover the basic concepts and features of LINQ to Entities, which
include:
What ORM is
Modeling the Northwind database with LINQ to EntitiesQuerying and updating a database
with a table
Deferred execution
In the next article, we will cover the advanced concepts and features of LINQ to Entities,
such as Stored Procedure support, inheritance, simultaneous updating, and transaction
processing.
ORMObject-Relational Mapping
LINQ to Entities is considered to be one of Microsoft's new ORM products. So before we
start explaining LINQ to Entities, let us first understand what ORM is.
ORM stands for Object-Relational Mapping. Sometimes it is called O/RM, or O/R
mapping. It is a programming technique that contains a set of classes that map relational
database entities to objects in a specific programming language.
Initially, applications could call specified native database APIs to communicate with a
database. For example, Oracle Pro*C is a set of APIs supplied by Oracle to query, insert,
update, or delete records in an Oracle database from C applications. The Pro*C precompiler translates embedded SQL into calls to the Oracle runtime library (SQLLIB).
Then, ODBC (Open Database Connectivity) was developed to unify all of the
communication protocols for various RDBMSs. ODBC was designed to be independent of
programming languages, database systems, and Operating Systems. So with ODBC, one
application can communicate with different RDBMSs by using the same code, simply by
replacing the underlying ODBC drivers.
No matter which method is used to connect to a database, the data returned from a database
has to be presented in some format in the application. For example, if an Order record is
returned from the database, there has to be a variable to hold the Order number, and a set of
variables to hold the Order details. Alternatively, the application may create a class for the
Orders, and another class for Order details. When another application is developed, the
same set of classes may have to be created again, or if it is designed well, they can be put
into a library, and re-used by various applications.
This is exactly where ORM fits in. With ORM, each database is represented by an ORM
context object in the specific programming language, and database entities such as tables
are represented by classes, with relationships between these classes. For example, the ORM
may create an Order class to represent the Order table, and an OrderDetail class to represent
the Order Details table. The Order class will contain a collection member to hold all of its
details. The ORM is responsible for the mappings and the connections between these
classes and the database. So, to the application, the database is now fully-represented by
these classes. The application only needs to deal with these classes, instead of with the
physical database. The application does not need to worry about how to connect to the
database, how to construct the SQL statements, how to use the proper locking mechanism
to ensure concurrency, or how to handle distributed transactions. These database-related
activities are handled by the ORM.
Entity Framework
Since LINQ to Entities is based on the Entity Framework, lets explain what Entity
Framework is now.
ADO.NET Entity Framework (EF) is a new addition to the Microsoft ADO.NET family. It
enables developers to create data access applications by programming against a conceptual
application model instead of programming directly against a relational storage schema. The
goal is to decrease the amount of code and maintenance required for data-oriented
applications. Entity Framework applications provide the following benefits:
Applications are freed from hard-coded dependencies on a particular data engine or storage
schema.
Mappings between the conceptual model and the storage-specific schema can change
without changing the application code.
Developers can work with a consistent application object model that can be mapped to
various storage schemas, possibly implemented in different database management systems.
With Entity Framework, developers work with a conceptual data model, an Entity Data
Model, or EDM, instead of the underlying databases. The conceptual data model schema is
expressed in the Conceptual Schema Definition Language (CSDL), the actual storage
model is expressed in the Storage Schema Definition Language (SSDL), and the mapping
in between is expressed in the Mapping Schema Language (MSL). A new data-access
provider, EntityClient, is created for this new framework but under the hood, the ADO.NET
data providers are still being used to communicate with the databases. The diagram below
shows the high level architectures of Entity Framework.
LINQ to Entities
Now lets have a look at what LINQ to Entities is.
LINQ to Entities provides Language-Integrated Query (LINQ) support that enables
developers to write queries against the Entity Framework conceptual model using Visual
Basic or Visual C#. Queries against the Entity Framework are represented by command tree
queries, which execute against the object context. LINQ to Entities converts Language-
Integrated Queries (LINQ) queries to command tree queries, executes the queries against
the Entity Framework, and returns objects that can be used by both the Entity Framework
and LINQ.
LINQ to Entities allows developers to create flexible, strongly-typed queries against the
Entity Data Model (EDM) by using LINQ expressions and standard LINQ query operators.
To certain degrees, LINQ to Entities is similar to LINQ to SQL, but LINQ to Entities is a
true ORM product from Microsoft, and it supports more features than LINQ to SQL, such
as multiple-table inheritance. LINQ to Entities also supports many other mainstream
RDBMSs such as Oracle, DB2, and MySQL in addition to Microsoft SQL Server.
LINQ to Entities needs an Object Context object. The ObjectContext object is the bridge
between LINQ and the database (we will explain more about ObjectContext later). LINQ to
Objects don't need any intermediate LINQ provider or API.
LINQ to Entities returns data of type IQueryable<T> whereas LINQ to Objects returns data
of type IEnumerable<T>.
LINQ to Entities queries are translated to SQL by way of Expression Trees, which allow
them to be evaluated as a single unit, and translated to appropriate and optimal SQL
Statements. LINQ to Objects queries do not need to be translated.
LINQ to Entities queries are translated to SQL calls and executed on the specified database
while LINQ to Objects queries are executed in the local machine memory.
The similarities shared by all aspects of LINQ are the syntax. They all use the same SQLlike syntax and share the same groups of standard query operators. From the language
syntax perspective, working with a database is the same as working with in-memory
objects.
LINQ to SQL
Before LINQ to Entities, Microsoft released another ORM product, which is LINQ to SQL.
Both LINQ to SQL and LINQ to Entities can be used in the data access layer to interact
with databases, but they are quite different. In this section, we will explain what LINQ to
SQL is, and in the next section, we will compare these two technologies.
In short, LINQ to SQL is a component of .NET Framework 3.5 that provides a run-time
infrastructure for managing relational data as objects.
In LINQ to SQL, the data model of a relational database is mapped to an object model
expressed in the programming language of the developer. When the application runs, LINQ
to SQL translates language-integrated queries in the object model into SQL, and sends them
to the database for execution. When the database returns the results, LINQ to SQL
translates the results back to objects that you can work with in your own programming
language.
Unlike LINQ to Entities, with LINQ to SQL, developers dont need to create an extra data
model between their applications and the underlying database. Under the hood of LINQ to
SQL, ADO.NET SqlClient adapters are used to communicate with the actual SQL Server
databases.
The following diagram shows the use of LINQ to SQL in a .NET application:
LINQ to SQL
LINQ to Entities
No
Yes
Storage Schema
No
Yes
Mapping Schema
No
Yes
No
Yes
No
Yes
Features
LINQ to SQL
LINQ to Entities
Yes
No
Yes
Yes
Stored Procedures
Yes
Yes
Single-table Inheritance
Yes
Yes
Multiple-table Inheritance
No
Yes
No
Yes
Yes
Yes
Interestingly, some say LINQ to SQL was an intermediate solution. Fact is that LINQ to
SQL was made by the C# team, instead of the ADO.NET team. It was of great importance
for the C# team to release an O/RM mapper together with their new LINQ technology.
Without a LINQ to databases implementation, the C# team would have a hard time
evangelizing LINQ.
In November 2008, the ADO.NET team announced that Microsoft will continue to make
some investments in LINQ to SQL, but they also made it pretty clear that LINQ to Entities
is the recommended data access solution in future frameworks. Microsoft will invest
heavily in the Entity Framework. So in this book, we will use LINQ to Entities in our data
access layer.
4. Click OK
Before you can create the EDM, you need to have a SQL Server database with the sample
database Northwind installed. You can just search "Northwind sample database download",
then download and install the sample database. If you need detailed instructions as how to
download/install the sample database, you can refer to the section "Preparing the Database"
in one of my previous articles, Implementing a WCF Service with Entity Framework".
Adding a LINQ to Entities Item to the Project
To start with, let us add a new item to our project TestLINQToEntitiesApp. The new item
added should be of type ADO.NET Entity Data Model, and named Northwind.edmx, as
shown in the following Add New Item dialog window:
After you click the Add button, the Entity Data Model Wizard window will pop up.
Follow these steps to finish this wizard:
1. On the Choose Model Contents page, select Generate from database. Later we will
connect to the Northwind database and let Visual Studio generate the conceptual data model
for us. If you choose the Empty model option here, you will have to manually create the
data model, which may be applicable sometimes, like you may not have a physical database
when you do the modeling. You may even create your physical database from your model
later if you choose this option and have finished your model.
2. Click Next on this window.
3. Now the Choose Your Data Connection window should be displayed. Since this is our
first LINQ to Entities application, there is no existing data connection to choose from, so
lets click button New Connection and set up a new data connection.
a. First choose Microsoft SQL Server as the data source, and leave .NET
Framework Data Provider for SQL Server as the data provider. Click OK to
close this window.
f.
4. On the Choose Your Database Objects page, select table Products, Categories, and view
Current Product List, then click Finish:
After you click Finish, the following two files will be added to the project:
Northwind.edmx and Northwind.designer.cs. The first file holds the model of the
entities, including the entity sets, entity types, conceptual models, and the mappings.
The second one is the code for the model, which defines the ObjectContext of the
model.
At this point, the Visual Studio LINQ to Entities designer should be open and as
shown in the following image:
If you open the file Northwind.Designer.cs (you need to switch from the Model Browser to
the Solution Explorer to open this file), you will find that the following classes have been
generated for the project:
public partial class NorthwindEntities : ObjectContext
public partial class Product : EntityObject
public partial class Category : EntityObject
public partial class Current_Product_List : EntityObject
In the above four classes, the NorthwindEntities class
property changed event methods, which we can extend to validate properties before and
after the change.
The last class is for the view. This is a simple class with only two property members.
Because we are not going to update the database through this view, it doesn't define any
property change or changed event method.
We can then use LINQ query syntax to retrieve records from the database:
IEnumerable<Product> beverages = from p in NWEntities.Products
where p.Category.CategoryName == "Beverages"
orderby p.ProductName
select p;
The preceding code will retrieve all of the products in the Beverages category, sorted by
product name.
You can use this statement to print out the total number of beverage products in the
Northwind database:
Console.WriteLine("There are {0} Beverages", beverages.Count());
Updating Records
We can update any of the products that we have just retrieved from the database, like this:
// update a product
Product bev1 = beverages.ElementAtOrDefault(10);
if (bev1 != null)
{
decimal newPrice = (decimal)bev1.UnitPrice + 10.00m;
Console.WriteLine("The price of {0} is {1}. Update to {2}",
bev1.ProductName, bev1.UnitPrice, newPrice);
bev1.UnitPrice = newPrice;
// submit the change to database
NWEntities.SaveChanges();
}
We used the ElementAtOrDefault method not the ElementAt method just in case there is no
product at element 10. We know that there are 12 beverage products in the sample database,
so we increase the 11th products price by 10.00 and call NWEntities.SaveChanges() to update
the record in the database. After you run the program, if you query the database, you will
find that the 11th beverages price is increased by 10.00.
Inserting Records
We can also create a new product and then insert this new product into the database, by
using the following code:
// add a product
Product newProduct = new Product {ProductName="new test product" };
NWEntities.Products.AddObject(newProduct);
NWEntities.SaveChanges();
Console.WriteLine("Added a new product with name 'new test product'");
Deleting Records
To delete a product, we first need to retrieve it from the database, and then call the
DeleteObject method, as shown in the following code:
// delete a product
IQueryable<Product> productsToDelete =
from p in NWEntities.Products
where p.ProductName == "new test product"
select p;
if (productsToDelete.Count() > 0)
{
foreach (var p in productsToDelete)
{
NWEntities.DeleteObject(p);
Console.WriteLine("Deleted product {0}", p.ProductID);
}
NWEntities.SaveChanges();
}
Note here that we used a variable of type IQueryable<Product>,
instead of IEnumerable<Product>,
to hold the result of the LINQ to Entities query. Since IQueryable extends the interface
IEnumerable, we can use either one of them, though with IQueryable, we can do much more as
we will see in the next section.
Running the Program
The file Program.cs has been used so far. Note that we added one method to contain all of
the test cases for table operations. We will add more methods later to test other LINQ to
Entities functionalities. Following is the content of this file now.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestLINQToEntitiesApp
{
class Program
{
static void Main(string[] args)
{
// CRUD operations on tables
TestTables();
Console.WriteLine("Press any key to continue ...");
Console.ReadKey();
}
static void TestTables()
{
NorthwindEntities NWEntities = new NorthwindEntities();
// retrieve all Beverages
IEnumerable<Product> beverages =
from p in NWEntities.Products
where p.Category.CategoryName == "Beverages"
orderby p.ProductName
select p;
Console.WriteLine("There are {0} Beverages",
beverages.Count());
// update one product
Product bev1 = beverages.ElementAtOrDefault(10);
if (bev1 != null)
{
decimal newPrice = (decimal)bev1.UnitPrice + 10.00m;
Console.WriteLine("The price of {0} is {1}.
Update to {2}",
bev1.ProductName, bev1.UnitPrice, newPrice);
bev1.UnitPrice = newPrice;
}
// submit the change to database
NWEntities.SaveChanges();
// insert a product
Product newProduct = new Product { ProductName =
"new test product" };
NWEntities.Products.AddObject(newProduct);
NWEntities.SaveChanges();
Console.WriteLine("Added a new product");
// delete a product
IQueryable<Product> productsToDelete =
from p in NWEntities.Products
where p.ProductName == "new test product"
select p;
if (productsToDelete.Count() > 0)
{
foreach (var p in productsToDelete)
{
NWEntities.DeleteObject(p);
Console.WriteLine("Deleted product {0}",
p.ProductID);
}
NWEntities.SaveChanges();
}
NWEntities.Dispose();
}
}
}
First lets write a new test method to contain LINQ to SQL queries:
static void ViewGeneratedSQL()
{
NorthwindEntities NWEntities = new NorthwindEntities();
IQueryable<Product> beverages =
from p in NWEntities.Products
where p.Category.CategoryName == "Beverages"
orderby p.ProductName
select p;
NWEntities.Dispose();
}
As we have learned from the previous section, the variable beverages is of type
IQueryable<Product>, which is a derived class of type IEnumerable<Product>. Actually, this type
is also a subtype of System.Data.Objects.ObjectQuery<Product>, which has a method ToTraceString
we can use to view the generated SQL statements. To make it easier for us to call the
ObjectQuery.ToTraceString method, we now define an extension method like this:
Note that this extension method is inside a non-generic static class MyEntensions, and we put
this class inside the namespace TestLINQToEntitiesApp, which is the same namespace of our
test class, so we can use it inside our test method without worrying about importing its
namespace.
Now we can print out the SQL statement of the LINQ to Entities query using this
statement:
// view SQL using ToTraceString method
Console.WriteLine("The SQL statement is:" + beverages.ToTraceString());
and we also need to add a using statement to import the namespace for
using System.Data.Objects;
Run this program, and you will see the following output:
With the ToTraceString method, we can view the generated SQL statements for some LINQ to
Entities expressions, but not all of them. For example, when we add a new product to the
database, or when we execute a Stored Procedure in the database, there is no IQueryable
object for us to use to view the generated SQL statements. In this case, we can use the SQL
profiler to view the SQL statements. But if you go to view the generated SQL statements
for the above query, you may be confused, as there is no SQL statement displayed in SQL
profiler. So we will not explain the steps to view the SQL statements in the Profiler here,
but we will explain it in the next section, together with the explanation of another important
LINQ to Entities feature, deferred execution.
Deferred Execution
One important thing to remember when working with LINQ to Entities is the deferred
execution of LINQ.
The standard query operators differ in the timing of their execution, depending on whether
they return a singleton value or a sequence of values. Those methods that return a singleton
value (for example, Average and Sum) execute immediately. Methods that return a sequence
defer the query execution, and return an enumerable object. These methods do not consume
the target data until the query object is enumerated. This is known as deferred execution.
In the case of the methods that operate on in-memory collections, that is, those methods
that extend IEnumerable<(Of <(T>)>), the returned enumerable object captures all of the
arguments that were passed to the method. When that object is enumerated, the logic of the
query operator is employed, and the query results are returned.
In contrast, methods that extend IQueryable<(Of <(T>)>) do not implement any querying
behavior, but build an expression tree that represents the query to be performed. The query
processing is handled by the source IQueryable<(Of <(T>)>) object.
Checking Deferred Execution With SQL Profiler
To test the deferred execution of LINQ to Entities, lets first add the following method to
our program.cs file:
static void TestDeferredExecution()
{
NorthwindEntities NWEntities = new NorthwindEntities();
// SQL is not executed
IQueryable<Product> beverages =
from p in NWEntities.Products
where p.Category.CategoryName == "Beverages"
orderby p.ProductName
select p;
// SQL is executed on this statement
Console.WriteLine("There are {0} Beverages",
beverages.Count());
NWEntities.Dispose();
}
Call this method from the Main method of the program,
3. Go back to Visual Studio, set a break point on the first line of the TestDeferredExecution
method.
4. Press F5 to start debugging the program.
The program is now running, and the cursor should be stopped on the first line of the
method. Press F10 to move to the next line of code, and press F10 again to step over this
line of code:
IQueryable<Product> beverages =
from p in NWEntities.Products
where p.Category.CategoryName == "Beverages"
orderby p.ProductName
select p;
Switch to the Profiler, you will find that there is nothing in there.
However, when you press F10 in Visual Studio and before the following statement is
executed, you will see from the Profiler that a query has been executed in the database:
Console.WriteLine("There are {0} Beverages", beverages.Count());
From the Profiler, we know that, under the hood, LINQ actually first creates a sub query to
get the total beverage products count, then gets this count from the sub query result. It also
uses an inner join to get the categories of products.
Note that with LINQ to SQL, we can set the DataContext objects Log property to Console.Out,
then view the generated SQL statements from the standard output for all subsequent LINQ
to SQL expressions. Unfortunately, with LINQ to Entities, the ObjectContext does not have
such a property to let us view generated SQL statements. We have to use either ToTraceString
or the Profiler to view the generated SQL statements.
Deferred Execution for Singleton Methods
If the query expression will return a singleton value, the query will be executed as soon as it
is defined. For example, we can add this statement to our test deferred execution method to
get the average price of all products:
// SQL is executed on this statement
decimal? averagePrice = (from p in NWEntities.Products
select p.UnitPrice).Average();
Console.WriteLine("The average price is {0}", averagePrice);
Start SQL Profiler, then press F5 to start debugging the program. When the cursor is
stopped on the line to print out the average price, from the Profiler window, we see a query
has been executed to get the average price, and when the printing statement is being
executed, no more query is executed in the database.
The Profiler window is like this:
However, just because a query is using one of the singleton methods such as sum, average,
or count, this doesn't mean that the query will be executed as soon as it is defined. If the
query result is a sequence, the execution will still be deferred. The following is an example
of this kind of query:
// SQL is not executed even there is a singleton method
var cheapestProductsByCategory =
from p in NWEntities.Products
group p by p.CategoryID into g
select new
{
CategoryID = g.Key,
CheapestProduct =
(from p2 in g
where p2.UnitPrice == g.Min(p3 => p3.UnitPrice)
select p2).FirstOrDefault()
};
// SQL is executed on this statement
Console.WriteLine("Cheapest products by category:");
foreach (var p in cheapestProductsByCategory)
{
Console.WriteLine("categery {0}: product name: {1} price: {2}",
p.CategoryID, p.CheapestProduct.ProductName,
p.CheapestProduct.UnitPrice);
}
Start SQL Profiler, then press F5 to start debugging the program. When the cursor is
stopped on the beginning of the foreach line, from the Profiler, we dont see the query
statement to get the minimum price for any product. When we press F10 again, the cursor
is stopped on the variable cheapestProductsByCategory within the foreach line of code, but we
still dont see the query statement to get the cheapest products.
Then after we press F10 again, the cursor is stopped on the in keyword within the foreach
line of code, and this time from the Profiler, we see the query is executed.
The actual SQL statements for this LINQ to Entities expression are like this:
SELECT
1 AS [C1],
[GroupBy1].[K1] AS [CategoryID],
[Limit1].[ProductID] AS [ProductID],
[Limit1].[ProductName] AS [ProductName],
[Limit1].[SupplierID] AS [SupplierID],
[Limit1].[CategoryID] AS [CategoryID1],
[Limit1].[QuantityPerUnit] AS [QuantityPerUnit],
[Limit1].[UnitPrice] AS [UnitPrice],
[Limit1].[UnitsInStock] AS [UnitsInStock],
[Limit1].[UnitsOnOrder] AS [UnitsOnOrder],
[Limit1].[ReorderLevel] AS [ReorderLevel],
[Limit1].[Discontinued] AS [Discontinued]
FROM (SELECT
[Extent1].[CategoryID] AS [K1],
MIN([Extent1].[UnitPrice]) AS [A1]
FROM [dbo].[Products] AS [Extent1]
GROUP BY [Extent1].[CategoryID] ) AS [GroupBy1]
OUTER APPLY (SELECT TOP (1)
[Extent2].[ProductID] AS [ProductID],
[Extent2].[ProductName] AS [ProductName],
[Extent2].[SupplierID] AS [SupplierID],
[Extent2].[CategoryID] AS [CategoryID],
[Extent2].[QuantityPerUnit] AS [QuantityPerUnit],
[Extent2].[UnitPrice] AS [UnitPrice],
[Extent2].[UnitsInStock] AS [UnitsInStock],
[Extent2].[UnitsOnOrder] AS [UnitsOnOrder],
[Extent2].[ReorderLevel] AS [ReorderLevel],
[Extent2].[Discontinued] AS [Discontinued]
FROM [dbo].[Products] AS [Extent2]
WHERE (([GroupBy1].[K1] = [Extent2].[CategoryID]) OR (([GroupBy1].[K1] IS NULL)
AND ([Extent2].[CategoryID] IS NULL)))
AND ([Extent2].[UnitPrice] = [GroupBy1].[A1]) ) AS [Limit1]
From this output, you can see that when the variable cheapestProductsByCategory
is accessed, it
first calculates the minimum price for each category. Then, for each category, it returns the
first product with that price. In a real application, you probably wouldn't want to write such
a complex query in your code, instead, you may want to put it in a Stored Procedure, which
we will discuss in the next article.
The test method is like this:
static void TestDeferredExecution()
{
NorthwindEntities NWEntities = new NorthwindEntities();
// SQL is not executed
IQueryable<Product> beverages =
from p in NWEntities.Products
where p.Category.CategoryName == "Beverages"
orderby p.ProductName
select p;
// SQL is executed on this statement
Console.WriteLine("There are {0} Beverages",
beverages.Count());
// SQL is executed on this statement
decimal? averagePrice = (from p in NWEntities.Products
select p.UnitPrice).Average();
Console.WriteLine("The average price is {0}", averagePrice);
// SQL is not executed even there is a singleton method
var cheapestProductsByCategory =
from p in NWEntities.Products
group p by p.CategoryID into g
select new
{
CategoryID = g.Key,
CheapestProduct =
(from p2 in g
If you comment out all other test methods (TestTables and ViewGeneratedSQL) and run the
program, you should get an output similar to the following image:
Even though there is no such field called categoryname in the Products table, we can still
get the category name of a product because there is an association between the Products
and Category tables. In the Northwind.edmx design pane, click on the line that connects the
Products table and the Categories table and you will see all of the properties of the
association. Note that its Referential Constraint properties are Category.CategoryID ->
Product.CategoryID, meaning that category ID is the key field to link these two tables.
Because of this association, we can retrieve the category for each product, and on the other
hand, we can also retrieve products for each category.
However, even with an association, the associated data is not loaded when the query is
executed. For example, suppose we use the following test method to retrieve all of the
categories, then access the products for each category:
static void TestAssociation()
{
NorthwindEntities NWEntities = new NorthwindEntities();
var categories = from c in NWEntities.Categories select c;
foreach (var category in categories)
{
Console.WriteLine("There are {0} products in category {1}",
category.Products.Count(), category.CategoryName);
}
NWEntities.Dispose();
}
Start SQL Profiler then press F5 to start debugging the program. When the cursor is
stopped on the foreach line (after you press F10 twice to move the cursor to the in keyword),
from the Profiler, we see this SQL statement:
SELECT
[Extent1].[CategoryID] AS [CategoryID],
[Extent1].[CategoryName] AS [CategoryName],
[Extent1].[Description] AS [Description],
[Extent1].[Picture] AS [Picture]
FROM [dbo].[Categories] AS [Extent1]
When you press F10 to execute the printout line, from the Profiler, we see this SQL
statement:
exec sp_executesql N'SELECT
[Extent1].[ProductID] AS [ProductID],
[Extent1].[ProductName] AS [ProductName],
[Extent1].[SupplierID] AS [SupplierID],
[Extent1].[CategoryID] AS [CategoryID],
[Extent1].[QuantityPerUnit] AS [QuantityPerUnit],
[Extent1].[UnitPrice] AS [UnitPrice],
[Extent1].[UnitsInStock] AS [UnitsInStock],
[Extent1].[UnitsOnOrder] AS [UnitsOnOrder],
[Extent1].[ReorderLevel] AS [ReorderLevel],
[Extent1].[Discontinued] AS [Discontinued]
FROM [dbo].[Products] AS [Extent1]
WHERE [Extent1].[CategoryID] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1
From these SQL statements, we know that Entity Framework first goes to the database to
query all of the categories. Then, for each category, when we need to get the total count of
products, it goes to the database again to query all of the products for that category.
This is because by default lazy loading is set to true, meaning that the loading of all
associated data (children) is deferred until the data is needed.
To change this behavior, we can use the Include method to tell the ObjectContext to
automatically load the specified children during the initial query:
static void TestEagerLazyLoading()
{
NorthwindEntities NWEntities = new NorthwindEntities();
// eager loading products of categories
var categories = from c
in NWEntities.Categories.Include("Products")
select c;
foreach (var category in categories)
{
Console.WriteLine("There are {0} products in category {1}",
category.Products.Count(), category.CategoryName);
}
NWEntities.Dispose();
}
As you can see, inside this test method, when constructing the LINQ to Entities query, we
added an Include clause to tell the framework to load all products when loading the
categories.
To test it, start SQL Profiler, then press F5 to start debugging the program. When the cursor
is stopped on the foreach line (at the in keyword), from the Profiler, you will see this SQL
statement:
SELECT
[Project1].[CategoryID] AS [CategoryID],
[Project1].[CategoryName] AS [CategoryName],
[Project1].[Description] AS [Description],
[Project1].[Picture] AS [Picture],
[Project1].[C1] AS [C1],
[Project1].[ProductID] AS [ProductID],
[Project1].[ProductName] AS [ProductName],
[Project1].[SupplierID] AS [SupplierID],
[Project1].[CategoryID1] AS [CategoryID1],
[Project1].[QuantityPerUnit] AS [QuantityPerUnit],
[Project1].[UnitPrice] AS [UnitPrice],
[Project1].[UnitsInStock] AS [UnitsInStock],
[Project1].[UnitsOnOrder] AS [UnitsOnOrder],
[Project1].[ReorderLevel] AS [ReorderLevel],
[Project1].[Discontinued] AS [Discontinued]
FROM ( SELECT
[Extent1].[CategoryID] AS [CategoryID],
[Extent1].[CategoryName] AS [CategoryName],
[Extent1].[Description] AS [Description],
[Extent1].[Picture] AS [Picture],
[Extent2].[ProductID] AS [ProductID],
[Extent2].[ProductName] AS [ProductName],
[Extent2].[SupplierID] AS [SupplierID],
[Extent2].[CategoryID] AS [CategoryID1],
[Extent2].[QuantityPerUnit] AS [QuantityPerUnit],
[Extent2].[UnitPrice] AS [UnitPrice],
[Extent2].[UnitsInStock] AS [UnitsInStock],
[Extent2].[UnitsOnOrder] AS [UnitsOnOrder],
[Extent2].[ReorderLevel] AS [ReorderLevel],
[Extent2].[Discontinued] AS [Discontinued],
CASE WHEN ([Extent2].[ProductID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
FROM [dbo].[Categories] AS [Extent1]
LEFT OUTER JOIN [dbo].[Products] AS [Extent2] ON
[Extent1].[CategoryID] = [Extent2].[CategoryID]
) AS [Project1]
ORDER BY [Project1].[CategoryID] ASC, [Project1].[C1] ASC
As you can see from this SQL statement, all products for all categories are loaded during
the first query.
In addition to pre-loading one child entity, with the Include method, you can also traverse
multiple child entities together. For example, you can use Include(Products.Orders) to preload
products and orders for all categories, if Orders is also added as an Entity to the model. You
can also chain multiple Includes to preload multiple child entities on the same level, like
Customers.Include(Orders).Include(Contacts) if there is a Contacts table for customers, and
customers, orders, and contacts are all added as entities to the model.
Note that with LINQ to SQL, you can set associations and eager loading configurations
with DataLoadOptions, and you can even pre-load some objects with conditions, but with
LINQ to Entities, you dont have any other choice. You have to pre-load an entity entirely.
Another difference between LINQ to SQL and LINQ to Entities is, with LINQ to SQL you
have strong typed load options for eager loading, like LoadWith<Category>, but with LINQ to
Entities, you have to put the entity names within a string expression, which might cause a
run time exception if you make a mistake in the entity names.
This is not so useful in the above example, because the tables Products and Categories are
associated with a foreign key relationship. If there is no foreign key association between the
two tables, or if we hadnt added the associations between these two tables, this will be
particularly useful.
From the following SQL statement, we can see that only one query is executed to get the
results:
SELECT
[Extent1].[CategoryID] AS [CategoryID],
[Extent1].[CategoryName] AS [CategoryName],
(SELECT
COUNT(1) AS [A1]
FROM [dbo].[Products] AS [Extent2]
WHERE [Extent1].[CategoryID] = [Extent2].[CategoryID]) AS [C1]
FROM [dbo].[Categories] AS [Extent1]
Querying a View
Querying a View is the same as querying a table. For example, you can query the View
"current product lists" like this:
static void TestView()
{
NorthwindEntities NWEntities = new NorthwindEntities();
var currentProducts = from p
in NWEntities.Current_Product_Lists
select p;
foreach (var p in currentProducts)
{
Console.WriteLine("Product ID: {0} Product Name: {1}",
p.ProductID, p.ProductName);
}
NWEntities.Dispose();
}
This will get all of the current products, using the View.
Summary
In this article, we have learned what an ORM is, why we need an ORM, and what LINQ to
Entities is. We also compared LINQ to SQL with LINQ to Entities, and explored some
basic features of LINQ to Entities.
The key points covered in this article include:
LINQ to Entities is one of Microsoft's ORM products that uses LINQ against a .NET
Conceptual Entity Model
The built-in LINQ to Entities designer in Visual Studio 2010 can be used to model the
Conceptual Entity Model
You can generate the Conceptual Entity Model from a physical database in Visual Studio
2010 Entity Model designer
LINQ methods that return a sequence defer the query execution and you can check the
timing of the execution of a query with Profiler
LINQ query expressions that return a singleton value will be executed as soon as they are
defined
By default, the loading of associated data is deferred (lazy loading); you can change this
behavior with the Include method
The Join operator can be used to join multiple tables and Views
Views can be used to query a database in LINQ to Entities in the same way as for tables
Note
This article is based on Chapter 7 of my book "WCF 4.0 Multi-tier Services Development
with LINQ to Entities" (ISBN 1849681147). This book is a hands-on guide to learn how to
build SOA applications on the Microsoft platform using WCF and LINQ to Entities. It is
updated for VS2010 from my previous book: WCF Multi-tier Services Development with
LINQ.
With this book, you can learn how to master WCF and LINQ to Entities concepts by
completing practical examples and applying them to your real-world assignments. This is
the first and only book to combine WCF and LINQ to Entities in a multi-tier real-world
WCF Service. It is ideal for beginners who want to learn how to build scalable, powerful,
easy-to-maintain WCF Services. This book is rich with example code, clear explanations,
interesting examples, and practical advice. It is a truly hands-on book for C++ and C#
developers.
You don't need to have any experience in WCF or LINQ to Entities to read this book.
Detailed instructions and precise screenshots will guide you through the whole process of
exploring the new worlds of WCF and LINQ to Entities. This book is distinguished from
other WCF and LINQ to Entities books by that, this book focuses on how to do it, not why
to do it in such a way, so you won't be overwhelmed by tons of information about WCF and
LINQ to Entities. Once you have finished this book, you will be proud that you have been
working with WCF and LINQ to Entities in the most straightforward way.
You can buy this book from Amazon, or from the publisher's website at
https://www.packtpub.com/wcf-4-0-multi-tier-services-development-with-linq-toentities/book.