http://www.devleap.com http://devcon.devleap.com Pag. 1
www.devleap.it
LINQ Introduction
Marco Russo
Cosa è LINQ
• Nuovo modello di programmazione
• Integrazione delle query nei linguaggi .NET
• Accesso a dati in formati diversi
• Grafo di oggetti in memoria
• Relazionale
• XML Infoset
• Fonte dati “esterna”
• È uno strumento, non un’architettura applicativa
• Poter mettere una query nel codice non implica
• Né rinunciare alle architetture n-tier
• Né cambiare per forza le architetture esistenti
Una query in LINQ
• Una query in LINQ assomiglia a una query SQL
var query =
from c in Customers
where c.Country == "Italy"
select c.CompanyName;
• Ma tutto è tipizzato e controllato dal compilatore
• I dati si possono leggere con foreach
foreach ( string name in query ) {
Console.WriteLine( name );
}
Una query in LINQ
• Cosa è Customers?
• Può essere
• Un array/collection
• Un DataSet
• Una tabella relazionale
• Un’entità (EDM)
Customer[] Customers;
DataSet ds = GetDataSet();
DataTable Customers = ds.Tables["Customers"];
DataContext db = new DataContext( ConnectionString );
Table<Customer> Customers = db.GetTable<Customer>();
NorthwindModel dataModel = new NorthwindModel();
ObjectQuery<Customer> Customers = dataModel.Customers;
LINQ: schema generale
Oggetti
<book> <title/>
<author/> <year/> <price/>
</book>
XML Mondo
relazionale
Come funziona LINQ
• La query expression viene trasformata in codice “normale”
Customer[] Customers = GetCustomers();
var query =
from c in Customers
where c.Country == "Italy"
select c;
Customer[] Customers = GetCustomers();
IEnumerable<Customer> query =
Customers
.Where( c => c.Country == "Italy" );
Local Type Inference
Lambda Expression
Extension Method
http://www.devleap.com http://devcon.devleap.com Pag. 2
Come funziona LINQ
• LINQ introduce il concetto di “tuple” attraverso gli anonymous type
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 } );Anonymous Type
Cosa si può fare con LINQ
• Dipende dalle implementazioni di LINQ
• Set di operazioni “standard” predefinito
• Si possono aggiungere operazioni specifiche per singole implementazioni
Query in memoria o remote?
• Entrambe le possibilità
• IEnumerable<T>
• La query è composta da delegate connessi tra loro
• Durante l’iterazione vengono richiamati i delegate
• Le espressioni nella query sono valutate solo durante l’iterazione
• IQueryable<T>
• La query è compilata in un expression tree (il codice è rappresentato come un albero di oggetti)
• Al momento dell’esecuzione, l’expression tree può essere manipolato
• Alla base delle query remote (es. LINQ to SQL)
Cosa NON è LINQ
• Non è una forma di Embedded-SQL
• Non è un Object Relational Mapper
• LINQ to SQL è un ORM “semplice”
• LINQ to Entities è un ORM più “completo
• Non è un modo per spostare il Data Layer nella UI
• Non è solo per l’accesso a database relazionali
• Non è obbligatorio usarlo
• Ma se padroneggiato migliora la qualità della vita
www.devleap.it
Ripasso C# 3.0 per LINQ
Local Type Inference
• Dichiarazione variabile con keyword var
• Tipo inferito dall’espressione dell’initializer
• Indispensabile con tipi anonimi
www.devleap.it
// Esempi di inferenza di tipo
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
}
// Usi non consentiti
class DemoNotAllowed {
var k = 0;
public void f( var x ) {}
public var g() {
return 2;
}
public void InvalidUseLocal() {
var x;
var y = null;
}
}
http://www.devleap.com http://devcon.devleap.com Pag. 3
Local Type Inference Best Practices
• Usare per anonymous types, query expressions e tipi generici complessi
www.devleap.it
var c3 = new { Name = "Tom", Age = 31 };
var query = from c in customers where c.Discount > 3 select c;
var ordersByCustomer = new Dictionary<string, List<Orders>>();
• Non consigliabile per risultato di new su tipo
• Non usare per costanti e semplici espressioni
var customer = new Customer();
var numbers = new int[] { 1, 3, 5, 9 };
var x = 5;
var amount = customer.Amount;
var x = y / z;
var count = list.Count();
Sintassi Lambda Expressions
• Anonymous delegate
www.devleap.it
int sum = Aggregate(
l.Values,
( int a, int b ) => { return a + b; } );
int sum = Aggregate(
l.Values,
( a, b ) => { return a + b; } );
int sum = Aggregate(
l.Values,
( a, b ) => a + b );
• Explicitly typed parameter list
• Implicitly typed parameter list
• Expression body
int sum = Aggregate(
l.Values,
delegate( int a, int b ) { return a + b; }
);
Sintassi Lambda Expressions
• Altri casi particolari
• Con un solo parametro si possono evitare le parentesi
• Se non ci sono parametri le parentesi sono obbligatorie
www.devleap.it
( 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
Lambda Expressions
• Predicate: espressione booleana che indica l’appartenenza di un elemento a un gruppo
• Projection: espressione che restituisce un tipo diverso dal parametro (che è singolo)
// Predicate
( age ) => age > 21
// Projection: takes a string and returns an int
( s ) => s.Length
Lambda Expressions
• Concetto chiave: il parametro è quello del delegate, ma si può accedere alle variabili in scope come in qualsiasi anonymous method
int sum = 0;
int result = AggregateSingle(
l.Values,
x => sum += x
);}
Func<T> per Lambda Expressions
• Lambda expression si può assegnare a variabili di tipo delegate Func<T> definite in System.Linq
• Non è obbligatorio, ma è una convenzione e serve per ottenere expression tree
www.devleap.it
public delegate T Func<T>();
public delegate T Func<A0, T>( A0 arg0 );
public delegate T Func<A0, A1, T> ( A0 arg0, A1 arg1 );
public delegate T Func<A0, A1, A2, T>( A0 arg0, A1 arg1, A2 arg2 );
public delegate T Func<A0, A1, A2, A3, T> ( A0 arg0, A1 arg1, A2 arg2, A3 arg3 );
http://www.devleap.com http://devcon.devleap.com Pag. 4
Expression Trees
• Assegnazione a Expression<D>
• Definito in System.Linq.Expressions
• Expression tree ottenuto si può compilare con Compile e il risultato è un delegate che può essere eseguito
• Expression Trees sono immutabili
www.devleap.it
Func<int> x = (a, b) => a + b; // Lambda Expression
Int a = x( 29, 13 ); // Invocation
Expression<Func<int>> y = (a, b) => a + b; // Expression Tree
Int b = y.Compile()( 29, 13 ); // Compilation and Invocation
Extension Methods
• Metodi che “apparentemente” estendono una classe senza derivarla
• Costrutto sintattico che usa metodi statici pubblici
• Usare this sul primo parametro
www.devleap.it
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 + 1; }
public static decimal Decrease( this decimal d ) { return d – 1; }
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();
Object Initialization Expressions
• Inizializzazione di oggetti (value o reference)
• Senza usare un costruttore
• Serve ad avere tutto in un’espressione
www.devleap.it
// Implicitly calls default constructor before object initialization
Customer customer = new Customer { Name = "Marco", Country = "Italy" };
Customer customer = new Customer();
customer.Name = "Marco";
customer.Country = "Italy";
Collection Initializers
• Sintassi di inizializzazione per classi collection
• Chiama metodo Add per ogni elemento nella lista
www.devleap.it
List<Customer> list = new List<Customer> {
new Customer( "Jack", 28 ) { Country = "USA"},
new Customer { Name = "Paolo" },
new Customer { Name = "Marco", Country = "Italy" }
};
List<Customer> list = new List<Customer>();
list.Add( new Customer( "Jack", 28 ) { Country = "USA"} );
list.Add( new Customer { Name = "Paolo" } );
list.Add( new Customer { Name = "Marco", Country = "Italy" } );
Anonymous Types
• Tipi definiti attraverso object initializer senza specificare un nome di classe
• Istanze di anonymous types sono immutabili in C#
• Membri definiti da sintassi assegnazione o nome membro passato
• Stesso tipo se membri hanno
• Stesso nome
• Stesso tipo
• Stesso ordine
www.devleap.it
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 };
c3 e c4 sono
dello stesso tipo
c5 e c6 sono di
due tipi diversi
www.devleap.it
LINQ Query Syntax
http://www.devleap.com http://devcon.devleap.com Pag. 5
LINQ Query Syntax
• Estensione sintassi C# 3.0 per usare LINQ
• Estensioni analoghe per Visual Basic 9.0
• Forma semplificata per accedere a operatori definiti con extension method
www.devleap.it
C#
Query Syntax
• from/where/select
from d in developers
where d.Language == "C#"
select d.Name;
developers.Where(
(d) => d.Language == "C#”) .Select( (d) => d.Name);
www.devleap.it
C#
Extension Methods
developers.Where(
(d) => d.Language == "C#"
).Select(
(d) => d.Name
);
Enumerable.Select(
Enumerable.Where(
developers,
(d) => d.Language == "C#" ),
(d) => d.Name
);
www.devleap.it
C#
Risultato di una query
• IEnumerable<T>
• Dove T è il tipo restituito dalla query
• Per i tipi anonimi è obbligatorio l’uso di var
IEnumerable<string> query =
from d in developers
where d.Language == "C#"
select d.Name;
var query =
from d in developers
select new {d.Name, d.Language };
www.devleap.it
Clausola from
• Definisce variabile usata in lambda expression di altri extension method per operatori usati nella query
• Iterazione di variabile per tutti gli elementi del data source specificato
from rangeVariable in dataSource
www.devleap.it
C#
Nested from
• Navigazione in grafo di oggetti con clausole from nidificate
• Più avanti vediamo anche join
var ordersQuery =
from c in customers
from o in c.Orders
select new { c.Name, o.IdOrder, o.EuroAmount };
www.devleap.it
http://www.devleap.com http://devcon.devleap.com Pag. 6
C#
Clausola where
• Specifica un’espressione di filtro da applicare a ogni elemento iterato nella clausola from
• L’espressione è un delegate – i calcoli vengono ripetuti ogni volta
• Mantenere espressioni semplici
• Evitare calcoli con risultati invarianti
from d in developers
where d.Language == "C#"
www.devleap.it
C#
C#
Clausola group
• Clausola group … by suddivide una sequenza in gruppi
var query = from d in developers
group d by d.Language;
• Risultato gerarchico – richiede due foreach nidificate
foreach (var group in query)
foreach (var item in group)
C#
Clausola orderby
• Specifica ordinamento del risultato
• Si può specificare ascending / descending
var ordersSortedByCustomerAndEuroAmount =
from c in customers
from o in c.Orders
orderby c.Name, o.EuroAmount descending
select new { c.Name, o.IdOrder, o.EuroAmount };
www.devleap.it
C#
Clausola join
• Associa due sequenze in base al valore di un membro che deve avere valore uguale
var categoriesAndProducts =
from c in categories
join p in products on c.IdCategory equals p.IdCategory
select new {
c.IdCategory,
CategoryName = c.Name,
Product = p.Description
}; www.devleap.it
C#
Risultato equivalente a LEFT JOIN
• Si usa DefaultIfEmpty “riappiattendo” il risultato
var categoriesAndProducts =
from c in categories
join p in products on c.IdCategory equals p.IdCategory
into productsByCategory
from pc in productsByCategory.DefaultIfEmpty(
new Product {
IdProduct = String.Empty,
Description = String.Empty,
IdCategory = 0})
select new { c.IdCategory, CategoryName = c.Name,
Product = pc.Description }; www.devleap.it
C#
Clausola let
• La keyword let consente di referenziare più volte la stessa espressione in una query
var categoriesByProductsNumberQuery =
from c in categories
join p in products on c.IdCategory equals p.IdCategory
into productsByCategory
let ProductsCount = productsByCategory.Count()
orderby ProductsCount
select new { c.IdCategory, ProductsCount};
www.devleap.it
http://www.devleap.com http://devcon.devleap.com Pag. 7
www.devleap.it
LINQ to SQL
Assembly e Namespace
• Assembly
• System.Data.Linq
• Namespace
• System.Data.Linq
• System.Data.Linq.Mapping
www.devleap.it
SQL
C#
Query in LINQ to SQL
• Customers è un tipo in .NET
var query =
from c in Customers
where c.Country == "USA"
&& c.State == "WA"
select new {c.CustomerID, c.CompanyName, c.City };
• Necessario mapping metadati per ottenere SQL:
SELECT CustomerID, CompanyName, City
FROM Customers
WHERE Country = 'USA’ AND Region = 'WA'
www.devleap.it
C#
DataContext
• Rappresenta connessione a database
• Metodo GetTable<T> per ottenere Table<T>
DataContext db = new DataContext("Database=Northwind");
Table<Customer> Customers = db.GetTable<Customer>();
• Classi derivate da DataContext
• Specializzazione per un database particolare
• Contiene dati membri corrispondenti a tabelle
• Istanze di Table<T> dove T descrive una riga
www.devleap.it
C#
Table<T>
• Rappresenta una tabella
• T è il tipo che descrive una riga
• Classe decorata con attributi di mapping
• Table<T> implementa IQueryable<T>
• Istanze di Table<T> usate nelle query LINQ
DataContext db = new DataContext( ConnectionString );
Table<Customer> customers = db.GetTable<Customer>();
var query = from c in customers
where c.Country == "USA” && c.State == "WA"
select new {c.CustomerID, c.CompanyName, c.City };
www.devleap.it
C#
Mapping entità con attributi
• Attributi definiscono mapping entità – tabelle SQL Server
[Table(Name="Customers")]
public class Customer {
[Column] public string CustomerID;
[Column] public string CompanyName;
[Column] public string City;
[Column(Name="Region")] public string State;
[Column] public string Country;
}
www.devleap.it
http://www.devleap.com http://devcon.devleap.com Pag. 8
C#
Come viene eseguita la query
• Le query LINQ to SQL sono expression tree
• Implementazione di IQueryable<T>
• La conversione a SQL avviene al momento di iterare il risultato della query (GetEnumerator)
var query = from c in Customers
where c.Country == "USA"
select c.CompanyName;
foreach( var company in query ) {
Console.WriteLine( company );
}
www.devleap.it
IEnumerator<string> enumerator = query.GetEnumerator();
while (enumerator.MoveNext()) {
Console.WriteLine( enumerator.Current ); }
C#
Manipolazione query
• Query LINQ to SQL possono essere composte tra loro – SQL generato solo per query eseguite
// All Customers
var query = from c in Customers
select new {c.CompanyName, c.State };
// Customers from Washington (WA)
query = from c in query
where c.State == “WA"
select c;
www.devleap.it
Top Related