(MVC) MVC (2011 год)

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

linq Я уже писал об этой странной микрософтовской технологии (в 2010-м году) - Извлекаем пользу из LINQ - но меня по прежнему не покидает ощущение странности этой уникальной (не повторяющейся нигде больше - ни на одной платформе) технологии - поэтому я решил сделать об этом чуде еще один топик на своем сайте.

Странность микрософтовских технологий в том, что для их использования необходимо знать нечто совершенно противоположное, чем знает любой программист на этой планете (не знающий о существовании микрософта).


Взять например 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 в вашей задачке.



Comments ( )
Link to this page: //www.vb-net.com/XLINQ-and-LINQ-to-SQL/index.htm
< THANKS ME>