2012-05-09 12 views
9

Cliffs:是否存在將標準「過濾器」類型傳遞給存儲過程以封裝stardate/enddate/pagesize/pagenum參數的已知模式?T-SQL過程 - 將過濾器參數設置爲Object/CLR/Xml/UDT

不確定此問題的正確位置。我正在探索將篩選對象參數傳遞給存儲過程的想法,該存儲過程封裝了我們常用的過濾參數(startdate,enddate,pagenumber,pagesize,int的列表等)。這樣做的原因是爲了減少類似參數和模板SQL在我們的過程中的傳播量。這將使我們從一開始就爲每個程序提供更加標準的界面和起點。我一直無法找到有關該主題的更多信息。

我注意到的模式 - 當第一次構建大多數SP時,它們以where子句中使用的單個id參數開始。稍後,您可能需要爲日期範圍參數(startdate,enddate或動態範圍「ytd,mtd,dtd」)添加參數。如果數據集足夠大,您可能還需要爲服務器端分頁引入pagesize/pagenum。過了一段時間,您可能會意識到需要使用id列表而不是單個id的結果,因此您需要添加一個CSV或XML參數來包含這些ID。

最終,許多存儲過程最終會有許多類似的樣板和(希望)相同的參數來處理這些標準過濾參數。我試圖研究已知的模式,將封裝的過濾器對象參數傳遞給我的過程,理想情況下,它將在C#端強制輸入。當管理一組功能強大的報告時,這些報告都需要相同的過濾選項(除了特定於報告的查詢參數),這將特別有用。

我的目標是減少WHERE子句所需的最小參數所需的參數數量,並創建一個將通用過濾選項傳遞給過程並在過程中使用這些值的標準機制。這怎麼能通過XML或CLR或UDT參數來實現?

對於這個問題的上下文,我通過C#2.0中的ADO.Net使用SQL Server 2008。不幸的是,LINQ/EF目前不是這個項目的選擇,我們必須堅持我們現有的RDBMS。如果有一種已知的模式需要改變技術,我會有興趣聽到它。

編輯:欣賞到目前爲止的回覆。我爲50分鐘添加了一個獎金,我會讓它再跑幾天來嘗試推廣更多討論。如果我的問題還不夠清楚,請發表評論..

+0

我考慮過的一個潛在解決方案是一個xml序列化的C#對象,它用UDF反序列化爲一個數據表。在每個程序中通過關鍵名稱捕捉適當的值仍然很麻煩。最後,我只是想對這個概念做更多的研究 - 是否可以在其他平臺上使用?有人開發了一個過濾層/ api來使SQL Server更容易嗎?提前致謝。 – mellodev

+0

我使用了很多動態SQL。在這種情況下,通過傳遞WHERE子句來解決問題。 –

+0

感謝您的輸入。我們已經做了大量的動態sql,實際上是存儲在應用程序中的這個項目的所有SQL,我們在其中附加了SqlDataParameters並根據需要執行我們自己專有的WHERE子句注入。然而,每個SQL塊都會有很多重複的參數和邏輯以滿足通常的過濾需求,所以我試圖找到一個將這些封裝成單個參數的模式。 – mellodev

回答

5

我個人認爲你還是得太多試圖減少一些不需要減少。你可能便獨自離開存儲過程的參數,或者試圖創造一些基類和輔助功能,可以附加組參數的命令對象更好。

不過,他這樣說,我會拋出一個解決你的問題在那裏,看看它是否適合您的需要:

我建議使用T-SQL用戶定義類型。創建一個或多個類型。也許一個用於日期範圍,另一個用於分頁和排序。我使用類似的過程將多行數據傳遞給存儲過程。 (這些代碼中的一些可能需要稍微調整,因爲我只是修改了一些我已經編寫的代碼,並且在一段時間內我還沒有與DataTable字段一起工作。)

最終,所有這一切縮短應用程序方法和匹配存儲過程中的參數列表。存儲過程將負責提取或連接表變量中的信息。下面列出的類提供了在.NET應用程序端強制輸入這些參數的功能。

if not exists (select * from INFORMATION_SCHEMA.DOMAINS where DOMAIN_SCHEMA = 'dbo' and DOMAIN_NAME = 'DateRange' and DATA_TYPE = 'table type') 
begin 

    create type dbo.DateRange as table 
    (
     StartDate datetime2 null 
     ,EndDate datetime2 null 
    ) 

end 
go 


if not exists (select * from INFORMATION_SCHEMA.DOMAINS where DOMAIN_SCHEMA = 'dbo' and DOMAIN_NAME = 'Paging' and DATA_TYPE = 'table type') 
begin 

    create type dbo.Paging as table 
    (
     PageNumber int null 
     ,PageSize int null 
     ,SortField sysname null 
     ,SortDirection varchar(4) null 
    ) 

