Microsoft .NET Лекция 4: LINQ Меженин Михаил, кафедра "Системное программирование", ВМИ, ЮУрГУ, 2014 Microsoft .NET LINQ LINQ – Language Integrated Query Набор возможностей языка и фреймворка для выполнения запросов над локальными наборами данных программы. 2 Microsoft .NET LINQ Последовательность – любой объект, реализующий интерфейс IEnumerable<T>: string[] names = { "Tom", "Dick", "Harry" }; List<float> numbers = new List<string>(); string chars = "abcdefg"; 3 Microsoft .NET LINQ Оператор запроса – метод, изменяющий входную последовательность. Класс System.Linq.Enumerable реализует около 40 стандартных операторов. System.Linq.Enumerable.Where(…); 4 Microsoft .NET LINQ Запрос – выражение, при прохождении которого входная последовательность модифицируется операторами запроса. string[] names = { "Tom", "Dick", "Harry" }; IEnumerable<string> filteredNames = System.Linq.Enumerable.Where(names,n => n.Length >= 4); foreach (string n in filteredNames) Console.WriteLine (n); // Dick, Harry 5 Microsoft .NET LINQ Операторы запроса реализованы как методы расширения: using System.Linq; string[] names = { "Tom", "Dick", "Harry" }; var filteredNames = System.Linq.Enumerable.Where(names,n => n.Length >= 4); var filteredNames2 = names.Where (n => n.Length >= 4); foreach (string n in filteredNames) Console.WriteLine (n); // Dick, Harry 6 Microsoft .NET LINQ Операторы запроса принимают в качестве аргумента лямбда-выражение: Для каждого элемента n вернуть значение выражения n.Length >= 4 n => n.Length >= 4 Для каждого элемента n вернуть n+2 n => n + 2 7 Microsoft .NET LINQ Стандартный синтаксис (fluent syntax): var filteredNames = names.Where (n => n.Length >= 4); Альтернативный синтаксис (query expression syntax): var filteredNames = from n in names where n.Length >= 4 select n; 8 Microsoft .NET Цепочки операторов string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = names .Where (n => n.Contains ("a")) .OrderBy (n => n.Length) .Select (n => n.ToUpper()); JAY MARY HARRY 9 Microsoft .NET Цепочки операторов string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = names .Where (n => n.Contains ("a")) .OrderBy (n => n.Length) .Select (n => n.ToUpper()); Query Syntax: var query2 = from n in names where n.Contains ("a") orderby n.Length select n.ToUpper(); 10 Microsoft .NET Цепочки операторов string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = names .Where (n => n.Contains ("a")) .OrderBy (n => n.Length) .Select (n => n.ToUpper()); ~SQL: SELECT FROM WHERE ORDER BY UPPER(names.n) names names.n.Contains("a") LEN(names.n.Length) 11 Microsoft .NET Цепочки операторов string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = names .Where (n => n.Contains ("a")) .OrderBy (n => n.Length) .Select (n => n.ToUpper()); Без методов расширения: var query = Enumerable.Select ( Enumerable.OrderBy ( Enumerable.Where ( names, n => n.Contains ("a") ), n => n.Length ), n => n.ToUpper() ); 12 Microsoft .NET Цепочки операторов string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = names .Where (n => n.Contains ("a")) .OrderBy (n => n.Length) .Select (n => n.ToUpper()); var filtered = names .Where (n => n.Contains ("a")); var sorted = filtered.OrderBy (n => n.Length); мфк finalQuery = sorted .Select (n => n.ToUpper()); 13 Microsoft .NET Цепочки операторов string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = names .Where (n => n.Contains ("a")) .OrderBy (n => n.Length) .Take (3); // Jay, Mary, Harry string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = names .Take (3) .Where (n => n.Contains ("a")) .OrderBy (n => n.Length); // Harry 14 Microsoft .NET Операторы Некоторые операторы возвращают элемент, а не последовательность: int[] numbers = { 10, 9, 8, 7, 6 }; int firstNumber = numbers.First(); int lastNumber = numbers.Last(); int secondNumber = numbers.ElementAt(1); bool hasTheNumberNine = numbers.Contains(9); // // // // 10 6 9 true 15 Microsoft .NET Операторы Некоторые операторы принимают две входных последовательности: int[] seq1 int[] seq2 var concat var union = = = = { 1, 2, 3 }; { 3, 4, 5 }; seq1.Concat(seq2); seq1.Union (seq2); // { 1, 2, 3, 3, 4, 5 } // { 1, 2, 3, 4, 5 } 16 Microsoft .NET Отложенное выполнение Выражения выполняются в процессе прохождения последовательности: var numbers = new List<int>(); numbers.Add (1); var query = numbers.Select (n => n * 10); numbers.Add (2); foreach (int n in query) Console.WriteLine(n + " "); // 10 20 17 Microsoft .NET Отложенное выполнение Выражения выполняются в процессе прохождения последовательности… …кроме операторов, возвращающих элемент (First, Count, etc.) и операторов преобразования (ToArray, ToList, ToDictionary, ToLookup). var numbers = new List<int>(); numbers.Add (1); var query = numbers.Count(); numbers.Add (2); Console.WriteLine(query); // 1; query – int! 18 Microsoft .NET Отложенное выполнение Выражения выполняются заново в случае нового прохождения последовательности: var numbers = new List<int>() { 1, 2 }; var query = numbers.Select (n => n * 10); foreach (int n in query) Console.Write (n + " "); numbers.Clear(); foreach (int n in query) Console.Write (n + "|"); // 10 20 // 19 Microsoft .NET Отложенное выполнение Выражения выполняются заново в случае нового прохождения последовательности: var numbers = new List<int>() { 1, 2 }; var query = numbers.Select (n => n * 10).ToList(); foreach (int n in query) Console.Write (n + " "); numbers.Clear(); foreach (int n in query) Console.Write (n + "|"); // 10 20 // 10 20; query – List! 20 Microsoft .NET Отложенное выполнение Выражения выполняются заново в случае нового прохождения последовательности: var numbers = new List<int>() { 1, 2 }; var query = numbers.Select (n => n * 10); foreach (int n in query.ToList()) Console.Write (n + " "); numbers.Clear(); foreach (int n in query.ToList()) Console.Write (n + "|"); // 10 20 // ? 21 Microsoft .NET Отложенное выполнение При использовании в лямбда-выражении внешних переменных принимается в расчет их значение на момент исполнения запроса. var query = "abcdefghijklmnopqrstuvwxyz"; string vowels = "aeiou"; for (int i = 0; i < vowels.Length; i++) query = query.Where (c => c != vowels[i]); foreach (char c in query) Console.Write (c); // ? 22 Microsoft .NET Отложенное выполнение При использовании в лямбда-выражении внешних переменных принимается в расчет их значение на момент исполнения запроса. var query = "abcdefghijklmnopqrstuvwxyz"; string vowels = "aeiou"; for (int i = 0; i < vowels.Length; i++) query = query.Where (c => c != vowels[i]); foreach (char c in query) Console.Write (c); // IndexOutOfRangeException // i == 5 23 Microsoft .NET Отложенное выполнение Объявление переменной внутри цикла создает 5 переменных, значения которых будут захватываться при выполнении запроса. var query = "abcdefghijklmnopqrstuvwxyz"; string vowels = "aeiou"; for (int i = 0; i < vowels.Length; i++) { char vowel = vowels[i]; query = query.Where (c => c != vowel); } foreach (char c in query) Console.Write (c); // bcdfghklmnpqrstvwxyz 24 Microsoft .NET Декораторы var lessThanTen = new int[] { 5, 12, 3 }.Where(n => n < 10); 25 Microsoft .NET Декораторы var query = new int[] { 5, 12, 3 }.Where (n => n < 10) .OrderBy (n => n) .Select (n => n * 10); 26 Microsoft .NET Декораторы { 5, 12, 3 } n < 10 n n * 10 27 Microsoft .NET Подзапросы string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var outerQuery = names .Where (n => n.Length == names.Min(n2 => n2.Length)); // Tom, Jay 28 Microsoft .NET Подзапросы string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var outerQuery = names .Where (n => n.Length == names.Min(n2 => n2.Length)); var query1 = names.Min(n => n.Length); var query2 = names.Where(n => n.Length = query1); 29 Microsoft .NET IQueryable LINQ обладает двумя различными архитектурами: IEnumerable – для локальных последовательностей на основе декораторов, выражения компилируются в IL. IQueryable – для внешних источников данных на основе деревьев разбора, интерпретируемых во время исполнения запроса. 30 Microsoft .NET IQueryable Провайдеры, реализующие IQueryable: LINQ to Entities (Entity Framework) LINQ to SQL LINQ to DataSets LINQ to XML … LINQ to Twitter 31 Microsoft .NET LINQ to SQL SQL Server: create table Customer ( ID int not null primary key, Name varchar(30) ) insert Customer values (1, 'Tom') insert Customer values (2, 'Dick') insert Customer values (3, 'Harry') insert Customer values (4, 'Mary') insert Customer values (5, 'Jay') 32 Microsoft .NET LINQ to SQL using using using using System; System.Linq; System.Data.Linq; System.Data.Linq.Mapping; [Table] public class Customer { [Column(IsPrimaryKey=true)] public int ID; [Column] public string Name; } 33 Microsoft .NET LINQ to SQL var dataContext = new DataContext ("connection string"); var customers = dataContext.GetTable <Customer>(); IQueryable<string> query = customers .Where (n => n.Name.Contains ("a")) .OrderBy (n => n.Name.Length) .Select (n => n.Name.ToUpper()); foreach (string name in query) Console.WriteLine (name); SELECT UPPER([t0].[Name]) AS [value] FROM [Customer] AS [t0] WHERE [t0].[Name] LIKE @p0 ORDER BY LEN([t0].[Name]) 34 Microsoft .NET IQueryable 35 Microsoft .NET IQueryable IEnumerable<string> q = customers .Select (c => c.Name.ToUpper()) .OrderBy (n => n) .Pair() .Select ((n, i) => "Pair " + i.ToString() + " = " + n); SELECT UPPER (Name) FROM Customer ORDER BY UPPER (Name) 36 Microsoft .NET AsEnumerable() IEnumerable<string> q = customers .Select (c => c.Name.ToUpper()) .AsEnumerable() .OrderBy (n => n) .Pair() .Select ((n, i) => "Pair " + i.ToString() + " = " + n); SELECT UPPER (Name) FROM Customer 37 Microsoft .NET LINQ to SQL [Table (Name="Customers")] public class Customer { [Column(IsPrimaryKey=true)] public int ID; [Column] public string Name; } 38 Microsoft .NET Entity Framework [EdmEntityType (NamespaceName = "NutshellModel", Name = "Customer")] public partial class Customer { [EdmScalarPropertyAttribute (EntityKeyProperty = true, IsNullable = false)] public int ID { get; set; } [EdmScalarProperty (EntityKeyProperty = false, IsNullable = false)] public string Name { get; set; } } 39 Microsoft .NET Conceptual Model <edmx:ConceptualModels> <Schema …> <EntityContainer Name="Model1Container" annotation:LazyLoadingEnabled="true"> <EntitySet Name="Customers" EntityType="Model1.Customer" /> </EntityContainer> <EntityType Name="Customer"> <Key> <PropertyRef Name="Id" /> </Key> <Property Name="Id" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" /> <Property Name="Name" Type="String" Nullable="false" /> </EntityType> </Schema> </edmx:ConceptualModels> 40 Microsoft .NET Storage Model <edmx:StorageModels> <Schema …> <EntityContainer Name="Model1StoreContainer"> <EntitySet Name="Customers" EntityType="Model1.Store.Customers" store:Type="Tables" Schema="dbo" /> </EntityContainer> <EntityType Name="Customers"> <Key> <PropertyRef Name="Id" /> </Key> <Property Name="Id" Type="int" StoreGeneratedPattern="Identity" Nullable="false" /> <Property Name="Name" Type="nvarchar(max)" Nullable="false" /> </EntityType> </Schema> </edmx:StorageModels> 41 Microsoft .NET Mapping <EntityContainerMapping StorageEntityContainer="Model1StoreContainer" CdmEntityContainer="Model1Container"> <EntitySetMapping Name="Customers"> <EntityTypeMapping TypeName="IsTypeOf(Model1.Customer)"> <MappingFragment StoreEntitySet="Customers"> <ScalarProperty Name="Id" ColumnName="Id" /> <ScalarProperty Name="Name" ColumnName="Name" /> </MappingFragment> </EntityTypeMapping> </EntitySetMapping> </EntityContainerMapping> 42 Microsoft .NET Model1.edmx <?xml version="1.0" encoding="utf-8"?> <edmx:Edmx Version="3.0" xmlns:edmx="http://schemas.microsoft.com/ado/2009/11/edmx"> <!-- EF Runtime content --> <edmx:Runtime> <!-- SSDL content --> <edmx:StorageModels> <Schema Namespace="Model1.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2012" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl"> <EntityContainer Name="Model1StoreContainer"> <EntitySet Name="Customers" EntityType="Model1.Store.Customers" store:Type="Tables" Schema="dbo" /> </EntityContainer> <EntityType Name="Customers"> <Key> <PropertyRef Name="Id" /> </Key> <Property Name="Id" Type="int" StoreGeneratedPattern="Identity" Nullable="false" /> <Property Name="Name" Type="nvarchar(max)" Nullable="false" /> </EntityType> </Schema></edmx:StorageModels> <!-- CSDL content --> <edmx:ConceptualModels> <Schema xmlns="http://schemas.microsoft.com/ado/2009/11/edm" xmlns:cg="http://schemas.microsoft.com/ado/2006/04/codegeneration" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" Namespace="Model1" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" annotation:UseStrongSpatialTypes="false"> <EntityContainer Name="Model1Container" annotation:LazyLoadingEnabled="true"> <EntitySet Name="Customers" EntityType="Model1.Customer" /> </EntityContainer> <EntityType Name="Customer"> <Key> <PropertyRef Name="Id" /> </Key> <Property Name="Id" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" /> <Property Name="Name" Type="String" Nullable="false" /> </EntityType> </Schema> </edmx:ConceptualModels> <!-- C-S mapping content --> <edmx:Mappings> <Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2009/11/mapping/cs"> <EntityContainerMapping StorageEntityContainer="Model1StoreContainer" CdmEntityContainer="Model1Container"> <EntitySetMapping Name="Customers"> <EntityTypeMapping TypeName="IsTypeOf(Model1.Customer)"> <MappingFragment StoreEntitySet="Customers"> <ScalarProperty Name="Id" ColumnName="Id" /> <ScalarProperty Name="Name" ColumnName="Name" /> </MappingFragment> </EntityTypeMapping> </EntitySetMapping> </EntityContainerMapping> </Mapping></edmx:Mappings> </edmx:Runtime> <!-- EF Designer content (DO NOT EDIT MANUALLY BELOW HERE) --> <edmx:Designer xmlns="http://schemas.microsoft.com/ado/2009/11/edmx"> <edmx:Connection> <DesignerInfoPropertySet> <DesignerProperty Name="MetadataArtifactProcessing" Value="EmbedInOutputAssembly" /> </DesignerInfoPropertySet> </edmx:Connection> <edmx:Options> <DesignerInfoPropertySet> <DesignerProperty Name="ValidateOnBuild" Value="true" /> <DesignerProperty Name="EnablePluralization" Value="True" /> <DesignerProperty Name="CodeGenerationStrategy" Value="None" /> <DesignerProperty Name="UseLegacyProvider" Value="False" /> </DesignerInfoPropertySet> </edmx:Options> <!-- Diagram content (shape and connector positions) --> <edmx:Diagrams> </edmx:Diagrams> </edmx:Designer> </edmx:Edmx> 43 Microsoft .NET LINQ to SQL vs. Entity Framework var l2sContext = new DataContext ("database connection string"); Table<Customer> customers = context.GetTable <Customer>(); var efContext = new ObjectContext ("entity connection string"); ObjectSet<Customer> customers = context.CreateObjectSet<Customer>(); 44 Microsoft .NET LINQ to SQL vs. Entity Framework Customer cust = customers.OrderBy (c => c.Name).First(); cust.Name = "Updated Name"; context.SubmitChanges(); Customer cust = customers.OrderBy (c => c.Name).First(); cust.Name = "Updated Name"; context.SaveChanges(); 45 Microsoft .NET LINQ to SQL vs. Entity Framework class MyContext : DataContext // For LINQ to SQL { public Table<Customer> Customers { get { return GetTable<Customer>(); } } } var l2sContext = new MyContext ("database connection string"); Console.WriteLine (context.Customers.Count()); 46 Microsoft .NET LINQ to SQL vs. Entity Framework /* 0, Ada 1, Zed 2, Jake */ var context = new MyContext ("connection string"); Customer a = context.Customers.OrderBy (c => c.Name).First(); Customer b = context.Customers.OrderBy (c => c.ID).First(); Console.WriteLine(a == b); // true 47 Microsoft .NET LINQ to SQL vs. Entity Framework create table Purchase ( ID int not null primary key, CustomerID int references Customer (ID), Price decimal not null ) [Association (Storage="_Customer", ThisKey="CustomerID", IsForeignKey=true)] public Customer Customer { get {...} set {...} } [EdmRelationshipNavigationProperty ("MyModel", "FK..., "Customer")] public Customer Customer { get {...} set {...} } 48