Блог программиста Еремина Вячеслава Викторовича
(ASP.NET) ASP NET (2010 год)

Извлекаем пользу из LINQ

Пример использования LINQ-to-XML (XLINQ) и LINQ-to-SQL

linq Мне довольно долго LINQ казалось совершенно бесполезной технологией. Достаточно осознать задекларировнные при разработке LINQ цели: "Традиционно запросы к данным выражаются в виде простых строк без проверки типов при компиляции или поддержки IntelliSense. Разработчику приходится изучать различные языки запросов для каждого типа источника данных: базы данных SQL, XML-документов".

На первый взгляд LINQ кажется совершенно бесполезной придумкой - ну действительно, для чего мне изучать кривые на всю голову конструкции, которые покрывают процентов 20% от нормального SQL-оператора SELECT (который я знаю в совершенстве в различных видах SQL-серверов) - плюс к тому же все эти на всю голову кривые конструкции обеспечивают функционал только SELECT. А Update где в этих кривых конструкциях? А где INSERT? Где With, рекурсии, пейджинг, UNION и все-все-все что любой программист знает уже даже после первого года SQL-программирования?

С XML все еще более кривое - где все функции XSLT? Только в бейсике (но не в более убогом шарпе!) есть доступ к свойствам осей навигации по XML. И это все что удосужились реализовать микрософтовцы?

И наконец, главное - произведя сборку высокореляционных данных на уровне приложения на VB или шарпе - на уровне данных в SQL получается полная ерунда (как в 1С) - куча каких-то помоев из мелких высокореляционных табличек. С которым невозможно работать из PHP, JAVA, из простого старого ASP и прочих технологий (фактически сборку осмысленных данных из высокореляционных придется повторить).

И вообще, насколько корректно будет работать LINQ for SQL с более развитыми бесплатными и кроссплатформенными SQL-серверами, например PostgreSQL? Или это опять какая-то микрософтовская придумка, чтобы загнать .NET-программистов на Win-платформу? Ведь MS SQL (даже в кастрированном Express-функционале) по прежнему не существует в Linux.

В общем кажется что LINQ полнейшая чушь - в нескольких кривых на всю голову синтаксических конструкциях реализовано 20% одного из SQL-операторов и 20% XSLT - при этом SQL-сервер рассматривается не как самостоятельная ценность, а как женщина которой заглядывают под юбку. В LINQ-подходе данные в SQL-сервере являются уже на самостоятельным и самоценным хранилищем данных, доступным из многих технологий, а некая вспомогательная приблудка, функционал которой ограничен убогим LINQ (одним единственным усеченным до безобразия SQL-оператором к тому же размещенным не там где он должен быть - в слое данных, а выше - на прикладном уровне приложения).

Фактически LINQ - это специфическая технология для тех, кто выбирает сразу помногу данных из SQL, кому лень изучать возможности SQL-сервера (но не лень изучать LINQ), кому от SQL-сервера почти ничего не нужно, кто не понимает убогости MS SQL относительно более мощных SQL-серверов, кто не рассчитывает на долгую жизнь своей проги и кто не рассчитывает, что с его данными будут работать другие программисты на других технологиях, кому лень составлять ручные обвязки (но он не хочет пользоваться более старыми и более ограниченными способами разметки буферов памяти из NET 2.0). Примерно то же самое можно сказать и об XLINQ - LINQ for XML.

Осознавая все это, я довольно долго искал ту самую нишу, где эту тупую микрософтовскую приблудку можно все-таки применить хоть с каким-нибудь полезным эффектом. И нашел! Об этой находке я и хочу рассказать на этой страничке.


Теоретически все началось с того, что было придумано ADO.NET, в котором данные получались из SQL-сервера не построчно, а можно было вычитать в буфер памяти сразу несколько строк (сколько выдает SELECT). Для размещения буфера в памяти были придуманы нетипизированные буфера (где каждая ячейка фрагмента данных имеет только номер) и типизированные буфера, где кроме номер ячейки с данными в ОЗУ имеют те же имена, что и в SQL-сервере (и те же типы данных). В буфер первого типа данные обычно грузятся по DT.Load(DR), а буфера с именами и типами данных можно создать либо диайнером студии, либо вручную DT.Column.Add.

Вся эта технологическая цепочка на момент введения ADO.NET была в разработке завершена некоторым тупиком. Допустим мы выбрали в буфер в памяти тысячу строк и как с ними дальше работать? Разработка ADO.NET в версии 1.1 была завершена в столь ублюдочном виде, что даже дизайнерами самой студии нельзя было создать типизированный DataSet, в NET 2.0 это стало уже возможно, но как работать с данными, содержщими множество строчек?

