Пример использования LINQ-to-XML (XLINQ) и LINQ-to-SQL.
Странность микрософтовских технологий в том, что для их использования необходимо знать нечто совершенно противоположное, чем знает любой программист на этой планете (не знающий о существовании микрософта).
Взять например LINQ-to-XML. На всех платформах в нашем мире поддерживается XSLT. Более того - это наверное по количеству внедрений - самая распространенная на планете технология. Достаточно сказать что любой мобильник и любой браузер ее поддерживает. Нет такого языка програмирования или платформы - где бы XSLT не поддерживалось. И нет такого программиста, который бы не знал XSLT. Эта старинная и полностью сертифицированная всеми возможными международными организациями технология. У меня на сайте есть несколько древних топиков, посвященных XSLT - от 2005-го и 2002-года рождения.
Тем не менее Мискрософт выпускает свою собственную технологию с полностью аналогичным XSLT назначением. Ничем не отличающимся от XSLT в лучшую сторону (подсказка по навигации как была в Altova - и как ее не было в Visual Studio по XSLT - так ее и с XLINQ нет и сейчас). При этом в Visual Studio есть довольно неплохой отладчик XSLT (я бы даже назвал его лучшим в мире), сама технология XSLT превосходно интегрирована в NET Framework - однако микрософт выпукает (и усиленно рекламирует) новую, нигде более не поддерживаемую и ничем не выделяющуюся в лучшую сторону технологию XLINQ. Скорее даже в хужшую (ибо даже для XSLT в Visual Studio есть отладчик), а отладить XLINQ-выражение практически невозможно. Ну и дела...
Все прочие продукты микрософта - например MS SQL сервер поддерживают только стандартный XSLT (причем неплохо) - но совершенно не знают о существовании XLINQ.
В общем цели разработки XLINQ остались для меня загадкой. Ну разве что какие-то более серьезные структуры, чем империя Билла Гейтса поддержат XLINQ (например WTP) и внедрят XLINQ допустим в ECLIPSE и NETBEANS. И сама Microsoft поддержит свою собственную инициативу и создаст для Visual Studio хотя бы отладчик выражений XLINQ.
Еще более удивительная и чудовищная технология - LINQ-TO-SQL. Стандарт обращения к реляционным данным - язык SQL - многократно сертифицирован всеми международными организациями - например SQL92 или SQL99. И нет на нашей планете реляционной СУБД, которая бы не поддерживала некий диалект стандартного языка манипулирования реляционными данными - SQL. Тем не менее Микрософт выпускает LINQ-TO-SQL, который ничем не лучше стандартного SQL, ему надо долго учиться (ибо все команды стандартного SQL перековерканы, изуродованы и переставлены в другом порядке). Правда, в случае LINQ-to-SQL есть хотя бы подсказка - как построить это чудовищное выражение. Но другие продукты, например MS SQL сервер ничего не знают о таком удивительном способе манипуляции реляционными данными, а все остальные продукты от Билла Гейтса знают (и хорошо умеют работать) лишь со стандартными SQL-запросами (и ничего не знают о LINQ-To-SQL).
Хотя в случае LINQ-to-SQL я хотя бы понимаю - откуда выросли ноги у этой технологии. Это своего рода развитие разметки кеша памяти для хранения данных, полученных из SQL - то что раньше называлось типизированным датасетом. Но... для того, что таким образом разметить буфер памяти - надо иметь доступ к свойствам обьектной модели SQL-сервера. Ведь простым запросом нельзя понять что возвращает при обращении к процедуре SQL сервер. Для этого надо подать правильные параметры на вход процедуры - иначе будет возвращено например NULL. А имея доступ к откомпилированной процедуре через SMO management MS SQL - можно все понять о процедуре. Но у PostgreSQL, MySQL, SQLITE и прочих механизмов хранения реляционных данных нет доступа к обьектной можели откомпилированной процедуры. Значит LINQ-TO-SQL не будет давать подсказку на этапе программирования. А тогда в чем ее преимущество относительно стандартных SQL-запросов?
Кроме того, все практикующие программисты наработали себе сервисные библиотеки, позволяющие облегчить формирование SQL-запросов и утапливающие паразитный код работы с датасетами во внутрь кода библиотек. Так сделал и я - вот например одна из моих библиотек для работы с SQL - ADO.NET обвязка для работы с MySQL в ASP.NET - причем она удобнее LINQ-To-SQL в двух ракурсах - она использует абсолютно стандартный язык запросов к реляцинным данным (SQL) и она позволяет работать с любым SQL-сервером, в частности по этой ссылке вы видите версию для MySQL (и так же удобно моя библиотека работает с PostgreSQL и SQLite).
Итак, этот топик будет посвящен странной и чудовищной (нигде более не повторяемой технологии) LINQ-To-XML и LINQ-To-SQL. Тем не менее этот пример будет полезен ибо в нем будет использована так же моя библиотека - WCF_CLIENT - клиент Web-сервиса (которая была мною написана из-за кривизны микрософтовского клиента WCF и невозможности его использования во многих практических ситуациях).
Задачка, которую я покажу в этом топике будет заключаться в сохранении справочников сервиса торговли авиабилетами http://davs.ru/ в базу. Я, как и прежде - стараюсь публиковать один-два процента кода из каждой своей системы на своем сайте - чтобы в интернете присутствовали примеры настоящего живого кода из настоящий проектов настоящего практикующего программиста (а не только фрагменты кода из учебников от Мискрософта, где какие-то проплаченные мошенники типа Дино Экспозито - даже не имеющего согласно его же собственных заверений постоянного места жительства и завершенных работающих в боевом режиме проектов - за долю малую от Билла Гейтса что-то пытаются гадить в мозг программистам.
Для начала создадим в MS SQL таблички, в которые мы сохраним данные. Таблички создадим обычным образом, пользуясь MS SQL (никакое LINQ-to-SQL проектированию базы не поможет) - ибо LINQ-to-SQL - это дополнительный чемодан знаний, который надо таскать с собой, но никак не замена знаний реляционной теории.
1:
2: CREATE TABLE [dbo].[City](
3: [i] [int] IDENTITY(1,1) NOT NULL,
4: [ID] [nvarchar](10) NOT NULL,
5: [Name] [nvarchar](50) NOT NULL,
6: [ToCountry] [nvarchar](10) NULL,
7: CONSTRAINT [PK_City] PRIMARY KEY CLUSTERED
8: (
9: [i] ASC
10: )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
11: ) ON [PRIMARY]
12: GO
13: CREATE TABLE [dbo].[AviaCompany](
14: [i] [int] IDENTITY(1,1) NOT NULL,
15: [ID] [nvarchar](10) NOT NULL,
16: [Name] [nvarchar](50) NOT NULL,
17: [Popular] [int] NOT NULL,
18: CONSTRAINT [PK_AviaCompany] PRIMARY KEY CLUSTERED
19: (
20: [i] ASC
21: )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
22: ) ON [PRIMARY]
23: GO
24: CREATE TABLE [dbo].[Airport](
25: [i] [int] IDENTITY(1,1) NOT NULL,
26: [ID] [nvarchar](10) NOT NULL,
27: [Name] [nvarchar](50) NOT NULL,
28: [Popular] [int] NOT NULL,
29: [ToCountry] [nvarchar](10) NOT NULL,
30: CONSTRAINT [PK_Airport] PRIMARY KEY CLUSTERED
31: (
32: [i] ASC
33: )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
34: ) ON [PRIMARY]
35: GO
36: CREATE TABLE [dbo].[Country](
37: [ID] [nvarchar](10) NOT NULL,
38: [Name] [nvarchar](50) NOT NULL,
39: CONSTRAINT [PK_Country] PRIMARY KEY CLUSTERED
40: (
41: [ID] ASC
42: )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
43: ) ON [PRIMARY]
44: GO
45: create View [dbo].[CityCountry]
46: as
47: SELECT [dbo].[City].*, [dbo].[Country].Name as Country
48: FROM [dbo].[City]
49: join [dbo].[Country] on [dbo].[City].ToCountry=[dbo].[Country].ID
50: GO
Для начала разметим буфер в ОЗУ для чтения данных из SQL-сервера (снабдим ячейки памяти символическими именами - причем так, чтобы эти имена соответствовали именам полей в SQL-сервере). Для этого надо просто добавить дизайнер LINQ-to-SQL и перетащить drag-and-drop'oм ему на морду таблички из SQL-сервера. И все. Не спорю - это приятнее чем размечать буфера памяти вручную. Это удачное развитие типизированных DataSet. Но зачем же было уродовать срандартный язык запросов SQL? И зачем создавать такую технологию, для которой изначально понятно, что она не будет работать нигде кроме MS SQL? Да, макаронник Тони Экспозито и какой-то индус Марио Шпушта нахваливает бригаду Билла Гейтса за такие чудо-идеи - но почему-то мой русский извращенный ум не может это решение принять как озарение.
Теперь когда буфера памяти размечены - сохранить их в базу можно командами InsertOnSubmit и SubmitChanges. Удобнее это чем было просто в ADO.NET - ExecuteNonQuery? Не знаю, преимуществ не вижу. Ну разве что две-три строки паразитного кода работы с датасетами теперь можно пропустить. Ну дык и так были у всех свои наработанные библиотеки, вставляющие эти пару строк паразитного кода.
Посмотрим внимательнее на задачку - обращаясь с серверу DAVS можно получить ответы в виде XML - которые надо распарсить и уложить в базу. Начнем с приятного - Visual Studio наконец-то в 2010-м году научилась подсвечивать XML и его наконец-то можно вводить не в виде строк, а напрямую (как в NetBeans и прочих средах). Для этого надо обьявить XML в виде типа данных XElement. Это приятно.
Но что же теперь вам в студии помешало сделать подсказку по выражению LINQ-to-XML? Однажды в каком-то проекте у меня даже подсказка по тегам засветилась - но, увы, сразу же пропала. И не лучше ли было микрософту не строить свой собственный сарайчик вдалеке от цивилиации, а сделать подсказку по XSLT?
Итак сервис DAVS хранит три вида справочников - соответственно мой загрузчик этих справочников умеет (с помощью моей библиотеки WCF_CLIENT - клиент Web-сервиса) отсылать эти запросы к сервису, принимать и декодировать в правильную кодировку ответы. Для начала готовим форму - где можно выбрать вид справочника, который мы хотим загрузить в базу.
И теперь вводим собственно несложный код загрузчика - который состоит из парсера на XLINQ и загрузчика распрарсенных данных с помощью Linq-to-SQL:
1:
2: Partial Class Admin
3: Inherits System.Web.UI.Page
4:
5:
6: Dim CountriesXML As XElement = <request>
7: <action>countries</action>
8: <gzip>0</gzip>
9: <ip>127.0.0.1</ip>
10: <encoding>utf-8</encoding>
11: </request>
12:
13:
14: Dim AviaXML As XElement = <request>
15: <action>avia</action>
16: <gzip>0</gzip>
17: <ip>127.0.0.1</ip>
18: <encoding>utf-8</encoding>
19: </request>
20:
21: Dim CitiesXML As XElement = <request>
22: <action>cities</action>
23: <gzip>0</gzip>
24: <ip>127.0.0.1</ip>
25: <country>RU</country>
26: <encoding>utf-8</encoding>
27: </request>
28:
29: Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
30: If Not IsPostBack Then
31: ResponseXML1.Text = ""
32: lerr1.Text = ""
33: Select Case DropDownList1.Text
34: Case "countries"
35: Session("XML1") = CountriesXML.ToString
36: Case "cities"
37: Session("XML1") = CitiesXML.ToString
38: Case "avia"
39: Session("XML1") = AviaXML.ToString
40: End Select
41: End If
42:
43: End Sub
44:
45: Protected Sub SendButton1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SendButton1.Click
46: Try
47: Dim ResponseXML As String
48: Select Case DropDownList1.Text
49: Case "countries"
50: ResponseXML = (New Wcf_Client).PostRequest(Wcf_Client.PostRequestEncode.UTF8, "http://xml.davs.ru/gate/index.php", Wcf_Client.PostRequestEncode.UTF8, 0, "req_xml", CountriesXML.ToString)
51: ResponseXML1.Text = Server.HtmlEncode(ResponseXML)
52: LoadCountry(ResponseXML)
53: Case "cities"
54: Dim AviaDB As New AviaDBDataContext
55: Dim RequestXML As XElement = CitiesXML
56: Dim Coutries = (From X In AviaDB.Country Select X).ToList
57: If Coutries IsNot Nothing Then
58: For Each One In Coutries
59: RequestXML...<country>(0).Value = One.ID
60: ResponseXML = (New Wcf_Client).PostRequest(Wcf_Client.PostRequestEncode.UTF8, "http://xml.davs.ru/gate/index.php", Wcf_Client.PostRequestEncode.UTF8, 0, "req_xml", RequestXML.ToString)
61: ResponseXML1.Text &= One.ID & ","
62: LoadAllCityInCountry(One.ID, ResponseXML)
63: Next
64: End If
65: Case "avia"
66: ResponseXML = (New Wcf_Client).PostRequest(Wcf_Client.PostRequestEncode.UTF8, "http://xml.davs.ru/gate/index.php", Wcf_Client.PostRequestEncode.UTF8, 0, "req_xml", AviaXML.ToString)
67: ResponseXML1.Text = Server.HtmlEncode(ResponseXML)
68: LoadAvia(ResponseXML)
69: End Select
70: Catch ex As Exception
71: lerr1.Text = ex.Message
72: End Try
73: End Sub
74:
75: Sub LoadCountry(ByVal ResponseXML As String)
76: Dim RDR1 As New System.IO.StringReader(ResponseXML)
77: Dim XE As New XDocument
78: XE = XDocument.Load(RDR1)
79: Dim AviaDB As New AviaDBDataContext
80: For Each One In XE.<data>.<response>.<items>.Elements
81: Dim New1 As New Country With {.ID = One.@id.ToString.Trim, .Name = One.Value.ToString.Trim}
82: AviaDB.Country.InsertOnSubmit(New1)
83: Next
84: AviaDB.SubmitChanges()
85: End Sub
86:
87: Sub LoadAvia(ByVal ResponseXML As String)
88: Dim RDR1 As New System.IO.StringReader(ResponseXML)
89: Dim XE As New XDocument
90: XE = XDocument.Load(RDR1)
91: Dim AviaDB As New AviaDBDataContext
92: For Each One In XE.<data>.<response>.<items>.Elements
93: Dim New1 As New AviaCompany With {.ID = One.@id.ToString.Trim, .Name = One.Value.ToString.Trim, .Popular = One.@popular.ToString.Trim}
94: AviaDB.AviaCompany.InsertOnSubmit(New1)
95: Next
96: AviaDB.SubmitChanges()
97: End Sub
98:
99: Sub LoadAllCityInCountry(ByVal CountyID As String, ByVal ResponseXML As String)
100: Dim RDR1 As New System.IO.StringReader(ResponseXML)
101: Dim XE As New XDocument
102: XE = XDocument.Load(RDR1)
103: Dim AviaDB As New AviaDBDataContext
104: For Each One In XE...<city>
105: Dim New1 As New City With {.ID = One.@id.ToString.Trim, .Name = One.Value.ToString.Trim, .ToCountry = CountyID}
106: AviaDB.City.InsertOnSubmit(New1)
107: Next
108: AviaDB.SubmitChanges()
109: For Each One In XE...<airport>
110: Dim New1 As New Airport With {.ID = One.@id.ToString.Trim, .Name = One.Value.ToString.Trim, .ToCountry = CountyID, .Popular = One.@popular.ToString.Trim}
111: AviaDB.Airport.InsertOnSubmit(New1)
112: Next
113: AviaDB.SubmitChanges()
114: End Sub
115:
116: End Class
В принципе код настолько тривиален, что комментировать его неловко. Запросы на языке Linq-To-SQL (который контора Билла Гейтса придумала взамен стандартного языка SQL) - вы можете видеть в строке 56. Выражения Linq-to-SQL (которые контора Билла Гейтса придумала взамен стандартного XSLT) вы можете видеть в строках 59, 81, 93, 105, 110. Новый Linq-to-SQL механизм записи в базу (который контора Билла Гейтса придумала взамен стандартного ADO.NET механизма ExecuteNonQuery) - вы можете видеть в строках 82, 84, 94, 96, 106, 111, 113.
Теперь я покажу пример задачки (с этими же примерно данными), которые на мой взгляд нерешаемы на XLINQ в каких-то разумных обьемах понятного кода. Но для стандартной XSLT - это лишь простейшая минимальная задачка. В более реальном практически применяемом виде XSLT-преобразование для предложений по билетам будет выглядеть как слияние нескольких XML-файликов в один (с попутным приведением структуры всех предложений к одному формату) и пересортировкой общего файла по цене (или динамической пересортировкой по разным критериям). Но это решение я публиковать не буду - ибо это получится уже не просто демонстрация подходов решения практической задачки реально практикующего програмиста, а разглашение довольно серьезного процента (не менее 5%) коммерческого решения.
Но рассмотрим пока лишь такую минимальную задачку преобразования XML, - в этом же проекте в качестве ответа от сервиса расписания авиабилетов я получил вот такой XML - он слишком велик чтобы его публиковать полностью - возьмите его по ссылке чтобы понять о чем речь. Его структура (показана Альтовой) - как видите, внутри этого XML файла находится 52 блока предложений по ценам авиабилетов - но они неотсортированы.
Естественно, перед показом ценовых предложений на сайте - их надо просортировать вот так. В данном случае я сделал сортировку вот таким простейшим XSLT-преобразованием:
1: <?xml version="1.0" encoding="utf-8"?>
2: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
3: xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
4: >
5: <xsl:output method="xml" indent="yes"/>
6:
7: <xsl:template match="/">
8: <xsl:element name="data">
9: <xsl:copy-of select="/data/session"/>
10: <xsl:copy-of select="/data/action"/>
11: <xsl:element name="response">
12: <xsl:apply-templates select="/data/response/items" />
13: <xsl:copy-of select="/data/response/transfer"/>
14: <xsl:copy-of select="/data/response/next_action"/>
15: </xsl:element>
16: </xsl:element>
17: </xsl:template>
18:
19: <xsl:template match="/data/response/items" >
20: <xsl:copy>
21: <xsl:for-each select="//item">
22: <xsl:sort select="@price"/>
23: <xsl:copy-of select="."/>
24: </xsl:for-each>
25: </xsl:copy>
26: </xsl:template>
27:
28: </xsl:stylesheet>
При этом Visual Studio имеет прекрасный пошаговый отладчик XSLT:
А сама по себе XSLT трансформация прекрасно интегрирована в .NET Framework. Вот такая моя функция берет для преобразования на вход XML (как строку) и выдает в виде строки (преобразованный через XSLT-трансформацию) XML-файлик. Собственно XSLT-трансформация задается в виде имени файлика на сервере и отлаживается отдельно от кода сайта.
1: Dim SortResult As String = XSLT(NoSortXML, System.IO.Path.Combine(Server.MapPath("~"), "SortResponse.xslt"))
2: ...
3: ...
4: ...
5: Function XSLT(ByVal InputXML As String, ByVal XSLT_URI As String) As String
6: Dim XSLTTransform As New System.Xml.Xsl.XslCompiledTransform
7: XSLTTransform.Load(XSLT_URI)
8: Dim Response_XML As System.Xml.XmlDocument = New System.Xml.XmlDocument
9: Response_XML.LoadXml(InputXML)
10: Dim OutXMLsettings As New System.Xml.XmlWriterSettings()
11: OutXMLsettings.Indent = True
12: OutXMLsettings.IndentChars = vbTab
13: Dim OutString As New StringBuilder
14: Dim XMLBuf As System.Xml.XmlWriter = System.Xml.XmlWriter.Create(OutString, OutXMLsettings)
15: XSLTTransform.Transform(Response_XML, XMLBuf)
16: Return OutString.ToString
17: End Function
Но есть ситуации, когда XLINQ может быть полезен - в том и состоит мастерство программиста, чтобы выбрать правильный и наиболее подходящий для решения задачки инструмент. Например чтобы вывести данные из показанного выше отсортированного XML на форму примерно вот в таком виде:
нужен некий парсер, результат работы которого надо скормить репитеру (точнее вложенной или рекурсивной системе репитеров). Код парсера я публиковать не буду - ибо это уже коммерческое решение, но покажу подходы для решения этой задачки.
Первый подход, который был всегда (до изобретения XLINQ индусами Билла Гейса ) - отобрать ноды по XPATCH и скормить эту коллекцию репитеру. Это достаточно универстальный подход, применимый и в Яве и в PHP - но чуток с другими именами классов.
Делается это вот таким фрагментом кода:
1: Dim SortResult As String = XSLT(ResponseXML, System.IO.Path.Combine(Server.MapPath("~"), "SortResponse.xslt"))
2: Dim XML1 As New System.Xml.XmlDocument
3: XML1.LoadXml(SortResult)
4: Dim NodeList As System.Xml.XmlNodeList = XML1.SelectNodes("//item")
5:
6: ItemRepeater.DataSource = NodeList
7: ItemRepeater.DataBind()
Другой подход состоит в том, чтобы скормить репитеру классы XLINQ. Тогда внутри репитеров будет уже (столь любимое индусами) обьектное программирование, а не работа с XPATCH. И если удается XML привести внутри репитеров в такому виду, чтобы студия на этапе компиляции давала подсказку по структуре XML - то такой подход даже в принципе более удобный, чем более мощный и универсальный подход по XPATCH - XSLT.
Этот вариант решения делается вот таким фрагментом кода:
1: Dim SortResult As String = XSLT(ResponseXML, System.IO.Path.Combine(Server.MapPath("~"), "SortResponse.xslt"))
2: Dim Tst1 As XElement = XElement.Parse(SortResult)
3: '
4: ItemRepeater.DataSource = Tst1.<response>.<items>.Elements
5: ItemRepeater.DataBind()
Надеюсь этот топик был полезен начинающим программистам, чтобы помочь принять решение - стоит ли применять Linq-to-XML и Linq-to-SQL вместо SQL и XSLT в вашей задачке.
|