T-SQL过程 - 过滤参数为Object / CLR / Xml / UDT

时间:2012-05-09 20:51:15

标签: sql sql-server-2008 tsql parameters filtering

Cliffs:是否存在将标准“过滤器”类型传递给存储过程以封装stardate / enddate / pagesize / pagenum参数的已知模式?

不确定此问题的正确位置。我正在探索将过滤对象参数传递给存储过程的想法,该存储过程封装了我们常用的过滤参数(startdate,enddate,pagenumber,pagesize,int列表等)。这样做的原因是为了减少类似参数的数量以及围绕我们程序传播的样板SQL。这将从一开始就为每个程序提供更标准的界面和起点。我无法找到有关该主题的更多信息。

模式我注意到 - 当第一次构建大多数SP时,它们以where子句中使用的单个id参数开始。稍后,您可能需要为日期范围参数添加参数(startdate,enddate或dynamic range“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分钟增加了一笔赏金,我会让它再跑几天,试图促进更多的讨论。如果我的问题不够明确,请发表评论..

5 个答案:

答案 0 :(得分:5)

我个人认为你是在过度思考或试图减少不需要减少的东西。您可能最好单独留下存储过程参数,或者尝试创建一些可以将一组参数附加到命令对象的基类和辅助函数。

然而,话虽如此,我会在那里提出你的问题的解决方案,看看它是否符合你的需求:

我建议使用TSQL用户定义的类型。创建一个或多个类型。也许一个用于日期范围,一个用于分页和排序。我使用类似的过程将多行数据传递给存储过程。 (有些代码可能需要稍微调整一下,因为我只是修改了一些我已编写过的代码,并且在很长一段时间内我都没有使用过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)

答案 1 :(得分:3)

我们也遇到了这个问题。通过在数据库的“可编程性/类型”部分上创建用户定义的表类型来解决此问题。

user defined table types SQL Server 2008 R2

在调用不同的存储过程和函数时,此表用于所有appl。我们在appl客户端(vb.net 2010)上以编程方式填写此表,然后将其作为参数传递。在存储过程中,我们只是读取表格并执行我们需要做的事情,过滤,处理等。希望这会有所帮助。

答案 2 :(得分:1)

在我看来,这个问题没有真正美妙的解决方案。最大的问题是,大多数情况下,某些参数可能为null,但有些参数不是(不关心参数是否来自表值参数或XML参数)。然后它最终得到类似于SQL的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,Certificates)。

然后可以使用一个输入XML参数创建过程,您可以在其中传递所需的所有参数然后生成SQL ..但仍然 - 这不是很好的方法,因为当SQL变得更复杂时,有涉及大量编码。例如,如果您从多个表中选择数据,并且在多个表中存在相同的列..

总结一下,我认为没有针对这个问题的优雅解决方案。使用实体框架和传递参数的经典方式:)。

答案 3 :(得分:1)

我会使用XML作为参数并添加一些UDF来帮助解压缩您感兴趣的XML部分。标量值UDF用于单值参数,表值UDF用于列表。

在查询中嵌入XML有一种混淆查询优化器的倾向,如果它最终出现在where子句或连接中,那么使用UDF的可以是性能杀手,所以我不会使用XML或查询本身中的UDF。我首先从XML获取值到局部变量,表变量或临时表,然后在查询中使用它们。

答案 4 :(得分:1)

我遇到了类似的情况,我发现UDT的工作非常完美。我们从一个非常类似的问题开始:“获取此帐户的数据”然后它变为“获取这些帐户的数据”,然后“使用这些条件”等。我们使用UDT而不是传递XML字符串 - 一旦进入SP ,你可以直接加入UDT,ADO.NET支持UDT,所以它很简单。我们从UDT(大量upserts)向我们的SP传递了数十万行,并且性能没有成为一个例外,只有一个例外:当你发送那么多行时,不要试图追踪查询 - SQL Server中的线程调度程序将爆炸。

使用用户定义表类型时要注意一件事:由于某些原因,Microsoft认为阻止您更改它们是个好主意,您只能删除/添加它们。然后有人认为如果某些东西依赖于它们会阻止你放弃它们会更好,所以如果你手动修改它们,你最终会放弃/重新设置它们。

我们没有将所有参数封装到单个UDT中,因为我们的需求在程序之间更具体。因此,当我们有事物列表时,我们使用UDT作为该参数,但我可以很容易地看到一个UDT来规则它们都很有用,有一些便利函数来提取日期等知名值。我鄙视多次编写相同的代码,这肯定会以较小的代价缩小代码库,从而增加复杂性。另一个好处是迫使所有开发人员坚持采用标准的做事方式,这在关键时刻点击时并不总是强制执行。您还可以在数据层中为代码重用打开一些不错的机会。