Чтобы хоть как-то завершить разработку - был придуман кривой на всю голову статический класс DataView - единственное что он позволял - это установить свойство FILTER, в котором можно было указать сортировку ASC/DESC.

Если смотреть с этой точки зрения - то LINQ to Dataset конечно огромный прогресс ADO.NET. Теперь в NET 4.0 - можно не просто тупо наложить простейший фильтр на наборы считанных из SQL данных, но в этом наборе данных сделать отборы, которые уже процентов на 20% дают функционал отборов из SQL-сервера (на родном языке SQL-cервера). Ну разве что синтаксис LINQ кривоват, ну да ладно - это все равно лучше чем получать DataView с помощью Filter.

Впрочем, есть другой подход - не забивать буфера в ОЗУ хламом, а выбирать в память только то что нужно (построчно), тогда код выглядит вот так (при использовании простейшей обвязки) . При таком подходе к программированию вообще не возникает никакого зазора для LINQ for Dataset или LINQ for SQL (при вызове SQL-процедур доступен полный функционал любого SQL-сервера а в буфере данных в ОЗУ оказывается только нужная строка данных).

Что касается обработки XML (XLINQ) - то все всегда пользовались Альтовой (или OpenSource аналогами) - просто тыкаешь мышкой в XML и альтова (или аналог) показывает путь XPATCH и все возможные дальнейшие действия с функциями, доступными из этой точки XML. При таком подходе код обработки XML выглядит вот так. Никаких проблем при этом способе обработки XML не бывает - все функции XSLT преобразования полностью доступны и зазора для LINQ тоже нет.


Итак, области памяти для считывания данных из SQL-сервера можно размечать вручную - но если это делать лень, то на помощь может прийти или DataSet'ы из NET 1.1-NET 2.0 или LINQ из NET 3.5-NET 4.0. Выше я обьяснил почему буфера в ОЗУ, размеченные с помощью LINQ все-таки предпочтительнее, чем буфера в памяти, размеченые как DataSet. Кроме того, в Visual Studio 2010 (NET 4.0) можно удобно создававать методы заполнения буфера данными простым перетаскиванием процедур мышкой в область методов в дизайнере LINQ-буферов. Это все что я сумел найти полезного в LINQ for DataSet и LINQ for SQL.

Шаблон кода при работе с LINQ for SQL, который я для себя выработал - вот такой:


   1:             Try
   2:                  'кеширование чтения профиля из базы
   3:                  Dim UserPaymentProfile1 As System.Collections.Generic.List(Of GetOneUserPaymentProfileResult)
   4:                  If Session("UserPaymentProfile") Is Nothing Then
   5:                      Dim AirtsDB As New UserPaymentProfileDataContext()
   6:                      Dim AirtsDB_MemoryBuf As System.Collections.Generic.List(Of GetOneUserPaymentProfileResult) = AirtsDB.GetOneUserPaymentProfile(New Guid(CheckUser1.id)).ToList
   7:                      Session("UserPaymentProfile") = AirtsDB_MemoryBuf
   8:                  End If
   9:                  UserPaymentProfile1 = Session("UserPaymentProfile")
  10:                  '
  11:                  Dim CommonProfile_xml As Collections.Generic.IEnumerable(Of XElement) = From AllColumn In UserPaymentProfile1 Select AllColumn.CommonProfile
  12:                  ...

Здесь GetOneUserPaymentProfile - имя SQL-процедуры для доступа к данным, .NET обвязку к которой я создал непосредственно в дизайнере студии с помощью mouse drag-and-drop. При этом доступ к таблицам осуществляется как для типизированного ДатаСета - с полной подсказкой. Ну и естественно нет никаких массивных ручных определений классов для разметки буфера работы с данными.


