I. Introduction
II. Pre-requisites
- The reader of this article should be aware of the LINQ term.
- The reader should have basic knowledge of SQL.
- The reader should know how to code in C#.
III. Overview
- LINQ is a uniform programming model for any kind of data. LINQ enables you to query and manipulate data with a consistent model that is independent from data sources.
- LINQ is just another tool for embedding SQL queries into code.
- LINQ is yet another data abstraction layer.
IV. What Is LINQ?
var query = from c in Customers where c.Country == "Italy" select c.CompanyName;
V. How LINQ Works Internally
Customer[] Customers = GetCustomers(); var query = from c in Customers where c.Country == "Italy“ select c;
Customer[] Customers = GetCustomers(); IEnumerable query = Customers .Where( c => c.Country == "Italy" );
var query = from c in Customers where c.Country == "Italy“ orderby c.Name select new { c.Name, c.City };
var query = Customers.Where( c => c.Country == "Italy" ).OrderBy( c => c.Name ).Select ( c => new { c.Name, c.City } );
1. var query = from c in Customers ... foreach ( string name in query ) ...
2. var query = from c in Customers …
List customers = query.ToList();
VI. Relational Model vs. Hierarchical/Graph Model
- In a relational model:
.
var query = from c in Customers join o in Orders on c.CustomerID equals o.CustomerID select new { c.CustomerID, c.CompanyName, o.OrderID };
- In a hierarchical model:
.
var query = from c in Customers from o in c.Orders select new { c.Name, o.Quantity, o.Product. ProductName };
A. Type declarations with simple relationships
class Customer { public string Name; public string City;
public Order[] Orders;
}
public class Order
{
public int Quantity;
public Product Product;
}
public class Product
{
public int ProductId;
public decimal Price;
public string ProductName;
}
B. Type declarations with two-way relationships
public class Customer { public string Name; public string City; public Order[] Orders; }
public class Order
{
public int Quantity;
public Product Product;
public Customer Customer;
}
public class Product
{
public int ProductId;
public decimal Price;
public string ProductName;
public Order[] Orders;
}
C. Querying the graph of objects
var query = from p in products where p.ProductId == 3 from o in p.Orders select o;
- If you have entity relationships in your data model, you can still use explicit relationships in a LINQ:
.
var query = from c in Customers join s in Suppliers on c.City equals s.City select new { c.City, c.Name, SupplierName = s.Name };
And something like the following will be returned:
.City=Torino Name=Marco SupplierName=Trucker City=Dallas Name=James SupplierName=FastDelivery City=Dallas Name=James SupplierName=Horizon City=Seattle Name=Frank SupplierName=WayFaster
- LINQ can return a hierarchy or graph of objects for a SQL query that contains several entities with one or more one-to-many relationships:
.
var query = from c in Customers join s in _ Suppliers on c.City equals s.City into customerSuppliers select new { c.City, c.Name, customerSuppliers };
Here is how the hierarchized results might appear:
.City=Torino Name=Marco customerSuppliers=... customerSuppliers: Name=Trucker City=Torino City=Dallas Name=James customerSuppliers=... customerSuppliers: Name=FastDelivery City=Dallas customerSuppliers: Name=Horizon City=Dallas City=Seattle Name=Frank customerSuppliers=... customerSuppliers: Name=WayFaster City=Seattle
- LINQ requires to describe your data in terms of entities that are also types in the language.
- A LINQ query, it is always a set of operations on instances of some classes.
- These objects might be the real container of data, or they might be a simple description (in terms of metadata) of the external entity one is going to manipulate.
- A query can be sent to a database through an SQL command only if it is applied to a set of types that map tables and relationships contained in the database.
- The conversion of all these operations in SQL commands is the responsibility of the LINQ engine.
- Class declaration mapped on a database table:
[Table("Products")] public class Product {
[Column(IsPrimaryKey=true)]
public int IdProduct;
[Column(Name=“UnitPrice”)]
public decimal Price;
[Column()]
public string ProductName;
[Column()]
public bool Taxable;
[Column()]
public decimal Tax;
}
D. Database update calling the SubmitChanges() method
var taxableProducts = from p in db.Products where p.Taxable == true select p;
foreach( Product product in taxableProducts ) { RecalculateTaxes( product ); }
db.SubmitChanges();
VII. XML Manipulation
<?xml version="1.0" encoding="utf-8" ?> <orders xmlns="http://schemas.devleap.com/Orders"> <order idCustomer="ALFKI" idProduct="1" quantity="10" price="20.59"/> <order idCustomer="ANATR" idProduct="5" quantity="20" price="12.99"/> <order idCustomer="KOENE" idProduct="7" quantity="15" price="35.50"/> </orders>
A. Reading the XML file of orders using an XmlReader
String nsUri = "http://schemas.devleap.com/Orders"; XmlReader xmlOrders = XmlReader.Create( "Orders.xml" ); List orders = new List();
Order order = null;
while (xmlOrders.Read())
{
switch (xmlOrders.NodeType)
{
case XmlNodeType.Element:
if ((xmlOrders.Name == “order”) && (xmlOrders.NamespaceURI == nsUri))
{
order = new Order();
order.CustomerID = xmlOrders.GetAttribute( “idCustomer” );
order.Product = new Product();
order.Product.IdProduct = Int32.Parse
( xmlOrders.GetAttribute( “idProduct” ) );
order.Product.Price = Decimal.Parse
( xmlOrders.GetAttribute( “price” ) );
order.Quantity = Int32.Parse
( xmlOrders.GetAttribute( “quantity” ) );
orders.Add( order );
}
break;
}
}
B. An XQuery like the following one to select nodes
for $order in document("Orders.xml")/orders/order return $order
C. Reading the XML file using LINQ to XML
XDocument xmlOrders = XDocument.Load( "Orders.xml" ); XNamespace ns = "http://schemas.devleap.com/Orders"; var orders = from o in xmlOrders.Root.Elements( ns + "order" ) select new Order { CustomerID = (String)o.Attribute( "idCustomer" ), Product = new Product { IdProduct = (Int32)o.Attribute("idProduct"), Price = (Decimal)o.Attribute("price") }, Quantity = (Int32)o.Attribute("quantity") };
VIII. Language Integration
var query = from c in Customers where c.Country == "Italy" orderby c.Name select new { c.Name, c.City };
var query = Customers .Where( c => c.Country == "Italy" ) .OrderBy( c => c.Name ) .Select( c => new { c.Name, c.City } );
- LINQ enables a more declarative style of coding for C# and Visual Basic.
- In SQL, we describe what you want. In C#, we describe how to obtain the expected result.
- Declarative programming can take advantage of services offered by compilers and frameworks, and in general, it is easier to read and maintain.
- This single “feature” can be the most important one because it boosts programmers’ productivity.
IX. Type Checking
X. Transparency Across Different Type Systems
XI. LINQ Flavors

