Извлекаем пользу из LINQ
Эта заметка является продолжением осмысления целей Linq Что заменила Linq?. Поначалу LINQ казалось совершенно бесполезной технологией. Достаточно осознать задекларированные при разработке LINQ цели: "Традиционно запросы к данным выражаются в виде простых строк без проверки типов при компиляции или поддержки IntelliSense. Разработчику приходится изучать различные языки запросов для каждого типа источника данных: базы данных SQL, XML-документов". Что за глупость? Посмотрите например на LINQ to SQL Debug Visualizer - что вам проще использовать? Тупые и нелепые индусские новоязы или стандартизированный до умопомрачения и понятный-предсказуемый язык SQL?
На первый взгляд 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.vb-net.com/">на страничку где нужна подсказка по XML. Кроме того, имя пространства имен схемы можно внести для всего проекта (в Win-приложениях в ту же вкладку свойств проекта, где мы добавляем ссылки на библиотеки).
Еще я хотел бы обрать внимание на то, что Visual Studio 2010 создает такие XSD-схемы, которые сама использовать для XML-подсказки не может. Почему это происходит я не знаю. Скажем для вот такого XML:
1: <AllowModule xmlns:p1="http://airts.vb-net.com/" p1:Assist="1" p1:Sirena="1" p1:Inbox="1" />
правильной схемой является вот такая:
1: <?xml version="1.0" encoding="utf-8"?>
2: <xs:schema xmlns="http://airts.vb-net.com/" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://airts.vb-net.com/" 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.
|