Я нашел также полезную фишку в XLINQ - вместо вот таких классов по сборке XML из отдельных свойств и вот таких классов по парсингу XML с помощью XPATCH (при этом способе XML-программирования используются внешние инструменты типа Альтова или OpenSource аналоги) можно делать вот так:


   1:              ...
   2:              'кеширование чтения профиля из базы
   3:              Dim UserPaymentProfile1 As System.Collections.Generic.List(Of GetOneUserPaymentProfileResult)
   4:              If Session("UserPaymentProfile") Is Nothing Then
   5:                  Dim AirtsDB As New UserPaymentProfileDataContext()
   6:                  Dim AirtsDB_MemoryBuf As System.Collections.Generic.List(Of GetOneUserPaymentProfileResult) = AirtsDB.GetOneUserPaymentProfile(New Guid(CheckUser1.id)).ToList
   7:                  Session("UserPaymentProfile") = AirtsDB_MemoryBuf
   8:              End If
   9:              UserPaymentProfile1 = Session("UserPaymentProfile")
  10:              '
  11:              Dim CommonProfile_xml As Collections.Generic.IEnumerable(Of XElement) = From AllColumn In UserPaymentProfile1 Select AllColumn.CommonProfile
  12:              '
  13:              If CommonProfile_xml(0) IsNot Nothing Then
  14:                  CommonProfile_xml(0).@a:Address = tx_Address.Text
  15:                  CommonProfile_xml(0).@a:BankBIK = tx_BankBIK.Text
  16:                  CommonProfile_xml(0).@a:BankName = tx_BankName.Text
  17:                  CommonProfile_xml(0).@a:BankRS = tx_BankRS.Text
  18:                  CommonProfile_xml(0).@a:BIK = tx_BIK.Text
  19:                  CommonProfile_xml(0).@a:FullName = tx_FullName.Text
  20:                  CommonProfile_xml(0).@a:KPP = tx_KPP.Text
  21:                  CommonProfile_xml(0).@a:OKPO = tx_OKPO.Text
  22:                  '
  23:                  UserPaymentProfile1(0).CommonProfile = CommonProfile_xml(0)
  24:                  '
  25:                  Dim AirtsDB1 As New UserPaymentProfileDataContext()
  26:                  AirtsDB1.SaveCommonUserProfile(New Guid(CheckUser1.id), UserPaymentProfile1(0).toParentUser.ToString, tx_ShortName.Text, CommonProfile_xml(0), UserPaymentProfile1(0).AllowModule)
  27:              Else
  28:                  Dim NewXML As New XElement(<CommonProfile/>)
  29:                  '
  30:                  NewXML.@a:Address = tx_Address.Text
  31:                  NewXML.@a:BankBIK = tx_BankBIK.Text
  32:                  NewXML.@a:BankName = tx_BankName.Text
  33:                  NewXML.@a:BankRS = tx_BankRS.Text
  34:                  NewXML.@a:BIK = tx_BIK.Text
  35:                  NewXML.@a:FullName = tx_FullName.Text
  36:                  NewXML.@a:KPP = tx_KPP.Text
  37:                  NewXML.@a:OKPO = tx_OKPO.Text
  38:                  '
  39:                  UserPaymentProfile1(0).CommonProfile = NewXML
  40:                  '
  41:                  Dim AirtsDB2 As New UserPaymentProfileDataContext()
  42:                  AirtsDB2.SaveCommonUserProfile(New Guid(CheckUser1.id), UserPaymentProfile1(0).toParentUser.ToString, tx_ShortName.Text, NewXML, UserPaymentProfile1(0).AllowModule)
  43:                  ...

Здесь я вычитал XML-поле CommonProfile_xml из SQL и сначала отобразил его на форме, а потом фрагментом кода, который вы видите, сохранил в базу исправления, которые внес юзер (процедурой SaveCommonUserProfile). Все работает с полной подсказкой.

Обратите также внимание на строку VB-кода 28 (на скрине это строка 79) - здесь видно насколько компилятор бейсика является более интеллектуальным, чем компилятор шарпа - можно напрямую в текст вносить XML (с удобной подсветкой и подсказкой). Наличие директивы Import также добавляет атрибут с определением пространства имен XML при каждом добавлении XML-тега. Ну и как я говорил выше - в бейсике доступны такие вещи, как свойства осей XML - к чему в более убогом шарпе нет доступа в принципе. В этом случай компилятор бейсика выступает уже не только как более высокоуровневый и интеллектуальный - но и как более функциональный и низкоуровневый, чем компилятор шарпа.

Для того, чтобы в LINQ for XML заработала XML-подсказка надо выполнить две вещи - сделать схему XML и внести директиву подобную этой

                       Imports <xmlns:a="http://airts.asp-net.ru/">
на страничку где нужна подсказка по XML. Кроме того, имя пространства имен схемы можно внести для всего проекта (в Win-приложениях в ту же вкладку свойств проекта, где мы добавляем ссылки на библиотеки).

Еще я хотел бы обрать внимание на то, что Visual Studio 2010 создает такие XSD-схемы, которые сама использовать для XML-подсказки не может. Почему это происходит я не знаю. Скажем для вот такого XML:


   1:  <AllowModule xmlns:p1="http://airts.asp-net.ru/" p1:Assist="1" p1:Sirena="1" p1:Inbox="1" />