end 
go 

的SQL用戶定義的類型可被表示在.NET應用程序作爲強類型對象。首先得有個基本類:

Imports System 
    Imports System.Data 
    Imports System.Data.SqlClient 
    Imports System.Runtime.Serialization 


    Namespace SqlTypes 

     <Serializable()> _ 
     <System.ComponentModel.DesignerCategory("Code")> _ 
     Public MustInherit Class SqlTableTypeBase 
      Inherits DataTable 

      Public Sub New() 

       MyBase.New() 
       Initialize() 

      End Sub 


      Public Sub New(ByVal tableName As String) 

       MyBase.New(tableName) 
       Initialize() 

      End Sub 


      Public Sub New(ByVal tableName As String, ByVal tableNamespace As String) 

       MyBase.New(tableName, tableNamespace) 
       Initialize() 

      End Sub 


      Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext) 

       MyBase.New(info, context) 

      End Sub 


      ''' <summary> 
      ''' Implement this method to create the columns in the data table to match the SQL server user defined table type 
      ''' </summary> 
      ''' <remarks></remarks> 
      Protected MustOverride Sub Initialize() 


      Public Function CreateParameter(parameterName As String) As SqlParameter 

       Dim p As New SqlParameter(parameterName, SqlDbType.Structured) 
       p.Value = Me 

       Return p 

      End Function 

     End Class 

    End Namespace 

創建的SQL類型的實現:

Imports System 
Imports System.Data 
Imports System.Runtime.Serialization 


Namespace SqlTypes 

    <Serializable()> _ 
    <System.ComponentModel.DesignerCategory("Code")> _ 
    Public Class DateRange 
     Inherits SqlTableTypeBase 

     Public Sub New() 

      MyBase.New() 

     End Sub 


     Public Sub New(ByVal tableName As String) 

      MyBase.New(tableName) 

     End Sub 


     Public Sub New(ByVal tableName As String, ByVal tableNamespace As String) 

      MyBase.New(tableName, tableNamespace) 

     End Sub 


     Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext) 

      MyBase.New(info, context) 

     End Sub 


     'TODO: throw some more overloaded constructors in here... 

     Public Sub New(startDate As DateTime?, endDate As DateTime?) 

      MyBase.New() 

      Me.StartDate = startDate 
      Me.EndDate = endDate 

     End Sub 


     Public Property StartDate As DateTime? 
      Get 
       Return CType(Me.Rows(0)(0), DateTime?) 
      End Get 
      Set(value As DateTime?) 
       Me.Rows(0)(0) = value 
      End Set 
     End Property 


     Public Property EndDate As DateTime? 
      Get 
       Return CType(Me.Rows(0)(1), DateTime?) 
      End Get 
      Set(value As DateTime?) 
       Me.Rows(0)(1) = value 
      End Set 
     End Property 


     Protected Overrides Sub Initialize() 

      Me.Columns.Add(New DataColumn("StartDate", GetType(DateTime?))) 
      Me.Columns.Add(New DataColumn("EndDate", GetType(DateTime?))) 

      Me.Rows.Add({Nothing, Nothing}) 

     End Sub 

    End Class 

End Namespace 

和:

Imports System 
Imports System.Data 
Imports System.Runtime.Serialization 


