9

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

不确定这个问题的正确位置。我正在探索将过滤对象参数传递给存储过程的想法,该存储过程封装了我们常见的过滤参数(开始日期、结束日期、页码、页面大小、整数列表等)。这样做的原因是为了减少在我们的过程中散布的类似参数和样板 SQL 的数量。这将为我们从一开始就为每个过程提供一个更标准的界面和起点。我无法找到有关该主题的太多信息。

我注意到的模式 - 在第一次构建大多数 SP 时,它们从 where 子句中使用的单个 id 参数开始。稍后,您可能需要为日期范围参数添加参数(开始日期、结束日期或动态范围“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 分的赏金,我将再运行几天以尝试促进更多讨论。如果我的问题不够清楚,请发表评论..

4

5 回答 5

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)
于 2012-05-19T02:20:44.813 回答
3

我们也面临这个问题。通过在数据库的可编程性/类型部分创建用户定义的表类型来解决。

用户定义的表类型 SQL Server 2008 R2

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

于 2012-05-17T07:24:06.400 回答
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、Certificates)。

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

总而言之,我认为这个问题没有很好的解决方案。使用实体框架和传递参数的经典方式:)。

于 2012-05-18T11:57:32.717 回答
1

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

在查询中嵌入 XML 往往会使查询优化器感到困惑,如果它最终出现在 where 子句或连接中,那么使用 UDF可能会成为性能杀手,因此我不会在查询本身中使用 XML 或 UDF。我首先将 XML 中的值获取到局部变量、表变量或临时表,然后在查询中使用这些值。

于 2012-05-18T13:04:27.437 回答
1

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

使用用户定义表类型时要注意的一件事:出于某种原因,Microsoft 认为阻止您更改它们是一个好主意,您只能删除/添加它们。然后其他人认为如果某些东西依赖于它们,最好阻止你丢弃它们,所以如果你手动更改它们,你最终会经历一个非常痛苦的过程来丢弃/重新组合它们。

我们没有将所有参数封装到单个 UDT 中,只是因为我们的需求因过程而异。因此,当我们有事物列表时,我们使用 UDT 作为该参数,但我可以很容易地看到 One UDT To Rule Them All 很有用,它有一些方便的函数来提取众所周知的值,如日期。我鄙视多次编写相同的代码,这肯定会以较小的成本缩小您的代码库,从而增加复杂性。一个附带的好处是迫使所有开发人员坚持一种标准的做事方式,这在关键时间到来时并不总是强制执行。您还将在数据层中为代码重用开辟一些不错的机会。

于 2012-05-21T16:55:40.663 回答