Интегрированный в язык запрос — LINQ — это новая функция доступа к данным, которая выполняет именно то, что говорит: позволяет запрашивать источники данных из самого языка.
Обычно в .NET мы используем технологию, такую как ADO.NET, для прямого запроса к серверу управления реляционной базой данных, например, SQL Server® или MySQL. LINQ делает еще один шаг вперед, позволяя осуществлять запросы из тех же источников СУБД, но в рамках объектно-ориентированной технологии.
С помощью LINQ мы получаем доступ к проверке синтаксиса во время компиляции, использованию IntelliSense и возможности доступа к другим источникам данных, таким как XML, или практически к любым пользовательским источникам данных. Существует много расширений LINQ, таких как LINQ-to-SQL, LINQ-to-Sharepoint, LINQ-to-MySQL и так далее.
Давайте начнем с LINQ, чтобы вы сразу почувствовали синтаксис.
// create an array of integers int[] sampleNumbers = { 1, 2, 3 }; IEnumerable doubledNumbers = from i in sampleNumbers select i * 2; // produces "2, 4, 6"; foreach (int number in doubledNumbers) Console.WriteLine(number);
Наша переменная IEnumerable
под названием doubledNumbers
содержит выходные данные из выражения запроса. Как видите, синтаксис похож на SQL. Однако Microsoft вводит «селекторы» (в нашем случае «i»).
Когда мы углубимся в синтаксис LINQ, эти селекторы станут более очевидными. Этот образец показывает только коллекцию чисел. В нашем случае мы знаем, что ожидаем возврата целых чисел. Однако, чтобы помочь в случаях, когда мы можем не знать точный тип возвращаемого значения во время выполнения, Microsoft ввела ключевое слово var
.
// create an array of integers int[] sampleNumbers = { 1, 2, 3 }; var squaredNumbers = from i in sampleNumbers select i * i; // produces "1, 4, 9"; foreach (int number in squaredNumbers) Console.WriteLine(number);
Так что, как и большинство ленивых программистов, мы будем использовать ключевое слово var
чтобы позволить компилятору определять тип, где это уместно.
Давайте сделаем еще один пример синтаксиса LINQ, чтобы ввести предложения WHERE, поскольку нам часто требуется подмножество данных.
// create an array of random fruits string[] fruits = { "Blueberry", "Banana", "Orange", "Peach", "Kiwi", "Blackberry" }; var fruitsStartingWithB = from f in fruits where f.StartsWith("B") && f.Contains("berry") select f; // produces "Blueberry, Blackberry" foreach (var fruit in fruitsStartingWithB) Console.WriteLine(fruit);
Здесь мы используем стандартный оператор «И» ( &&
) как часть C #. Также доступен оператор «ИЛИ» ( ||
) в предложении WHERE.
Методы расширения
LINQ интенсивно использует методы расширения, чтобы манипулировать нашими данными так, как нам нужно. Одной из таких функций является метод расширения First()
. Это берет первый элемент в результате и возвращает его. Может использоваться следующим образом:
string[] fruits = { "Blueberry", "Banana", "Orange", "Peach", "Kiwi", "Blackberry" }; string berry = (from f in fruits where f.StartsWith("B") && f.Contains("berry") select f).First(); // Output is "Blueberry" Console.WriteLine(berry);
В следующей статье мы обсудим больше методов расширения, которые позволяют нам лучше контролировать наш набор результатов, а также настраивать возможности LINQ.
LINQ to SQL
Как это помогает нам при работе с необъектной ориентированной структурой данных? Давайте посмотрим, как LINQ может помочь нам при работе с реляционными данными.
Мы начнем с простого запроса из образца базы данных AdventureWorks как части выпуска Microsoft SQL Server 2008 R2 Express . Экспресс-версия доступна для скачивания здесь . Вы можете получить образец базы данных AdventureWorks в Microsoft CodePlex, доступной здесь .
После установки SQL Server 2008 Express Edition вам нужно будет «присоединить» образец базы данных AdventureWorks. Для этого откройте SQL Server 2008 Management Studio . Щелкните правой кнопкой мыши Базы данных и выберите Задачи -> Присоединить базу данных . Выберите файл .mdf
в том месте, в котором вы его установили, и тогда вы будете готовы начать пробные упражнения.
Затем мы создаем соединение с сервером в Visual Studio с помощью окна панели инструментов обозревателя сервера .
Чтобы подключиться к нашему экземпляру SQL Express, мы выбираем параметр источника данных Microsoft SQL Server . Затем мы просто выбираем экземпляр SQLEXPRESS и базу данных AdventureWorks в качестве опции базы данных по умолчанию.
Далее мы добавляем новый элемент в наш проект. Чтобы использовать LINQ с источником данных SQL, мы решили создать класс данных LINQ-to-SQL. В самом простом объяснении LINQ-to-SQL отображает объектно-ориентированные классы с таблицей базы данных SQL, иначе называемой объектным реляционным преобразователем.
Доступны и другие платформы ORM, такие как nHibernate или SubSonic. У каждого из них есть свои сильные и слабые стороны, но ради этой статьи мы сконцентрируемся на возможностях LINQ-to-SQL.
Мы .dbml
этот файл .dbml
AdventureWorks.dbml
. Создав это, вы начинаете с чистого листа, где у вас есть возможность создавать объекты. Сущность — это строго типизированная структура данных, обычно используемая для представления деловых отношений или объекта. Microsoft представила ADO.NET Entity Framework, которая развивает эти передовые концепции. Эти темы выходят за рамки нашего текущего обсуждения и могут быть обсуждены в будущем.
В нашем примере мы хотим, чтобы объект с именем «Сотрудник» представлял один экземпляр сотрудника в корпорации AdventureWorks. Сведения на экране показывают, что вы можете использовать Панель инструментов для создания новой сущности, но мы хотим использовать всю мощь созданного соединения с источником данных для нашего экземпляра SQL Express, открыв обозреватель сервера, чтобы перетаскивать таблицу «Сотрудники». ,
В результате получается новый объект-сущность с именем «Сотрудник» (обратите внимание, что это единственная формулировка имени таблицы «Сотрудники», они автоматически именуются Visual Studio). Visual Studio не только создает для нас объект, но и автоматически генерирует код для объекта DataContext
. Этот объект DataContext
представляет соединение с источником данных, в нашем случае соединение с базой данных SQL Express.
DataContext
также отслеживает все изменения во всех сущностях и служит в качестве контрольной точки для вставки, обновления или удаления записи сущности обратно в ее основной источник данных (таблица базы данных «Сотрудники»). Эти объекты предназначены для однократного использования, поскольку они легки и не оказывают заметного влияния при создании тысяч экземпляров.
Кроме того, DataContext
отличается от стандартного способа ADO.NET — открытия SqlConnection и последующего выполнения запросов; SQL, генерируемый при выполнении запросов LINQ, выполняется во время выполнения, а также для управления соединением.
DataContext
создается в одну строку:
AdventureWorksDataContext dataContext = new AdventureWorksDataContext();
DataContext
создается и назван в честь файла DBML, созданного ранее. Если вы создали свой DMBL как « Sample.dbml
», тогда ваш DataContext
будет называться SampleDataContext
.
Все на этом этапе было подключено Visual Studio с использованием автоматически сгенерированного кода для класса DBML. В окне обозревателя решений вы можете изучить этот автоматически сгенерированный код для файла AdventureWorks.dbml
, щелкнув правой кнопкой мыши файл для просмотра кода . Обязательно не вносите здесь постоянных изменений, любые изменения в конструкторе автоматически перезаписывают любые изменения в автоматически сгенерированных файлах кода.
Теперь мы можем запросить таблицу данных сотрудников с помощью LINQ. В качестве примера мы ищем только работниц. Для целей этих упражнений вы можете предположить, что мы используем те же ссылочные пространства имен в разделе «использование» в верхней части исходного кода:
using System; using System.Collections.Generic; using System.Linq; using System.Text; AdventureWorksDataContext dataContext = new AdventureWorksDataContext(); var employees = from emp in dataContext.Employees where e.Gender == 'F' select emp; // produces a list of employee IDs for all female employees foreach (var employee in employees) Console.WriteLine(employee.EmployeeID);
Обновление данных
Одним из столбцов в таблице «Сотрудники» является столбец MaritalStatus. Это символьное поле, которое имеет «S» для одинокого сотрудника или «M» для женатого сотрудника. В связи с резкой вспышкой женщин, вступающих в брак, отдел кадров решил просто отметить всех как состоящих в браке.
Таким образом, наш пример — выполнить это обновление, используя LINQ-to-SQL.
using (AdventureWorksDataContext dataContext = new AdventureWorksDataContext()) { var employees = from e in dataContext.Employees where e.Gender == 'F' select e; // produces a list of employee IDs for all female employees foreach (var employee in employees) { employee.MaritalStatus = 'M'; // all changes have not been persisted to the database. Console.WriteLine(employee.EmployeeID + " is now married."); } // Send changes to the database. dataContext.SubmitChanges(); }
Изменения здесь кажутся достаточно простыми, но следует отметить, что изменения в записи объекта Employee не сохраняются (сохраняются) в базе данных до тех пор, пока не будет вызван метод SubmitChanges()
DataContext
.
Отношения
До сих пор мы имели дело только с одной таблицей сотрудников. Наша сущность Employee имеет свойство ContactID, которое является ссылочным ключом для таблицы контактов. Средство конструктора DBML в Visual Studio автоматически распознает внешние ключи в базе данных. Мы просто перетаскиваем таблицу «Контакты» из окна «Обозреватель серверов» для соединения SQL Express (как мы это делали для таблицы «Сотрудники») на конструктор DBML, чтобы Visual Studio создала объект сущности «Контакт» для нас.
Дизайнерское представление теперь выглядит так:
Теперь мы можем ссылаться на личную информацию для каждого из сотрудников. Давайте подготовим отчет для HR со списком сотрудников, принятых на работу после 2002 года.
using (AdventureWorksDataContext dataContext = new AdventureWorksDataContext()) { var employees = from e in dataContext.Employees where e.HireDate > DateTime.Parse("2002-01-01") select e; foreach (var employee in employees) Console.WriteLine(employee.Contact.FirstName + " " + employee.Contact.LastName); }
Непосредственное выполнение запросов из DataContext
В этот момент вы, скорее всего, будете знакомы с непосредственным вызовом стандартного ANSI SQL для базы данных через ADO.NET и / или хранимые процедуры. LINQ-to-SQL поддерживает эти прямые запросы с помощью ExecuteQuery()
метода ExecuteQuery()
. Этот метод имеет два переопределенных параметра: стандартную версию, которая принимает Type
качестве первого параметра, или универсальную версию, которая возвращает заданный универсальный тип ввода.
ExecuteQuery(String SqlQuery, Object[] Parameters)
ExecuteQuery(Type result, String sqlQuery, Object[] Parameters)
При использовании ExecuteQuery
с общей версией мы намереваемся использовать тип сущности Employee, чтобы получить доступ к связи с сущностью Contact, чтобы получить имя и фамилию сотрудника. Таким образом, в наших интересах использовать универсальную сигнатуру метода для ExecuteQuery()
.
using (AdventureWorksDataContext dataContext = new AdventureWorksDataContext()) { var employees = from e in dataContext.Employees where e.HireDate > DateTime.Parse("2002-01-01") select e; var emps = dataContext.ExecuteQuery("SELECT * FROM HumanResources.Employee WHERE HireDate > '2002-01-01'"); foreach (var currentEmployee in emps) Console.WriteLine(currentEmployee.Contact.FirstName + " " + currentEmployee.Contact.LastName); Console.WriteLine("----------------"); foreach (var employee in employees) Console.WriteLine(employee.Contact.FirstName + " " + employee.Contact.LastName); }
При выполнении этого примера вы должны получить одинаковые результаты для обоих запросов. Кроме того, LINQ-to-SQL также поддерживает прямое выполнение запросов, когда от выполнения SQL не ожидается никаких возвращаемых параметров, таких как удаление записей, ручная вставка или обновление записей с помощью метода Execute()
.
HR попросил нас отменить прежнюю политику вступления в брак всех работающих женщин. Теперь они хотят, чтобы мы сделали их всех одинокими. Мы сделаем это, используя прямое выполнение запроса с LINQ-to-SQL.
using (AdventureWorksDataContext dataContext = new AdventureWorksDataContext()) { int rowsAffected = dataContext.ExecuteCommand("UPDATE HumanResources.Employee WITH(ROWLOCK) SET MaritalStatus = 'S' WHERE Gender = 'F' "); // should display "Execution completed, 84 rows affected." Console.WriteLine("Execution completed, {0} rows affected", rowsAffected); }
Хранимые процедуры с LINQ-to-SQL
Одна из моих любимых функций LINQ-to-SQL — поддержка хранимых процедур для LINQ. LINQ-to-SQL рассматривает хранимые процедуры как вызовы стандартных методов. В составе базы данных AdventureWorks есть хранимая процедура, которая называется «uspGetManagerEmployees».
Чтобы позволить LINQ-to-SQL интегрировать этот вызов хранимой процедуры, мы выполняем перетаскивание из окна обозревателя серверов, где выбираем хранимую процедуру из нашего соединения SQL Express на правой панели нашего конструктора DBML.
Visual Studio теперь создаст метод с именем uspGetManagerEmployees()
. Поскольку префикс «usp» не требуется для вызовов методов, мы GetManagerEmployees()
нему правой кнопкой мыши, чтобы выбрать « Свойства», и переименуем его в « GetManagerEmployees()
». Теперь мы можем вызвать эту процедуру в нашем коде:
using (AdventureWorksDataContext dataContext = new AdventureWorksDataContext()) { var employees = dataContext.GetManagerEmployees(185); foreach (var currentEmployee in employees) Console.WriteLine(currentEmployee.FirstName + " " + currentEmployee.LastName); }
Обратите внимание, что тип возвращаемого значения из GetManagerEmployees()
является объектом ISingleResult
. Здесь ключевое слово var
становится полезным, поскольку мы можем не знать полный тип возвращаемого значения. Свойства для вызова процедуры в представлении « Дизайнер» показывают, что тип результата является автоматически сгенерированным .
Однако что, если мы хотим, чтобы дизайнер строго напечатал этот возврат к одному из известных типов сущностей? Чтобы выполнить этот пример, мы сначала создадим новую хранимую процедуру с именем « uspGetEmployeeByID
»:
CREATE PROCEDURE uspGetEmployeeByID -- Add the parameters for the stored procedure here @EmployeeID int = 0 AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; -- Insert statements for procedure here SELECT * FROM HumanResources.Employee where EmployeeID = @EmployeeID END GO
В представлении конструктора мы перетаскиваем этот вызов процедуры на правую панель представления конструктора. Затем мы переименовываем вызов метода, чтобы удалить префикс «usp». Кроме того, мы намереваемся изменить тип результата на объект Employee.
Когда вы сделаете этот выбор, вы получите предупреждение, предупреждающее вас об одностороннем характере этого изменения. Вы не можете отменить это изменение. Если вам нужно внести изменения в хранимую процедуру, необходимо удалить вызов метода, а затем повторно добавить его с помощью перетаскивания из окна обозревателя сервера.
После того, как мы удовлетворены внесенными изменениями, мы можем выполнить вызов этой новой хранимой процедуры с помощью объекта DataContext
.
using (AdventureWorksDataContext dataContext = new AdventureWorksDataContext()) { Employee jefferyFord = dataContext.GetEmployeeByID(15).First(); Console.WriteLine(jefferyFord.Contact.FirstName + " " + jefferyFord.Contact.LastName); }
Использование ASP.NET и LINQ-to-SQL
LINQ позволяет полностью привязывать объекты в качестве источника данных. Это позволяет нам создавать полностью функциональные сетки данных с помощью LINQDataSource. Чтобы начать, мы перетаскиваем LinqDataSource
из меню боковой панели Visual Studio Toolbox на стандартную страницу ASP.NET Web Forms .aspx
. После выделения этого нового объекта на странице теперь мы можем настроить этот источник данных для использования нашего объекта DataContext
для базы данных AdventureWorks.
В этом примере нам просто нужен источник данных только для чтения для стандартного элемента управления сеткой данных ASP.NET. Мы настраиваем наш объект LinqDataSource
следующим образом:
Выбрав все столбцы, у нас достаточно информации для отображения имени и фамилии записей сотрудников, которые мы хотим отобразить. При настройке источника данных доступны более продвинутые функции, такие как пользовательский запрос или пользовательский заказ по предложению с предварительной сортировкой записей, когда они возвращаются из запроса LINQ.
Кроме того, LinqDataSource
позволяет LinqDataSource
автоматического редактирования, вставки и удаления. Эти расширенные темы выходят за рамки этого текущего руководства и могут быть обсуждены в будущем.
Далее мы продолжим добавление стандартного GridView
управления GridView
на нашу страницу. Теперь мы можем настроить эту сетку для использования LinqDataSource
мы создали ранее.
Мы выбираем наш недавно созданный объект LinqDataSource1
в качестве источника данных для Grid View . После того, как мы выберем это, элемент управления GridView
мгновенно изменится, чтобы отразить новую схему объекта LinqDataSource
.
Теперь вы готовы к просмотру страницы в браузере. Помните, что полная загрузка страницы может занять некоторое время, поскольку LinqDataSource
в настоящее время настроен на возврат всех строк и столбцов из таблицы «Контакты» из базы данных AdventureWorks.
Чтобы подготовить отчет для HR, который показывает список всех сотрудников и их адреса электронной почты, мы можем выбрать задачу « Редактировать столбцы» из всплывающего окна SmartTag
для GridView
и выбрать только те столбцы, которые мы хотим показать.
Полный HTML-код страницы .aspx должен выглядеть следующим образом:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div> <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ContactID" DataSourceID="LinqDataSource1"> <Columns> <asp:BoundField DataField="ContactID" HeaderText="ContactID" InsertVisible="False" ReadOnly="True" SortExpression="ContactID" /> <asp:CheckBoxField DataField="NameStyle" HeaderText="NameStyle" SortExpression="NameStyle" /> <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" /> <asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" /> <asp:BoundField DataField="MiddleName" HeaderText="MiddleName" SortExpression="MiddleName" /> <asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" /> <asp:BoundField DataField="Suffix" HeaderText="Suffix" SortExpression="Suffix" /> <asp:BoundField DataField="EmailAddress" HeaderText="EmailAddress" SortExpression="EmailAddress" /> </Columns> </asp:GridView> <br /> </div> <asp:LinqDataSource ID="LinqDataSource1" runat="server" ContextTypeName="LinqWebApp1.AdventureWorksDataContext" EntityTypeName="" TableName="Contacts"> </asp:LinqDataSource> </form> </body> </html>
Последние мысли
В целом, LINQ — это мощный инструмент, который позволяет быстро разрабатывать код и тратить меньше времени на его аспекты управления данными.
Это повышает нашу производительность, потому что мы можем запрашивать все данные в Visual Studio без необходимости знать базовую структуру конфигурации сервера базы данных или даже знать сам источник данных.