Namespace SqlTypes 

    <Serializable()> _ 
    <System.ComponentModel.DesignerCategory("Code")> _ 
    Public Class Paging 
     Inherits SqlTableTypeBase 

     Public Sub New() 

      MyBase.New() 

     End Sub 


     Public Sub New(ByVal tableName As String) 

      MyBase.New(tableName) 

     End Sub 


     Public Sub New(ByVal tableName As String, ByVal tableNamespace As String) 

      MyBase.New(tableName, tableNamespace) 

     End Sub 


     Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext) 

      MyBase.New(info, context) 

     End Sub 


     'TODO: throw some more overloaded constructors in here... 


     Public Sub New(pageNumber As Integer?, pageSize As Integer?) 

      MyBase.New() 

      Me.PageNumber = pageNumber 
      Me.PageSize = pageSize 

     End Sub 


     Public Sub New(sortField As String, sortDirection As String) 

      MyBase.New() 

      Me.SortField = sortField 
      Me.SortDirection = sortDirection 

     End Sub 


     Public Sub New(pageNumber As Integer?, pageSize As Integer?, sortField As String, sortDirection As String) 

      Me.New(pageNumber, pageSize) 

      Me.SortField = sortField 
      Me.SortDirection = sortDirection 

     End Sub 


     Public Property PageNumber As Integer? 
      Get 
       Return CType(Me.Rows(0)(0), Integer?) 
      End Get 
      Set(value As Integer?) 
       Me.Rows(0)(0) = value 
      End Set 
     End Property 


     Public Property PageSize As Integer? 
      Get 
       Return CType(Me.Rows(0)(1), Integer?) 
      End Get 
      Set(value As Integer?) 
       Me.Rows(0)(1) = value 
      End Set 
     End Property 


     Public Property SortField As String 
      Get 
       Return CType(Me.Rows(0)(2), String) 
      End Get 
      Set(value As String) 
       Me.Rows(0)(2) = value 
      End Set 
     End Property 


     Public Property SortDirection As String 
      Get 
       Return CType(Me.Rows(0)(3), String) 
      End Get 
      Set(value As String) 
       Me.Rows(0)(3) = value 
      End Set 
     End Property 


     Protected Overrides Sub Initialize() 

      Me.Columns.Add(New DataColumn("PageNumber", GetType(Integer?))) 
      Me.Columns.Add(New DataColumn("PageSize", GetType(Integer?))) 
      Me.Columns.Add(New DataColumn("SortField", GetType(String))) 
      Me.Columns.Add(New DataColumn("SortDirection", GetType(String))) 

      Me.Rows.Add({Nothing, Nothing, Nothing, Nothing}) 

     End Sub 

    End Class 

End Namespace 

實例化對象,並在構造函數中設置的值,然後簡單地從對象獲取參數,並將其附加到存儲過程命令對象的參數集合中。

cmd.Parameters.Add(New DateRange(startDate, endDate).CreateParameter("DateRangeParams")) 
cmd.Parameters.Add(New Paging(pageNumber, pageSize).CreateParameter("PagingParams")) 

編輯 因爲這個答案是圍繞強類型,我想我應該在方法簽名中添加強類型的例子:

'method signature with UDTs 
Public Function GetMyReport(customParam1 as Integer, timeFrame as DateRange, pages as Paging) as IDataReader 

'method signature without UDTs 
Public Function GetMyReport(customParam1 as Integer, startDate as DateTime, endDate as DateTime, pageNumber as Integer, pageSize as Integer) 
+0

謝謝花時間評論並給出代碼示例。雖然很多答案圍繞UDT進行,但由於SQL和C#示例,您的答案很突出。 – mellodev

+0

我同意,如果我們確實需要這些參數,那麼接口應該是什麼。 – zinking

3

我們也面臨這個問題。通過在數據庫的可編程性/類型部分創建用戶定義的表類型來解決。調用不同的存儲過程和函數時

user defined table types SQL Server 2008 R2

此表在所有申請使用。我們在appl客戶端(vb.net 2010)上以編程方式填寫此表,然後將其作爲參數傳遞。在存儲過程中,我們只是讀取表格並做我們需要做的事情,過濾,處理等。希望這有助於。

+0

感謝您的回答。我曾考慮過類似的東西,創建一個作爲參數傳遞的udt。你是如何在每個SP內實現「只讀表格並做我們需要的」模式的?我假設udt本質上將成爲一個關鍵/價值商店,而且我還是會在每個SP內部留下很多樣板文件 – mellodev

+0

對不起,在我完成我的想法之前提交的評論。您的UDT表格結構是什麼樣的?您是否構建了任何UDF來提取值並減少樣板?事後和你的經驗,你會在未來使用這種模式? – mellodev

+0

@ mello702不客氣。首先,我儘量避免在TSQL內部使用任何額外的進程,我的意思是,沒有UDF,沒有動態參數等。有些情況是唯一的解決方案,但我相信不是這個。當我說剛剛閱讀的表格只不過是這樣,做一個普通的SELECT作爲參數傳遞的表。不用說,這個SELECT可以像我想要的那樣複雜,甚至可以使用CTE,合併操作,調用另一個SP或函數或任何其他可能需要的來實現我的目標。 – Yaroslav

1

在我看來,這個問題並沒有真正美麗的解決方案。最大的問題是大多數情況下,一些參數可能爲空,但有些不是(不關心參數是來自表值參數還是XML參數)。然後,它結束了與SQL與此類似:

Declare @Col1Value int = null 
Declare @Col2Value int = null 
Select * 
From dbo.MyTable 
where (@Col1Value is Null Or Col1 = @Col1Value) 
    And (@Col2Value is Null Or Col2 = @Col2Value) 

當然,它的效率不高+查詢計劃是迄今爲止不是最好的..

爲了解決這個問題的動態SQL可能有很大的幫助。在這種情況下,雖然應該非常仔細地考慮用戶權限(可使用EXECUTE AS someProxyUser,證書)。

