XML-коллектор параметров ASP2-форм & XSLT-генератор динамических SQL.
На этой страничке я расскажу об одной из своих любимых технологий, которые не очень распространены, но которые я с успехом применяю на протяжении ряда лет. Фактически, я опишу здесь некий макропроцессор над SQL-запросами, встроенный в ASP2 и построенный на стандартных XSLT-шаблонах.
Когда-то я писал многокритериальные поисковые запросы ВОТ ТАК: многокритериальные сортировки - развешиваением огромных гирлянд в ORDER BY и с огромными гирляндами WHERE - пример на той же страничке пониже, строка 443. У меня на хомячке буквально горы моего кода, который я приводил для демонстрации разнообразных концепций.
В некоторых проектах у меня были такие огромные и длинные SQL-запросы - что ввести их вручную было просто невозможно и я писал некие препроцессоры (обработчки текста) - результатом работы которых был собственно итоговой оператор SQL немерянной длины. Например в самом конце этой странички я выкладывал фрагмент такого формирователя оператора SQL.
Этот подход хорош, конечно, полным использованием возможностей предкомпилированных планов запросов в SQL - но как быть, когда количество параметров в WHERE становится просто астрономическим и кроме того, заказчики меняют его без конца?
Тогда такой подход уже не годится - нужна более гибкая схема. Позволяющаяя менять эти параметры пачками. Кроме того, бывают ситуации, когда несколько подобных форм (обычно это поисковые формы) формируют весьма похожие запросы в SQL. Вот, например, пять таких похожих поисковых форм одного проекта (по анкетам , по объявлениям , по фотоальбомам , по рассказам , по видеороликам ). Причем по мере развития проекта, этих поисковых форм может стать и двадцать пять. Кроме того, параметры поисков (как впрочем и параметры регистрации) - заказчики тоже меняют чуть ли не каждый день. В такой ситуации полагаться на фиксированные предкомпилированные SQL-планы уже нереально, и составлять огромные SQL-процедуры тоже бессмысленно - придется надеяться только на SQL-кеширование.
Итак, на этой страничке я опишу одну свою технику, с помощью которой я справляюсь с подобными задачами.
Ясно, что требуемую гибкость не достичь с помощью каких-то реляционных моделей или типизированных коллекций. Хотя у меня на сайте лежит один пример такого менеджера классификаторов на базе реляционной модели, где поисковые параметры вообще формируются оператором (как собственно и сами параметры-классифкаторы товарных позиций). Но во втором подобном менеджере классифкаторов, котороый я делал позже - я уже отказался от реляционных моделей. Требуемую гибкость в сочетании с простотой дают только XML-модели. XML я с давних пор активно юзаю, и у меня на сайте есть и множество конкретных примеров использования XML и несколько описаний общих обзоров.
И эта задача для весьма гибкого формирования поисковых запросов со множество подобных и меняющихся форм - идеально подходит для применения XML. Однако в этом деле есть несколько ньюансов, на которых мы остановимся.
Итак, в первую очередь нам надо собрать все параметры с формы. С учетом конкретных выбранных юзером значений и умолчаний для каждой позиции выбора:
Как вы видите, этот сформированный XML даже несколько напоминает коллекцию Request.Forms, одако содержит и GET-параметры запроса странички и главное - параметры умолчания для каждого выбора пользователя, которые нам позволяют понять, что это выбор ПРОПУЩЕН и не участвует в формировании SQL-запроса. Это важный элемент гибкости - иначе нам бы пришлось формировать в WHERE десятки погашающих условия флагов - так как это было описано выше.
Мы могли бы даже проверить построенную нами XML на корректность данной схеме (как например я делал в этом проекте) - если бы не были уверены, что построили ее сами и корректно вот этим кодом:
00001: Imports Microsoft.VisualBasic 00002: 00003: Public Class ParseFindParm 00004: 00005: #Region "Анализ формы поиска и построение XML с параметрами поиска" 00006: 00007: Dim XMLNameSpace As String 00008: Dim FindParm As New System.Xml.XmlDocument 00009: ''' <summary> 00010: ''' При создании класса соберем с формы все значения текстбоксов, чекбоксов и комбобоксов в XML 00011: ''' и параметров запроса формы (для пейждера) 00012: ''' </summary> 00013: Public Sub New(ByVal FromForm As System.Web.UI.Page) 00014: XMLNameSpace = FromForm.GetType.FullName 00015: Dim Root As System.Xml.XmlElement = FindParm.CreateElement("FindParm", XMLNameSpace) 00016: For Each OneKey As String In FromForm.Request.QueryString.AllKeys 00017: Dim OneAttribute As System.Xml.XmlAttribute = FindParm.CreateAttribute(OneKey) 00018: OneAttribute.Value = FromForm.Request.QueryString(OneKey) 00019: Root.Attributes.Append(OneAttribute) 00020: Next 00021: FindParm.AppendChild(Root) 00022: For Each M1_Master As Control In FromForm.Controls 'Me.Controls=M1.Master - это корень, в котором Head и проч. 00023: For Each M1_Master_Conntrol As Control In M1_Master.Controls(3).Controls 'это внутренность PlaceHolder'а 00024: If M1_Master_Conntrol.GetType.FullName = "System.Web.UI.WebControls.ContentPlaceHolder" Then 00025: For Each X As Control In M1_Master_Conntrol.Controls 00026: If X.GetType.FullName = "System.Web.UI.WebControls.DropDownList" Then 00027: 'формируем <cbOrder SelectedValue="0" Text="0" ToolTip="Порядок" xmlns="System.Web.UI.WebControls.DropDownList" /> 00028: Dim Drop1 As System.Web.UI.WebControls.DropDownList = CType(X, System.Web.UI.WebControls.DropDownList) 00029: 'пространство имен xmlns="System.Web.UI.WebControls.DropDownList" все равно все равно всегде присутсвует в элементе - поэтому выносить его в атрибут нет смысла 00030: Dim OneDropNode As System.Xml.XmlNode = FindParm.CreateNode(System.Xml.XmlNodeType.Element, Drop1.ID, XMLNameSpace) 00031: Dim ValueAttribute As System.Xml.XmlAttribute = FindParm.CreateAttribute("Value") 00032: Dim ToolTipAttribute As System.Xml.XmlAttribute = FindParm.CreateAttribute("ToolTip") 00033: Dim DefaultValueAttribute As System.Xml.XmlAttribute = FindParm.CreateAttribute("DefaultValue") 00034: Dim WebControlAttribute As System.Xml.XmlAttribute = FindParm.CreateAttribute("WebControl") 00035: If Drop1.SelectedItem IsNot Nothing Then 00036: ValueAttribute.Value = Drop1.SelectedValue 00037: Else 00038: ValueAttribute.Value = Drop1.Text 00039: End If 00040: ToolTipAttribute.Value = Drop1.ToolTip 00041: DefaultValueAttribute.Value = Drop1.Attributes("DefaultValue") 00042: WebControlAttribute.Value = X.GetType.FullName 00043: OneDropNode.Attributes.Append(ValueAttribute) 00044: OneDropNode.Attributes.Append(ToolTipAttribute) 00045: OneDropNode.Attributes.Append(DefaultValueAttribute) 00046: OneDropNode.Attributes.Append(WebControlAttribute) 00047: Root.AppendChild(OneDropNode) 00048: End If 00049: If X.GetType.FullName = "System.Web.UI.WebControls.TextBox" Then 00050: 'формируем <TextBox3 Text="" ToolTip="Ник содержит" xmlns="System.Web.UI.WebControls.TextBox" /> 00051: Dim Text1 As System.Web.UI.WebControls.TextBox = CType(X, System.Web.UI.WebControls.TextBox) 00052: Dim OneTextNode As System.Xml.XmlNode = FindParm.CreateNode(System.Xml.XmlNodeType.Element, Text1.ID, XMLNameSpace) 00053: Dim TextAttribute As System.Xml.XmlAttribute = FindParm.CreateAttribute("Text") 00054: Dim ToolTipAttribute As System.Xml.XmlAttribute = FindParm.CreateAttribute("ToolTip") 00055: Dim DefaultValueAttribute As System.Xml.XmlAttribute = FindParm.CreateAttribute("DefaultValue") 00056: Dim WebControlAttribute As System.Xml.XmlAttribute = FindParm.CreateAttribute("WebControl") 00057: TextAttribute.Value = Text1.Text 00058: ToolTipAttribute.Value = Text1.ToolTip 00059: DefaultValueAttribute.Value = Text1.Attributes("DefaultValue") 00060: WebControlAttribute.Value = X.GetType.FullName 00061: OneTextNode.Attributes.Append(TextAttribute) 00062: OneTextNode.Attributes.Append(ToolTipAttribute) 00063: OneTextNode.Attributes.Append(DefaultValueAttribute) 00064: OneTextNode.Attributes.Append(WebControlAttribute) 00065: Root.AppendChild(OneTextNode) 00066: End If 00067: If X.GetType.FullName = "System.Web.UI.WebControls.CheckBox" Then 00068: 'формируем <OnlyFoto ControlType="" Checked="True" Text="Только с фото" ToolTip="" xmlns="System.Web.UI.WebControls.CheckBox" /> 00069: Dim Check1 As System.Web.UI.WebControls.CheckBox = CType(X, System.Web.UI.WebControls.CheckBox) 00070: Dim OneCheckNode As System.Xml.XmlNode = FindParm.CreateNode(System.Xml.XmlNodeType.Element, Check1.ID, XMLNameSpace) 00071: Dim CheckedAttribute As System.Xml.XmlAttribute = FindParm.CreateAttribute("Checked") 00072: Dim TextAttribute As System.Xml.XmlAttribute = FindParm.CreateAttribute("Text") 00073: Dim ToolTipAttribute As System.Xml.XmlAttribute = FindParm.CreateAttribute("ToolTip") 00074: Dim DefaultValueAttribute As System.Xml.XmlAttribute = FindParm.CreateAttribute("DefaultValue") 00075: Dim WebControlAttribute As System.Xml.XmlAttribute = FindParm.CreateAttribute("WebControl") 00076: CheckedAttribute.Value = Check1.Checked 00077: TextAttribute.Value = Check1.Text 00078: ToolTipAttribute.Value = Check1.ToolTip 00079: DefaultValueAttribute.Value = Check1.Attributes("DefaultValue") 00080: WebControlAttribute.Value = X.GetType.FullName 00081: OneCheckNode.Attributes.Append(CheckedAttribute) 00082: OneCheckNode.Attributes.Append(TextAttribute) 00083: OneCheckNode.Attributes.Append(ToolTipAttribute) 00084: OneCheckNode.Attributes.Append(DefaultValueAttribute) 00085: OneCheckNode.Attributes.Append(WebControlAttribute) 00086: Root.AppendChild(OneCheckNode) 00087: End If 00088: Next 00089: End If 00090: Next 00091: Next 00092: End Sub 00093: 00094: ''' <summary> 00095: ''' Сформированный XML будет виден тут 00096: ''' </summary> 00097: Public Overrides Function ToString() As String 00098: Return FindParm.OuterXml 00099: End Function 00100: #End Region
Итак, мы имеем корректный XML и хотим с его помощью построить SQL-запрос. Какую же технологию нам применить? Задумываться тут нечего - у меня на сайте десятки примеров XSLT-преобразований.
Но сначала нам надо выявить какие из параметров поисковой формы НЕ ЗАДЕЙСТВОВАНЫ юзером для запроса. Те сохранили свои изначальные умолчания. Для этого мы вводим на формах дополнительные атрибуты к стандартным атрибутам контролов. Собственно же семантику для построения запросов мы могли ы отгребать из имен контролов и из еще одного дополнительного атрибута, но я предпочитаю это делать по ToolTip'ам. В итоге входные данные для нашего парсера выглядят это в исходном тексте примерно так:
...... 00201: <asp:DropDownList ID="cbMonthTo" runat="server" Font-Size="Small" Width="80px" ToolTip="Месяц (до)" DefaultValue="12"> 00202: <asp:ListItem Value="1">январь</asp:ListItem> 00203: <asp:ListItem Value="2">февраль</asp:ListItem> 00204: <asp:ListItem Value="3">март</asp:ListItem> 00205: <asp:ListItem Value="4">апрель</asp:ListItem> 00206: <asp:ListItem Value="5">май</asp:ListItem> 00207: <asp:ListItem Value="6">июнь</asp:ListItem> 00208: <asp:ListItem Value="7">июль</asp:ListItem> 00209: <asp:ListItem Value="8">август</asp:ListItem> 00210: <asp:ListItem Value="9">сентябрь</asp:ListItem> 00211: <asp:ListItem Value="10">октябрь</asp:ListItem> 00212: <asp:ListItem Value="11">ноябрь</asp:ListItem> 00213: <asp:ListItem Value="12" Selected="True">декабрь</asp:ListItem> 00214: </asp:DropDownList> 00215: <asp:DropDownList ID="cbYearTo" runat="server" Width="60px" ToolTip="Год (до)" DefaultValue="2010"> 00216: <asp:ListItem>2008</asp:ListItem> 00217: <asp:ListItem>2009</asp:ListItem> 00218: <asp:ListItem Selected="True">2010</asp:ListItem> 00219: </asp:DropDownList> .....
Итого, давайте подведем промежуточные итоги - чего мы добились с помощью такой технологии. Мы вынесли всю гибкость и модифицируемость нескольких подобных форм из алгоритмов в данные - в единый XSLT-файлик с описанием процесса получения SQL-запроса. С другой стороны наш движок, производящий XML, работает тоже по исходным текстовым файлам страничек, куда нам тоже несложно просто редактированием текстов вносить любые изменения - при этом для любых модификациях поисковых параметров форм НЕ ИЗМЕНЯЕТСЯ НИ ЕДИНАЯ СТРОЧКА ПРОГРАММНОГО КОДА!
Собственно говоря - вызов нашего класса парсинга формы и построения SQL на форме выглядит вот так:
Ни этот код на форме, ни код класса НИКОГДА уже не будет меняться - ни при изменениях постановки задачи по полям отборов, ни при изменениях структуры базы. Вся изменчивость у нас вынесена фактически в несколько XSLT-файликов, которым скармливается на вход разметка формы - а на выходе мы получаем готовый SQL-запрос.
Фишка тут также в том, что поскольку поддерживается пейджинг, то запросов должно быть ДВА - первых просто дает количество записей в отборе по базе и его результат участвует просто в формировании пейджера, а второй запрос - это собственно пейждинговый запрос, который формирует рекордсет, выводимый на форму
Первый формирователь SQL (для запроса на количество) - выглядит примерно вот так:
Этот файл формирования SQL-запроса достаточно длинный (и на момент написания этой заметки - я даже не изготовил его полностью для данного проекта) - но смысл должен быть понятен:
- Каждая ветка этого XSLT-шаблона работает для одного файла странички - NameSpace которой совпадает с именем класса ASPX-хандлера, обрабатывающего реквест юзера - проще говоря - с полным именем странички. Те в данном шаблоне у меня будет пять таких веток - в текущей постановки задачи. В дальнейшем, при добавлении новых форм - я просто буду добавлять новые ветки в этом шаблоне.anketa
- каждый контрол разметки странички, будучи встречен в XML (и если он находится не в дефолтном состоянии и непустой) - вносит свой вклад в формирование условия WHERE для SQL-отбора. Для данной странички у меня в текущей постановке участвует 13 контролов в формировании условий WHERE - при расширении этого количества или измененнии типов я просто буду менять или дополнять эти шаблоны.
- Вот у меня сейчас пять таких форм, на некоторых из них уже по 30 параметров отборов, но я просто сейчас проскочил в проекте момент отладки около семидесяти условий WHERE - оставив их или админу, который сможет в ходе эксплуатации гибко менять эти запросы или просто до того момента, когда в голове у заказчкиков окончательно сформируются условия этих отборов. Но формы-то на данный момент УЖЕ отлично работают - и без тщательной и трудоемкой отладки множества SQL-процедур! Вот какой гибкости мы добились - вынося формирование SQL в XSLT-шаблон!
Ну а собственно пейжинговый запрос за данными выглядит вот так: (начало)
00001: <?xml version="1.0" encoding="utf-8"?> 00002: 00003: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 00004: xmlns:anketa="ASP.find_anketa_aspx" 00005: xmlns:foto="ASP.find_foto_aspx" 00006: xmlns:video="ASP.find_video_aspx" 00007: xmlns:story="ASP.find_story_aspx" 00008: xmlns:poputi="ASP.find_poputi_aspx"> 00009: <xsl:output method="text"/> 00010: 00011: <xsl:template match="/poputi:*" > 00012: 00013: <xsl:param name="PageSize"> 00014: <xsl:value-of select="10"/> 00015: </xsl:param> 00016: <xsl:param name="PageNum"> 00017: <xsl:value-of select="0"/> 00018: </xsl:param> 00019: <!-- это для автономной отладки запроса минуя exec sp_executesql --> 00020: <!--declare @PageSize int 00021: declare @PageNum int 00022: select @PageSize=<xsl:value-of select="$PageSize"/> 00023: select @PageNum=<xsl:value-of select="$PageNum"/> 00024: ;--> 00025: WITH All_Users as 00026: ( 00027: SELECT ROW_NUMBER() OVER (order by UserName) as [ROW_NUMBER], Poputi.* from Poputi 00028: WHERE (1=1) 00029: <xsl:apply-templates /> <!-- так отгребаются все узлы --> 00030: <!--<xsl:apply-templates select="//*[@DefaultValue!=@Value]" />--> <!-- а так только те, где юзер установил параметры на форме --> 00031: ) 00032: SELECT * from All_Users 00033: WHERE [ROW_NUMBER]>ISNULL(@PageSize,1)*ISNULL(@PageNum,0) and [ROW_NUMBER]<=ISNULL(@PageSize,1)*ISNULL(@PageNum+1,1000) 00034: ; 00035: </xsl:template> 00036: <xsl:template match="/poputi:FindParm/poputi:cbCity" > 00037: <xsl:if test="@Value!=''" > 00038: <xsl:if test="@DefaultValue!=@Value" > 00039: and City_Name='<xsl:value-of select="@Value" />' -- <xsl:value-of select="@ToolTip" /> 00040: </xsl:if> 00041: </xsl:if> 00042: </xsl:template> 00043: <xsl:template match="/poputi:FindParm/poputi:cbFrom" > 00044: <xsl:if test="@Value!=''" > 00045: <xsl:if test="@DefaultValue!=@Value" > 00046: and Country_Name='<xsl:value-of select="@Value" />' -- <xsl:value-of select="@ToolTip" /> 00047: </xsl:if> 00048: </xsl:if> 00049: </xsl:template>
Ну и теперь остаток моего класса-формирователя SQL (который мы вызывали с формы выше):
00102: #Region "Построение SQL-запросов на количество записей и собственно на отбор юзеров по заданному XML с параметрами запроса" 00103: 00104: ''' <summary> 00105: ''' XSLT-преобразованием сформируем SQL-запрос 00106: ''' На всякий случай XSLT-преобразование догружает для формирования SQL-запроса текущие значения параметров PageSize и PageNum 00107: ''' </summary> 00108: Private Sub BuildSelect(ByVal XSLT_FileName As String) 00109: Dim XsltFile As String = HttpContext.Current.Server.MapPath(XSLT_FileName) 00110: Dim XSLT As New System.Xml.Xsl.XslCompiledTransform 00111: Try 00112: XSLT.Load(XsltFile) 00113: Catch ex As System.IO.FileNotFoundException 00114: Throw New Exception("Нету файла: " & XsltFile) 00115: Catch ex As System.Xml.Xsl.XsltCompileException 00116: Throw New Exception("Ошибка XSLT-компиляции в: " & XsltFile) 00117: Catch ex As System.Xml.Xsl.XsltException 00118: Throw New Exception("Ошибка XSLT-преобразования в: " & XsltFile) 00119: End Try 00120: XSLT_Result = New System.Text.StringBuilder 00121: Dim Writer As New System.IO.StringWriter(XSLT_Result) 00122: Dim XSLT_Parm As New System.Xml.Xsl.XsltArgumentList 00123: XSLT_Parm.AddParam("PageSize", XMLNameSpace, _PageSize) 00124: XSLT_Parm.AddParam("PageNum", XMLNameSpace, _PageNum) 00125: XSLT.Transform(FindParm, XSLT_Parm, Writer) 00126: End Sub 00127: 00128: Dim XSLT_Result As System.Text.StringBuilder 00129: ''' <summary> 00130: ''' Это свойство вернет сформированный XSLT-преобразованием оператор SQL 00131: ''' </summary> 00132: Public ReadOnly Property XSLT_Generated_SQL_Statement() 00133: Get 00134: Return XSLT_Result.ToString 00135: End Get 00136: End Property 00137: 00138: #End Region 00139: 00140: #Region "Предварительный подсчет количества записей" 00141: 00142: Dim _Total_Rows As Integer 00143: ''' <summary> 00144: ''' Суммарное количество записей в отборе для пейджера, которые вернул предварительный запрос 00145: ''' </summary> 00146: Public ReadOnly Property Total_rows() As Integer 00147: Get 00148: Return _Total_Rows 00149: End Get 00150: End Property 00151: 00152: ''' <summary> 00153: ''' Сформированная команда подсчета количества записей 00154: ''' </summary> 00155: Public ReadOnly Property CountCMD() As String 00156: Get 00157: Return _CountCMD.SqlCMD.CommandText 00158: End Get 00159: End Property 00160: 00161: Dim _CountCMD As ExecSQL_RDR 00162: ''' <summary> 00163: ''' Получить счетчик отобранных запросом записей для пейджера 00164: ''' Эта функция выполнит установленный заданный в XSLT-файле оператор SQL 00165: ''' И вычитает из ного ОДНО значение из нулевой строки и поля ROW_COUNT, которое будет считатся общем количеством записей 00166: ''' </summary> 00167: Public Function GetCount(ByVal XSLT_FileName As String) As Integer 00168: BuildSelect(XSLT_FileName) 00169: _CountCMD = New ExecSQL_RDR() 00170: Dim RDR As Data.SqlClient.SqlDataReader = _CountCMD.ExecSQL(XSLT_Result.ToString, Data.CommandType.Text, Data.CommandBehavior.SingleRow) 00171: If RDR.Read Then 00172: _Total_Rows = RDR("ROW_COUNT") 00173: End If 00174: _CountCMD.Close() 00175: Return _Total_Rows 00176: End Function 00177: 00178: #End Region 00179: 00180: #Region "Отбор заказанных юзеров в DataTable c указанием конкретной страницы отбора" 00181: 00182: Dim _PageSize As Integer = 10 00183: ''' <summary> 00184: ''' Размер страницы пейджинга 00185: ''' </summary> 00186: Public Property PageSize() As Integer 00187: Get 00188: Return _PageSize 00189: End Get 00190: Set(ByVal value As Integer) 00191: _PageSize = value 00192: End Set 00193: End Property 00194: 00195: Dim _PageNum As Integer = 0 00196: ''' <summary> 00197: ''' Текущая запрашиваемая страница 00198: ''' </summary> 00199: Public Property PageNum() As Integer 00200: Get 00201: Return _PageNum 00202: End Get 00203: Set(ByVal value As Integer) 00204: _PageNum = value 00205: End Set 00206: End Property 00207: 00208: Dim CMD As ExecSQL_RDR 00209: ''' <summary> 00210: ''' Итоговая команда, которую скармливаем SQL-серверу после всего 00211: ''' </summary> 00212: Public ReadOnly Property SelectCMD() As String 00213: Get 00214: Return CMD.SqlCMD.CommandText 00215: End Get 00216: End Property 00217: 00218: 00219: ''' <summary> 00220: ''' Итог: получить заданную страницу отбора юзеров 00221: ''' Эта функция выполнит указанный в XSLT-файле SQL-запрос, 00222: ''' Передав ему параметрами текущие значения свойств класса PageSize и PageNum 00223: ''' И вернет DataTable с именем OnePageOfSelectedUsers с отбором юзеров из базы 00224: ''' </summary> 00225: Public Function GetUsers(ByVal XSLT_FileName As String) As Data.DataTable 00226: BuildSelect(XSLT_FileName) 00227: Dim DT As New Data.DataTable("OnePageOfSelectedUsers") 00228: CMD = New ExecSQL_RDR("@PageSize", _PageSize, "@PageNum", _PageNum) 00229: Dim RDR As Data.SqlClient.SqlDataReader = CMD.ExecSQL(XSLT_Result.ToString, Data.CommandType.Text, Data.CommandBehavior.CloseConnection) 00230: DT.Load(RDR) 00231: CMD.Close() 00232: Return DT 00233: End Function 00234: #End Region 00235: 00236: End Class
Тут используется пара моих собственных библиотечных функций для обращения к ADO.NET - хотя по большому счету он не нужет. Фактически эта описанная мною шаблонная надстройка над SQL является альтернативой ADO.NET-параметризации и параметризации запросов хранимыми процедурами SQL и уж тем более LINQ-расширениям NET-языков.. Мы просто опираемся на внутреннее кеширование SQL и строим SQL-запросы динамически, сами - причем cуществующими стандартными технологиями - XSLT-шаблонами. И вы видите, насколько это более легкое и гибкое решение, чем LINQ-идиотизм!
В шаблоне вы там видите закоментированную заглушку, которая позволяет обходится без параметров в sp_executeSQL, а уложить параметры непосредственно в SQL-запрос. Собственно XSLT-шаблон для этого и принимает параметры. Просто я не воспользовался ими в шаблоне - в данном проекте запросы просты и примитивны - а взял параметры SQL-запроса уже в sp_executeSQL.
Но такая ситуация бывает редко - например нам нужно при различных выборах на форме джойнить или две таблицы или десять - и описанная мною технология с успехом справляется с этой задачей, даже когда заказчики на ходу меняют структуру базы, контролы на форме для запросов. И админ сайта тоже может все менять на ходу - лишь меняя разметку страницы и XSLT-шаблон.
При желании, можете считать это очередным моим ответом тупоголовости MS, ее идиотизму как со строгой типизацией, так и с LINQ, а также технологиям классических BLL. Впрочем, MS так швыряет из стороны сторону, что даже КПСС отдыхает. То у них была суперфинишной идеология с переработками ЧИСТО исходных текстов - простая ASP. Потом вдруг, текстовые технологии, куда нас заводили много лет, вдруг у MS оказали ацтойными, и оказалось вдруг, что хорошо, наоборот, когда HTML-разметка откомпилирована в DLL, а не лежит в исходниках. И все аргументы насчет удобства XHTML, XML и прочих чисто текстовых технологий оказались вдруг забытыми. И даже то, что каждая девочка в простом ASP безо всякой CMS могла что угодно подкорректировать. Это вдруг тоже оказалось забытым. Вдруг стали актуальными не только html, упакованные в DLL, но и сверх-строгие типизации и дженерики, которыми нам парили мозги несколько лет. Умалчивая, правда, что самый распространенный на планете язык - JavaScript, который работает в каждом браузере - никаких типизаций не поддерживает. Не говоря уж про LISP и множество прочих языков. Но вот очередной пьяный качок MS уже в противоположную сторону - теперь строжайшая типизация опять ацтой - да здравствуют переменные VAR из LINQ-расширений! Круг MS-идиотизма обязан замкнутся - в следующем релизе ASP.NET html-разметка опять по идее должна быть строго исходными текстиками. Так и будет крутить нас MS из одной стороны в другую - набивая нам головы дерьмом, а себе карманы - деньгами!
PS.
В ходе обсуждения этой моей концепции на различных форумах - многие товариши напирали на отсутствие проверки корректности ТЕЛА формируемых XSLT-выражений на этапе компиляции. Господа Креш, Нахлобуч, ЗЫ и другие - я ведь предупреждал вас, что чтение на ночь микрософтовских учебников (и особенно Фаулера) - приводит к обширным поражениям мозга. Попробуйте асилить такую мысль - СТРАНИЧКА САЙТА ВЫСТУПАЕТ КОМПЛЯТОРОМ этих выражений. Те админ сайта (или же девелопер на завершающих этапах девелопмента) вводит SQL-выражение, ИСПОЛНЯЕТ ЕГО САЙТОМ - если плохо - корректирует это выражение, нажимает назад в браузере - и новое откорректированное XSLT-выражение исполняется ЕЩЕ РАЗ. Вот я намеренно упустил в выражении закрывающую кавычку - понимаете, в чем отличия этих технологий?
|