有什么方法可以将SqlParameter从一个类传递到另一个类?

时间:2011-10-19 15:05:04

标签: sql vb.net input sanitization sqlparameters

我有一个类 LogRequest ,其中包含对象的参数(成员)。
此对象转向使用另一个类 DataAccess 的SQL查询 最后,我有运行sql查询的搜索类。

在搜索中:

'Create the request
Dim myRequest As LogRequest = MakeRequest()
'Does the SQL query part
responseList = DataAccess.ReadLogs(myRequest)

我的问题是在LogRequest中建立了 Where 部分(“x = 1”,“b = 2”,“x和y之间的日期......”)。

在DataAccess中:

' request is a LogRequest object  
For Each filter As String In request.GetFilters()
    sqlfilters.AppendLine(String.Format("{0} {1}", IIf(first, "WHERE", "AND"), filter))
    first = False
Next
sql = String.Format("SELECT * FROM table_x {0} ", sqlfilters)    

关于如何破坏这些字符串以使用sqlParameters的任何建议?

这是一个设计问题,我应该将“GetFilters”部分移动到dataAccess类吗?

编辑:

此外,任何有关如何将sqlParameters从函数传递到另一个的建议都将受到赞赏。 (第二个创建和处理命令。)

2 个答案:

答案 0 :(得分:1)

首先,用sql参数替换字符串参数是件好事,特别是如果你在其中包含用户输入。这可以防止所谓的SQL注入攻击!

您尝试解决的问题看起来很像query object的实现。我会在query object内保留代表查询的所有逻辑,在您的情况下,这将是LogRequest对象。

然后我会创建一个(SQL)查询转换器类,它将给定的查询对象转换为可以执行的sql表示形式。在您的情况下,这将是ReadLogs()方法。 您可以考虑以更通用的方式设置它,以便它不介意您是否要读取日志或执行其他类型的查询。这为您提供了一种灵活的定义,转换方式 并执行查询。

为了满足您的需求,您将不得不摆脱当前的过滤器实现。您必须在单独的属性中分隔columnname,operator和operand。最好的方法是定义一个标准类。

以下示例的目的是让您朝着正确的方向前进。它们不是一个完整的开箱即用解决方案。

BTW我很抱歉这段代码在C#中,而我应该知道一些VB.NET,这需要我更多的时间,而我现在还没有。

public class Criterion
{
    public string PropertyName { get; set; }
    public object Value { get; set; }
    public CriterionOperator Operator { get; set; }
}

public enum CriterionOperator
{
    Equals        
}

由于我的示例仅支持相等运算符,因此您可以根据需要添加更多内容。这可能还需要额外的Criterion类。

为了保存我们的查询,我们还需要定义一些接口,枚举和抽象类。

public enum QueryOperator
{
    And,
    Or
}

public interface IQuery
{
    IEnumerable<Criterion> Criteria { get; }
    QueryOperator QueryOperator { get; set; }
    void Add(Criterion criterion);
}

public interface IQuery<TResult> : IQuery
{
    TResult Execute();
}

public abstract class BaseQuery<TResult> : IQuery<TResult>
{
    private readonly List<Criterion> _criteria = new List<Criterion>();

    public IEnumerable<Criterion> Criteria
    {
        get { return _criteria; }
    }

    public QueryOperator QueryOperator
    {
        get;
        set;
    }

    public void Add(Criterion criterion)
    {
        _criteria.Add(criterion);
    }

    public abstract TResult Execute();
}

public abstract class SqlQuery<TResult> : BaseQuery<TResult>
{
    protected string _baseSelectQuery = String.Empty;

    protected SqlQuery(string baseSelectQuery)
    {
        _baseSelectQuery = baseSelectQuery;
    }
}

我们现在可以创建一个SQL Query Translator类,它负责将IQuery和基本选择查询转换为适当的SQLCommand。

public static class SqlQueryTranslator
{
    public static void Translate(IQuery query, string baseSelectQuery, SqlCommand command)
    {
        var sqlQuery = new StringBuilder();

        sqlQuery.Append(baseSelectQuery);

        if (query.Criteria.Count() > 0)
        {
            sqlQuery.Append("WHERE ");
        }

        var isNotFirst = false;

        foreach (Criterion criterion in query.Criteria)
        {
            if (isNotFirst)
                sqlQuery.Append(query.QueryOperator == QueryOperator.And ? "AND " : "OR ");

            sqlQuery.Append(GetQueryPartFrom(criterion));

            command.Parameters.Add(new SqlParameter("@" + criterion.PropertyName, criterion.Value));

            isNotFirst = true;
        }

        command.CommandType = CommandType.Text;
        command.CommandText = sqlQuery.ToString();
    }