правильной схемой является вот такая:


   1:  <?xml version="1.0" encoding="utf-8"?>
   2:  <xs:schema xmlns="http://airts.asp-net.ru/" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://airts.asp-net.ru/" attributeFormDefault="qualified">
   3:      <xs:element name="AllowModule">
   4:          <xs:complexType>
   5:              <xs:attribute name="Assist" use="optional">
   6:                  <xs:simpleType>
   7:                      <xs:restriction base="xs:byte"/>
   8:                  </xs:simpleType>
   9:              </xs:attribute>
  10:              <xs:attribute name="Sirena" use="optional">
  11:                  <xs:simpleType>
  12:                      <xs:restriction base="xs:byte"/>
  13:                  </xs:simpleType>
  14:              </xs:attribute>
  15:              <xs:attribute name="Inbox" use="optional">
  16:                  <xs:simpleType>
  17:                      <xs:restriction base="xs:byte"/>
  18:                  </xs:simpleType>
  19:              </xs:attribute>
  20:          </xs:complexType>
  21:      </xs:element>
  22:  </xs:schema>

а для более сложного XML с вложенными тегами такого XML правильная XML-схема для Intellisense будет такая. Такой XML создается и обрабатывается с полной Intellisense на XLINQ вот так.


На этом более сложном примере видна также вся бестолковость XLINQ - согласитесь, что работать на простом XPATCH с простыми строками (без XLINQ-посказки) гораздо удобнее. Строки в XPATCH-навигации можно менять динамически, удобнее сделать циклы. К тому же Visual Studio не умеет делать правильные XML-схемы. Да и выборка данных из SQL без LINQ выглядит гораздо проще. Собственно говоря этот же самый фрагмент того же самого класса без LINQ, без правильного изготовления XML-схем, без изучения LINQ, без создания типизированных LINQ-буферов данных дизайнером VS2010, без возни с пространствами имен XML и всего остального - выглядит вот так.

Ну собственно бестолковость XLINQ очевидна даже для MS : "LINQ to XML не предназначен для замены XSLT. XSLT все еще является лучшим средством для сложных XML-преобразований".


Я заметил, что у меня на сайте опубликованы десятки моих различных SQL-процедур, сделанных на XSLT, различных SQL-CLR-сборок, сделаных на XSLT, всякие хитрые движки на XSLT, многочисленные шлюзы, разбирающие XML, но совсем мало примеров на LINQ. Поэтому я выложу тут еще одну версию той же формы, что я показал выше. Эту форму я переделывал больше десятка раз. Первую версию - на простом классе, когда предполагались четко зафиксированные набор свойств, была фиксированная схема XML и по существу могли меняться только данные (значения XML-атрибутов) - я показал выше. Потом я переделал эту же форму на LINQ - в условиях когда атрибуты, теги и пространства имен были изначально известны и четко определены. Предполагалось, что XML-схема меняться не будет. Это позволило загрузить XSD-схему в каталог проекта Visual Studio, указать директиву Import и работать на LINQ с прекрасной подсказкой (небольшой фрагмент кода из этого варианта я показал выше).

Однако позже выяснилось, что схема может меняться администратором системы. От статической подсказки Visual Studio пришлось отказаться. Пришлось отказаться и от сервиса компилятора VB, который автоматически умеет добавлять пространство имен, указанное в Import. Потом выяснилось, что некие атрибуты у некоторых тегов должны все же быть всегда и админам системы нельзя позволять менять схему произвольно. Потом выяснилось, что и имя схемы может иногда меняться, потом выяснилось что эта же форма должна работать с несколькими разными XML-полями в SQL и так далее и так далее - уже изготовлено более 10 вариантов этой формы.

Поэтому я решил показать тут один промежуточный вариант этой формы на LINQ, в котором начинающие программисты могут увидеть для себя много поучительного. Этот вариант состоит из трех форм и работает с динамически формируемой админом системы XSD-схемой с фиксированным пространством имен, но работающий с несколькими различными XML-полями в базе. При этом пользователи системы могут вносить данные по схеме, определенной админом системы, имя схемы определено статически, но никаких изначальных ограничений на построение админом схемы данных этот вариант кода не предусматривает. Я выбрал для показа на сайте именно этот вариант (из более чем десяти вариантов), потому что именно этот вариант кода наиболее легко гнется в любую сторону.

В заключение я бы хотел порекомендовать начинающим программистам одну микрософтовскую прогу - которая лично мне изначально помогала в изучении LINQ. И еще пару практических примеров применения LINQ можно посмотреть на страничках - Как сделать простейший Web-handler - формирующий XML или JSON, Делегаты сравнения для сортировки и Linq-отборов в VB.NET, Как сделать SOAP/WSDL-вебсервис на ASP.NET/MONO для вызова его из FLEX.



Комментарии к этой страничке ( )
ссылка на эту страничку: http://www.vb-net.ru/Linq/index.htm
<На главную>  <В раздел ASP>  <В раздел NET>  <В раздел SQL>  <В раздел Разное>  <Написать автору>  < Поблагодарить>