C# 将值集合传递给指定要排序的列和要筛选的谓词的存储过程的最安全的方法

C# 将值集合传递给指定要排序的列和要筛选的谓词的存储过程的最安全的方法,c#,sql-server,stored-procedures,dapper,C#,Sql Server,Stored Procedures,Dapper,我们正在构建一个需要利用MVC数据网格的MVC项目。作为其中的一部分,我们希望允许对DataGrid列进行过滤和排序。我们希望在Sql端通过分页来处理这个问题。处理分页非常简单,我们已经在存储过程中实现了这一点 我们现在面临的挑战是如何将用户排序所依据的列放入存储过程中,以便在分页期间对记录进行排序。我使用一个表类型发送一个“集合”列,如下所示: CREATE TYPE [dbo].[SortableEntity] AS TABLE( [TableName] [varchar](50)

我们正在构建一个需要利用MVC数据网格的MVC项目。作为其中的一部分,我们希望允许对DataGrid列进行过滤和排序。我们希望在Sql端通过分页来处理这个问题。处理分页非常简单,我们已经在存储过程中实现了这一点

我们现在面临的挑战是如何将用户排序所依据的列放入存储过程中,以便在分页期间对记录进行排序。我使用一个表类型发送一个“集合”列,如下所示:

CREATE TYPE [dbo].[SortableEntity] AS TABLE(
    [TableName] [varchar](50) NULL,
    [ColumnName] [varchar](50) NULL,
    [Descending] [bit] NULL
)

CREATE PROCEDURE [dbo].[DoSomethingWithEmployees]
  @SortOrder AS dbo.SortableEntity READONLY
AS
BEGIN
    SELECT [ColumnName] FROM @SortOrder
END
我们使用Dapper作为我们的ORM,并且根据策略,我们只能使用存储过程。在我的存储库中,我使用下面的DataTable尝试将记录插入到
SortableEntity
中,该实体工作正常

var parameters = new DynamicParameters();

// Check if we have anything to sort by
IEnumerable<SortDefinition> sortingDefinitions = builder.GetSortDefinitions();
if (sortingDefinitions.Count() > 0)
{
    var dt = new DataTable();
    dt.Columns.Add(nameof(SortableEntity.TableName));
    dt.Columns.Add(nameof(SortableEntity.ColumnName));
    dt.Columns.Add(nameof(SortableEntity.IsDescending));
    Type tableType = typeof(SortableEntity);
    foreach(SortDefinition sortDefinition in sortingDefinitions)
    {
        var dataRow = dt.NewRow();
        dataRow.SetField(0, sortDefinition.TableName);
        dataRow.SetField(0, sortDefinition.Column);
        dataRow.SetField(2, sortDefinition.IsDescending);

        dt.Rows.Add(dataRow);
    }

    parameters.Add("SortOrder", dt.AsTableValuedParameter(tableType.Name));
}
var参数=新的DynamicParameters();
//检查一下我们是否有什么东西需要分类
IEnumerable sortingDefinitions=builder.GetSortDefinitions();
if(sortingDefinitions.Count()>0)
{
var dt=新数据表();
添加(nameof(SortableEntity.TableName));
添加(nameof(SortableEntity.ColumnName));
dt.Columns.Add(nameof(SortableEntity.IsDescending));
Type tableType=typeof(可排序实体);
foreach(排序定义中的排序定义SortDefinition)
{
var dataRow=dt.NewRow();
dataRow.SetField(0,sortDefinition.TableName);
dataRow.SetField(0,sortDefinition.Column);
设置字段(2,sortDefinition.IsDescending);
dt.Rows.Add(数据行);
}
parameters.Add(“SortOrder”,dt.AsTableValuedParameter(tableType.Name));
}
有了它,我可以将排序后的值放入存储过程,但我关心的是Sql注入。一种解决方法是在syscolumns表中查找给定的列是否为有效列,然后再使用它们。我不知道如何执行该操作,如何获取有效列并将它们应用于存储过程中的
orderby
语句。由于我们没有对插入到
数据表中的值使用Sql参数对象,我们如何防止Sql注入?我知道,使用
DynamicParameters
可以保护存储过程参数中的值,但是当值是一个包含值的表时,这是如何工作的呢


然而最大的挑战是
WHERE
子句。我们希望将过滤器从数据网格传递到存储过程中,这样用户就可以过滤出结果集。其思想是存储过程将为我们过滤、排序和分页。我知道我可以使用嵌入式或动态Sql轻松地处理这个问题;事实证明,试图通过存储过程来处理这一问题是我无法理解的。我需要做什么才能让我的存储过程从应用程序接收一个谓词,该谓词适用于一系列列,它作为安全域中的
WHERE
子句应用,这不会让我们接受Sql注入

我想使参数输入“安全”的唯一方法是在分配给存储的过程参数之前检查值。您必须查找“选择”、“删除”和“更新”。但是,我认为,由于您使用的是列名而不是整个动态SQL命令,所以您应该可以。阅读以下内容:

但是,我不是这方面的专家。你应该自己做研究

为了让您了解如何在存储过程中处理动态筛选,我只使用了一个SQL函数,该函数将带有逗号分隔值的字符串拆分,并将其转换为一个表。我将此函数与包含需要筛选的列的表连接起来。例如,我需要使用某个表中的除法列过滤具有多个值的数据集。我的存储过程将接受长度为3000的可选VARCHAR参数:

@strDIVISION VARCHAR(3000) = NULL
接下来,当接收到此参数的NULL值时,为其指定一个空字符串值:

SELECT @strDIVISION = ISNULL(@strDIVISION,'')
不必在WHERE子句中进行筛选,您可以按如下方式加入字符串拆分函数:

...
FROM tblTransDTL td 
   INNER JOIN tblTransHDR th ON th.JOB_ID = td.JOB_ID 
   INNER JOIN dbo.udf_STRSPLIT(@strDIVISION) d1 ON 
      (d1.Value = th.DIVISION OR 1=CASE @DIVISION WHEN '' THEN 1 ELSE 0 END)
CASE语句有助于确定何时应允许所有值或仅使用参数输入中的值

最后,这是将字符串值拆分为表的SQL函数:

CREATE FUNCTION udf_STRSPLIT 
(   
   @Delim_Values VARCHAR(8000)
)
RETURNS  @Result TABLE(Value VARCHAR(2000))
AS
begin
WITH StrCTE(start, stop) AS
(
  SELECT  1, CHARINDEX(',' , @Delim_Values )
  UNION ALL
  SELECT  stop + 1, CHARINDEX(',' ,@Delim_Values  , stop + 1)
  FROM StrCTE
  WHERE stop > 0
)

insert into @Result
SELECT   SUBSTRING(@Delim_Values , start, CASE WHEN stop > 0 THEN stop-start ELSE 4000 END) AS stringValue
FROM StrCTE

return
end
GO

谢谢,我们还在努力。你的解决方案很有趣。我们来试一试,看看进展如何