我想问一下其他程序员如何生成动态SQL字符串以作为SQLCommand对象的CommandText执行。
我正在生成包含用户生成的WHERE子句和SELECT字段的参数化查询。有时查询很复杂,我需要对如何构建不同部分进行大量控制。
目前,我使用许多循环和switch语句来生成必要的SQL代码片段并创建所需的SQL参数对象。这种方法很难遵循,它使维护成为一项真正的苦差事。
有更清洁,更稳定的方法吗?
任何建议?
编辑: 要在我之前的帖子中添加细节:
我会展示一些代码(恐怖!),以便你们知道我正在处理什么。
sqlCmd.CommandText = "DECLARE @t Table(ContactId int, ROWRANK int" + declare
+ ")INSERT INTO @t(ContactId, ROWRANK" + insertFields + ")"//Insert as few cols a possible
+ "Select ContactID, ROW_NUMBER() OVER (ORDER BY " + sortExpression + " "
+ sortDirection + ") as ROWRANK" // generates a rowrank for each row
+ outerFields
+ " FROM ( SELECT c.id AS ContactID"
+ coreFields
+ from // sometimes different tables are required
+ where + ") T " // user input goes here.
+ groupBy+ " "
+ havingClause //can be empty
+ ";"
+ "select @@rowcount as rCount;" // return 2 recordsets, avoids second query
+ " SELECT " + fields + ",field1,field2" // join onto the other cols n the table
+" FROM @t t INNER JOIN contacts c on t.ContactID = c.id"
+" WHERE ROWRANK BETWEEN " + ((pageIndex * pageSize) + 1) + " AND "
+ ( (pageIndex + 1) * pageSize); // here I select the pages I want
在这个例子中,我正在查询XML数据。对于纯粹的关系数据,查询要简单得多。每个节变量都是StringBuilders。条款的构建方式如下:
// Add Parameter to SQL Command
AddParamToSQLCmd(sqlCmd, "@p" + z.ToString(), SqlDbType.VarChar, 50, ParameterDirection.Input, qc.FieldValue);
// Create SQL code Fragment
where.AppendFormat(" {0} {1} {2} @p{3}", qc.BooleanOperator, qc.FieldName, qc.ComparisonOperator, z);
答案 0 :(得分:2)
我有必要在我最近的一个项目中这样做。以下是我用于生成SQL的方案:
它仍然有点复杂,但最后你知道查询的每个部分的SQL生成源自哪里(我不认为有任何大的switch语句)。并且不要忘记使用StringBuilder。
答案 1 :(得分:2)
我们创建了自己的FilterCriteria对象,它是一种黑盒动态查询构建器。它具有SelectClause,WhereClause,GroupByClause和OrderByClause的集合属性。它还包含CommandText,CommandType和MaximumRecords的属性。
然后我们将FilterCriteria对象传递给我们的数据逻辑,并对数据库服务器执行它,并将参数值传递给执行动态代码的存储过程。
适用于我们......并使SQL生成很好地包含在对象中。
答案 2 :(得分:1)
您可以尝试CodeSmith等代码生成工具使用的方法。使用占位符创建SQL模板。在运行时,将模板读入字符串并用实际值替换占位符。这仅在所有SQL代码都遵循模式时才有用。
答案 3 :(得分:1)
Gulzar和Ryan Lanciaux在提及CodeSmith和ORM时提出了很好的观点。在生成动态SQL时,这些可能会减少或消除您当前的负担。您当前使用参数化SQL的方法是明智的,因为它可以很好地保护SQL注入攻击。
如果没有实际的代码示例进行评论,很难为您当前使用的循环和切换语句提供明智的替代方案。但是既然你提到你正在设置CommandText属性,我建议在你的实现中使用string.Format(如果你还没有使用它)。我认为它可以使您的代码更容易重组,从而提高可读性和理解。
答案 4 :(得分:1)
通常它是这样的:
string query= "SELECT {0} FROM .... WHERE {1}"
StringBuilder selectclause = new StringBuilder();
StringBuilder wherecaluse = new StringBuilder();
// .... the logic here will vary greatly depending on what your system looks like
MySqlcommand.CommandText = String.Format(query, selectclause.ToString(), whereclause.ToString());
我也刚刚开始使用ORM。你可能想看看其中一个。 ActiveRecord / Hibernate是谷歌的一些很好的关键词。
答案 5 :(得分:1)
如果您确实需要从代码中执行此操作,那么ORM可能是尝试保持其清洁的方法。
但是我想提供一个运行良好的替代方案,可以避免伴随动态查询的性能问题,因为需要创建新的查询计划的SQL会发生变化,对索引有不同的要求。
创建一个接受所有可能参数的存储过程,然后在where子句中使用类似的东西:
where...
and (@MyParam5 is null or @MyParam5 = Col5)
然后,从代码中,当参数值不适用时,将参数值设置为DBNull.Value要简单得多,而不是更改您生成的SQL字符串。
您的DBA会对您更加满意,因为他们将有一个地方可以进行查询调优,SQL将易于阅读,并且他们不必挖掘探查器跟踪以找到许多不同的查询由您的代码生成。
答案 6 :(得分:0)
出于好奇,您是否考虑使用ORM来管理数据访问。您尝试实现的许多功能已经存在。这可能是值得关注的,因为它最好不要重新发明轮子。
答案 7 :(得分:0)
ORM已经解决了动态SQL生成的问题(我更喜欢NHibernate / ActiveRecord)。使用这些工具,您可以通过循环用户输入并生成Expression对象数组来创建具有未知条件数的查询。然后使用该自定义表达式集执行内置查询方法。
List<Expression> expressions = new List<Expression>(userConditions.Count);
foreach(Condition c in userConditions)
{
expressions.Add(Expression.Eq(c.Field, c.Value));
}
SomeTable[] records = SomeTable.Find(expressions);
还有更多'表达式'选项:不相等,大于/小于,空/非空等。我刚刚组成的'条件'类型,你可以把你的用户输入填入一个有用的类。