XII. LINQ to Objects
System.Linq
namespace.XIII. LINQ to ADO.NET
- LINQ to SQLHandles the mapping between custom types in C# and the physical table schema
- LINQ to EntitiesIs in many ways similar to LINQ to SQL. However, instead of using the physical database as a persistence layer, it uses a conceptual Entity Data Model (EDM). The result is an abstraction layer that is independent from the physical data layer.
- LINQ to DataSetMakes it possible to query a DataSet using LINQ
XIV. LINQ to XML
var book = new XElement( "Book", new XAttribute( "Title", "Introducing LINQ" ), from person in team where person.Role == "Author“ select new XElement( "Author", person.Name ) );
XV. C#2 and C#3 Important Language Features
1. C# 2.0 Revisited – Generics
Min()
function of C# for example,Problem
Min
function, following is the issue:int Min( int a, int b ) { if (a < b) return a; else return b; } object Min( object a, object b ) { if (a < b) return a; else return b; } IComparable Min( IComparable a, IComparable b ) { if (a.CompareTo( b ) < 0) return a; else return b; } int a = 5, b = 10; int c = (int) Min( a, b );
Min
function:Solution
T Min( T a, T b ) where T : IComparable { if (a.CompareTo( b ) < 0) return a; else return b; }
2. C# 2.0 Revisited – Delegates
delegate
is a class that encapsulates one or more method. Internally, one delegate
stores a list of method pointers, each of which can be paired with a reference to an instance of the class containing an instance method.delegate
can be declared as:delegate void SimpleDelegate(); delegate int ReturnValueDelegate(); delegate void TwoParamsDelegate( string name, int age );
- In C# 1.X,
Delegate
s were instantiated as:.public class DemoDelegate { void MethodA() { … } int MethodB() { … }
void MethodC( string x, int y ) { … }
void CreateInstance() {
SimpleDelegate a = new SimpleDelegate( MethodA );
ReturnValueDelegate b = new ReturnValueDelegate ( MethodB );TwoParamsDelegate c = new TwoParamsDelegate(
MethodC ); // … } }
Delegate
instantiation in C# 2.0:.public class DemoDelegate { void MethodA() { … }
int MethodB() { … }
void MethodC( string x, int y ) { … }
void CreateInstance() {
SimpleDelegate a = MethodA;
ReturnValueDelegate b = MethodB;
TwoParamsDelegate c = MethodC; // …
}// … }
3. C# 2.0 Revisited – Anonymous Methods
- Using an anonymous method:
.
public class DemoDelegate { void Repeat10Times( SimpleDelegate someWork ) { for (int i = 0; i < 10; i++) someWork(); } void Run2() { int counter = 0; this.Repeat10Times( delegate { Console.WriteLine("C# chapter" ); counter++; } ); Console.WriteLine( counter ); } // … }
- Parameters for an anonymous method:
.
public class DemoDelegate {
void Repeat10Times( TwoParamsDelegate callback )
{ for (int i = 0; i < 10; i++)callback( “Linq book”, i ); }
void Run3() { Repeat10Times(
delegate( string text, int age )
{ Console.WriteLine( “{0} {1}”, text, age ); } );
}
// … }
4. C# 2.0 Revisited – Enumerators and Yield
a. IEnumerator and IEnumerable declarations
public interface IEnumerator { bool MoveNext(); object Current { get; } void Reset(); }
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
yield
statement through which the compiler automatically generates a class that implements the IEnumerator
interface returned by the GetEnumerator
method. The yield
statement can be used only immediately before a return
or break
keyword. Enumeration using a yield
statement:public class CountdownYield : IEnumerable { public int StartCountdown;
public IEnumerator GetEnumerator() {
for (int i = StartCountdown – 1; i >= 0; i–)
{
yield return i; }
}
}
b. Multiple yield statements
public class CountdownYieldMultiple : IEnumerable { public IEnumerator GetEnumerator() {
yield return 4;
yield return 3;
yield return 2;
yield return 1;
yield return 0;
}
}
c. Enumeration using yield (typed)
public class CountdownYieldTypeSafe : IEnumerable<int> { public int StartCountdown;
IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
public IEnumerator<int> GetEnumerator() {
for (int i = StartCountdown – 1; i >= 0; i–)
{ yield return i; }
}
}
5. C# 3.0 Features – Local Type Inference
var
keyword instead of a specific type. This might seem to be equivalent to defining a variable of type object, but it is not.var a = 2; //a is declared as int object b = 2; // Boxing an int into an object int C = a; // No cast, no unboxing int D = (int)b // Cast is required, an unboxing is done
- When
var
is used, the compiler infers the type from the expression used to initialize the variable. The compiled IL code contains only the inferred type. - In other words, consider this code:
.
int a = 5; var b = a;
- It is perfectly equivalent to this example:
.
int a = 5; int b = a;
- The
var
keyword can be used only within a local scope. - In other words, a local variable can be defined in this way, but not a member or a parameter.
- The following code shows some examples of valid uses of
var
:.public void ValidUse( decimal d ) { var x = 2.3; // double var y = x; // double var r = x / y; // double var s = "sample"; // string var l = s.Length; // int var w = d; // decimal var p = default(string); // string }
- The sample shows some cases in which the
var
keyword is not allowed:.class VarDemo {
// invalid token ‘var’ in class, struct or interface member declaration
var k =0;// type expected in parameter list
public void InvalidUseParameter( var x ){} // type expected in result type declaration
public var InvalidUseResult() { return 2; }public void InvalidUseLocal() {
var x; // Syntax error, ‘=’ expected
var y = null; // Cannot infer local variable type from ‘null’
}
// … }
6. C# 3.0 Features – Lambda Expressions
• ( int a, int b ) => { return a + b; } // Explicitly typed, statement body • ( int a, int b ) => a + b; // Explicitly typed, expression body • ( a, b ) => { return a + b; } // Implicitly typed, statement body • ( a, b ) => a + b // Implicitly typed, expression body • ( x ) => sum += x // Single parameter with parentheses • x => sum += x // Single parameter no parentheses • () => sum + 1 // No parameters
// Predicate age ) => age > 21
// Projection: takes a string and returns an int ( s ) => s.Length
- Lambda expression as a predicate:
.
public static void Demo() { string[] names = { "Marco", "Paolo", "Tom" }; Display( names, s => s.Length > 4 ); } public static void Display( T[] names, Func<T, bool> filter ){ foreach( T s in names) { if (filter( s )) Console.WriteLine( s ); } }
- A lambda expression can also be assigned to a variable of these delegate types:
.
public delegate T Func(); public delegate T Func( A0 arg0 ); public delegate T Func( A0 arg0, A1 arg1 ); public delegate T Func( A0 arg0, A1 arg1, A2 arg2 ); public delegate T Func( A0 arg0, A1 arg1, A2 arg2, A3 arg3 );
7. C# 3.0 Features – Extension Methods
public
members of the type itself, just as you can do from any piece of code outside the target type.static
and public
, must be declared inside a static
class, and must have the keyword this
before the first parameter type, which is the type that the method extends.public
because they can be (and normally are) called from outside the class where they are declared.FormattedUS
and FormattedIT
) that converts a decimal value into a string
formatted with a specific culture:static class Traditional { public static void Demo() { decimal x = 1234.568M; Console.WriteLine( FormattedUS( x ) );
Console.WriteLine( FormattedIT( x ) );
}
public static string FormattedUS( decimal d ) {
return String.Format( formatIT, “{0:#,0.00}”, d );
}
public static string FormattedIT( decimal d ) {
return String.Format( formatUS, “{0:#,0.00}”, d );
}
static CultureInfo formatUS = new CultureInfo( “en-US” );
static CultureInfo formatIT = new CultureInfo( “it-IT” );
}
a. Extension methods declaration
using System; using System.Globalization;
internal static class ExtensionMethods
{
public static void Demo()
{
decimal x = 1234.568M;
Console.WriteLine(x.FormattedUS());
Console.WriteLine(x.FormattedIT());
Console.WriteLine(FormattedUS(x)); // Traditional call allowed
Console.WriteLine(FormattedIT(x));// Traditional call allowed
}
private static CultureInfo formatUS = new CultureInfo(“en-US”);
private static CultureInfo formatIT = new CultureInfo(“it-IT”);
public static string FormattedUS(this decimal d)
{
return String.Format(formatIT, “{0:#,0.00}”, d);
}
public static string FormattedIT(this decimal d)
{
return String.Format(formatUS, “{0:#,0.00}”, d);
}
}
b. Extension methods for native value types
static class ExtensionMethods { public static decimal Double( this decimal d ) { return d + d; } public static decimal Triple( this decimal d ) { return d * 3; } public static decimal Increase( this decimal d ) { return ++d; } public static decimal Decrease( this decimal d ) { return --d; } public static decimal Half( this decimal d ) { return d / 2; } // … }
decimal x=14M,y= 14M; x = Half(Triple(Decrease(Decrease(Double(Increase(x) )))) ); y = y.Increase().Double().Decrease().Decrease().Triple().Half( );
- An extension method is not automatically considered. Its resolution follows some rules.
- Here is the order of evaluation used to resolve a method for an identifier.
- Instance method: If an instance method exists, it has priority.
- Extension method: The search for an extension method is made through all
static
classes in the “current namespace” and in all namespaces included in active using directives.
c. Extension methods resolution
public class A { public virtual void X() { } } public class B : A { public override void X() { } public void Y() { } } static public class E { static void X(this A a) { } static void Y(this A b) { } public static void Demo() { A a = new A(); B b = new B(); A c = new B(); a.X(); // Call A.X b.X(); // Call B.X c.X(); // Call B.X a.Y(); // Call E.Y b.Y(); // Call B.Y c.Y(); // Call E.Y } }
d. Lambda expression as predicate
public static void Display( this T[] names, Func<T, bool> filter ) {…}
public static void Demo() {
string[] names = { “Marco”, “Paolo”, “Tom” };
names.Display( s => s.Length > 4 );// It was: Display( names, s => s.Length > 4 );
}
8. C# 3.0 Features – Object Initialization Expressions
// Implicitly calls default constructor before object initialization Customer customer = new Customer { Name = "Marco", Country = "Italy" };
a. Explicit constructor call in object initializer
// Explicitly specify constructor to call before object initialization Customer c1 = new Customer() { Name = "Marco", Country = "Italy" }; // Explicitly specify nondefault constructor Customer c2 = new Customer( "Paolo", 21 ) { Country = "Italy" };
b. Nested object initializers
public class Point { private int x, y;
public int X
{
get { return x; }
set { x = value; }
}
public int Y
{
get { return y; }
set { y = value; }
}
}
public class Rectangle
{
private Point tl, br;
public Point TL
{
get { return tl; }
set { tl = value; }
}
public Point BR
{
get { return br; }
set { br = value; }
}
}
// Possible code inside a method
Rectangle r = new Rectangle { TL = new Point { X = 0, Y = 1 },BR = new Point { X = 2, Y = 3 } };
c. Initializers for owned objects
public class Rectangle { Point tl = new Point(); Point br = new Point();
public Point TL { get { return tl; } } public Point BR { get { return br; } } }
// Possible code inside a method
Rectangle r = new Rectangle { TL = { X = 0, Y = 1 },BR = { X = 2, Y = 3 } };
d. Collection initializers
//Collection classes that implement ICollection List<int> integers = new List<int> { 1, 3, 9, 18 }; List list = new List {
new Customer( “Jack”, 28 ) { Country = “USA”}, new Customer { Name = “Paolo” },
new Customer { Name = “Marco”, Country = “Italy” }
};
//Collection classes that implement IEnumerable
ArrayList integers = new ArrayList() { 1, 3, 9, 18 };
ArrayList list = new ArrayList {
new Customer( “Jack”, 28 ) { Country = “USA”}, new Customer { Name = “Paolo” },
new Customer { Name = “Marco”, Country = “Italy” }
};
9. C# 3.0 Features – Anonymous Types
new
operator.new
class-an anonymous type-is created.Customer c1 = new Customer { Name = "Marco" }; var c2 = new Customer { Name = "Paolo" };
var c3 = new { Name = “Tom”, Age = 31 };
var c4 = new { c2.Name, c2.Age };
var c5 = new { c1.Name, c1.Country };
var c6 = new { c1.Country, c1.Name };
GetType()
– the following is the output that is generated:c1
is Customer c2
is Customer c3
is:f__AnonymousType0`2[System.String,System.Int32] c4 is f__AnonymousType0`2[System.String,System.Int32] c5 is f__AnonymousType5`2[System.String,System.String] c6 is f__AnonymousTypea`2[System.String,System.String]
10. C# 3.0 Features – Query Expressions
XVI. LINQ Syntax Fundamentals
1. LINQ Query Expression
query-expression ::= from-clause query-body query-body ::= join-clause* (from-clause join-clause* | let-clause | where-clause)* orderby-clause? (select-clause | groupby-clause) query-continuation? from-clause ::= from itemName in srcExpr _ select-clause ::= select selExpr groupby-clause ::= group selExpr by keyExpr
from
clause can be followed by zero or more from
, let
, or where
clauses.let
clause applies a name to the result of an expression, while a where
clause defines a filter that will be applied to include specific items in the results. Each from
clause is a generator that represents an iteration over a sequence on which query operators (such as the extension methods of System.Linq.Enumerable
) are applied.let-clause ::= let itemName = selExpr where-clause ::= where predExpr
from
clause can be followed by any number of join clauses. The final select
or group
clause can be preceded by an orderby
clause that applies an ordering to the results:join-clause ::= join itemName in srcExpr on keyExpr equals keyExpr (into itemName)?
orderby-clause ::= orderby (keyExpr (ascending | descending)?)* _
query-continuation ::= into itemName query-body.
XVII. Conclusion