    private static string GetQueryPartFrom(Criterion criterion)
    {
        return string.Format("{0} {1} @{2}",
                             criterion.PropertyName,
                             GetSqlOperatorFor(criterion.Operator),
                             criterion.PropertyName);
    }

    private static string GetSqlOperatorFor(CriterionOperator criterionOperator)
    {
        switch (criterionOperator)
        {
          case CriterionOperator.Equals:
                return "=";
            default:
                throw new ApplicationException("Not supported Operator");
        }
    }
}

看看这个部分:

sqlQuery.Append(GetQueryPartFrom(criterion));

command.Parameters.Add(new SqlParameter("@" + criterion.PropertyName, criterion.Value));

如果您认为这一切都很多,则只能使用此部分将字符串参数替换为SQL参数。

好了,现在我们可以使用所有类型来创建ReadLogs查询(在此示例中,查询返回一系列字符串):

public class ReadLogsQuery : SqlQuery<IEnumerable<string>>
{
    public ReadLogsQuery(): base("SELECT * FROM table_x ")
    {
        Add(new Criterion() { PropertyName = "x", Operator = CriterionOperator.Equals, Value = 1 });
        Add(new Criterion() { PropertyName = "y", Operator = CriterionOperator.Equals, Value = 2 });
        QueryOperator = QueryOperator.And;
    }

    public override IEnumerable<string> Execute()
    {
        //define result;
        var result = new List<string>();

        using (var conn = new SqlConnection("PUT IN YOU CONNECTION STRING"))
        {
            SqlCommand command = conn.CreateCommand();

            SqlQueryTranslator.Translate(this, _baseSelectQuery, command);

            conn.Open();

            using (SqlDataReader reader = command.ExecuteReader())
            {
                while(reader.Read())
                {
                    //read from the datareader
                    result.Add(reader["colname"].ToString());
                }
            }
        }

        return result;
    }
}

然后您可以按如下方式执行查询:

var query = new ReadLogsQuery();

IEnumerable<string> result = query.Execute();

就像我之前说过的,这不是开箱即用的解决方案!例如,您可以扩展查询类以支持排序,事务,命名查询(存储过程/函数),更多标准运算符(gt,lt,in等)或向数据访问部分添加更抽象级别的抽象。

我希望其他人可以帮助你将它翻译成VB.NET它应该不那么难。如果有必要,我可以在晚上做这个tommorow。让我知道!

您是否还可以详细说明有关传递sqlparameters的问题。它有什么问题?将一系列SQL参数传递给antoher函数没有问题。

答案 1 :(得分:0)

使用存储过程

在存储过程中使用布尔代数或动态SQL来打开和关闭部分查询。例如,在下面的这个例子中(使用布尔逻辑来启用/禁用参数),如果查询长度为零,则不会在查询中使用这三个参数。

ALTER Procedure [dbo].[Report_ExportReworkData]
@x as nvarchar(50),
@b as nvarchar(50),
@n as nvarchar(50)

-- Make sure this gets compiled each time to avoid incorrect parmeter sniffing
WITH RECOMPILE
as

select
    FieldName1,
    FieldName2
from tablename where
(len(@x) = 0 or x = @x) and
(len(@b) = 0 or b = @b) and
(len(@n) = 0 or n = @n)

三个参数最终是可选的,如果你传递一个空白字符串,它们不会在where子句中使用。如果你想忽略一个数字参数你可以传入你知道它不能的值并进行比较,或者你可以传入其他参数来单独打开和关闭每个参数。如果在表中的所有记录中传递三个零长度参数,则返回。

确保在sp中包含WITH RECOMPILE以在以多种不同方式运行此sp时停止不正确的参数嗅探/性能命中。我在why you should do this in some cases.

上发了一篇博文

然后,您可以将此存储过程用作带参数的普通存储过程。

动态构建c#代码中的参数可能非常糟糕,因为您可能会打开SQL注入攻击。