然後可以用一個輸入XML參數來創建過程,在那裏你傳遞你需要的所有參數,然後生成SQL ..但仍然不是很好的方法來做事情,因爲當SQL變得更復雜時是大量的編碼參與。例如,如果您選擇從多個表中的數據並沒有在他們不止一個相同的列..

總之我不認爲有這個漂亮和優雅的解決方案問題..使用實體框架和經典的傳遞參數的方式:)。

+0

感謝您花時間回覆。要明確我不希望將一個參數傳遞給代表整個Where子句的所有程序。作爲一個簡單的例子,我只想封裝所需的常見分頁和日期過濾值(PageSize,PageNum,startdate,enddate)。幾乎我們用於報告的所有查詢都需要這些相同的參數,並且都包含您提到的樣板類型。沿着declare @pagenum int; set @ pagenum = isnull(@ pagenumparam,1)的行; ...其中1 = 1和cte.PageNum = @pagenum ;.我同意沒有一個很好的解決方案:) – mellodev

1

我將使用XML作爲參數並添加一些UDF來幫助解壓縮您感興趣的XML部分。對於單個值參數的標量值UDF和對列表值的UDF值。

在查詢中嵌入XML有一種混淆查詢優化器和使用UDF的傾向可以如果性能殺手結束於where子句或連接,所以我不會使用XML或UDF中的查詢自己。我首先從XML局部變量,表變量或臨時表中的值,然後使用這些查詢。

+0

謝謝 - 你在使用UDF或者子句時對UDF的關注是正確的。對於我的實現,我將在使用它們之前將XML /過濾器參數值解壓縮到單獨的作用域變量中。我擔心的一個問題是查詢計劃緩存或優化器因此失敗,但我相信這可以通過在使用where子句之前解壓縮作用域變量來避免(類似於使用DateTime參數嗅探的問題/解決方案) – mellodev

1

我遇到了類似的情況,我發現UDT的工作非常完美。我們從一個非常類似的問題開始:「爲這個賬戶獲取數據」,然後它變成「爲這些賬戶獲取數據」,然後「按照這些標準」等。我們使用UDT而不是傳遞XML字符串 - 一旦你進入SP ,您可以直接加入UDT,而UDT則可以通過ADO.NET支持,因此它非常簡單。我們通過UDT(大量的upserts)將成千上萬行傳入我們的SP中,並且性能不會成爲一個例外:當您發送多行時,不要嘗試跟蹤查詢 - SQL服務器內的線程調度器將爆炸。

使用用戶定義表類型時需謹防一件事:由於某些原因,Microsoft認爲防止您更改它們是個好主意,您只能刪除/添加它們。然後有人認爲,如果某些事情取決於他們,防止他們放棄他們會更好,所以如果您通過手動更改他們,則會以非常痛苦的過程結束/重新調整他們。

我們沒有將所有參數封裝到單個UDT中,只是因爲我們的需求在程序之間更具體。因此,當我們有事物清單時,我們使用了UDT作爲該參數,但是我可以輕鬆看到一個UDT對它們進行規則化,所有這些都很實用,並且有一些便利功能可以提取日期等衆所周知的值。我鄙視多次編寫相同的代碼,這肯定會以較小的代價縮小代碼庫,從而增加複雜性。一個好處是迫使所有的開發人員堅持一種標準的做事方式,這種方式在關鍵時刻命中時並不總是強制執行。您還可以在數據層中打開一些好的機會來重用代碼。

+0

我喜歡你的答案 - 你確定了相同的模式,並提出了類似的解決方案。你是如何解決更新你的UDT的?如果我正確理解你,他們不能被更新,而必須被刪除/重新創建,這通過參考檢查而變得複雜。當你說「手工」時,你的意思是通過腳本還是使用UI? – mellodev

+0

由於只能刪除/創建UDT,而不能更改所有相關對象,例如使用UDT的存儲過程/函數也必須在UDT刪除之前被刪除。沒有簡單的方法可以刪除使用特定UDT的所有對象。他們需要手動識別。您可以查詢信息模式以查找特定類型的參數,然後編寫一個循環來刪除那些包含這些參數的例程,但我不會將其稱爲「簡單」解決方案。 – ulty4life

+0

這正是我們所做的 - 花了幾個小時來設置它,不是太痛苦,但不是微不足道的。您可以在sys模式中查找依賴關係,然後編寫一個循環,在sys.all_sql_modules中使用依賴對象定義,並將這些定義寫入表中。然後循環,刪除依賴關係,爲UDT放置/添加,然後爲依賴對象重新構建定義。我沒有這樣做,我也沒有斷言這是真的,但我被告知像dbProj這樣的工具會自動執行。 –