117

我正在用 C# 构建一些SQL查询。它会根据存储为代码中的变量的某些条件而有所不同。

string Query="SELECT * FROM Table1 WHERE 1=1 ";
if (condition1) 
    Query += "AND Col1=0 ";
if (condition2) 
    Query += "AND Col2=1 ";
if (condition3) 
    Query += "AND Col3=2 ";

它有效,但测试 1=1 似乎并不优雅。如果我不使用它,我必须记住并检查每次是否已将“where”关键字添加到查询中。

有更好的解决方案吗?

4

22 回答 22

160

将条件保存在列表中:

List<string> conditions = new List<string>();

if (condition1) conditions.Add("Col1=0");
//...
if (conditions.Any())
    Query += " WHERE " + string.Join(" AND ", conditions.ToArray());
于 2013-06-26T13:26:54.003 回答
87

一种解决方案是简单地不通过附加字符串来手动编写查询。您可以使用 ORM,例如Entity Framework,并通过 LINQ to Entities 使用语言和框架为您提供的功能:

using (var dbContext = new MyDbContext())
{
    IQueryable<Table1Item> query = dbContext.Table1;

    if (condition1)
    {
        query = query.Where(c => c.Col1 == 0);
    }
    if (condition2)
    {
        query = query.Where(c => c.Col2 == 1);
    }
    if (condition3)
    {
        query = query.Where(c => c.Col3 == 2);
    }   

    PrintResults(query);
}
于 2013-06-26T13:31:09.647 回答
17

在这个简单的案例中有点矫枉过正,但我​​过去使用过类似的代码。

创建一个函数

string AddCondition(string clause, string appender, string condition)
{
    if (clause.Length <= 0)
    {
        return String.Format("WHERE {0}",condition);
    }
    return string.Format("{0} {1} {2}", clause, appender, condition);
}

像这样使用它

string query = "SELECT * FROM Table1 {0}";
string whereClause = string.Empty;

if (condition 1)
    whereClause = AddCondition(whereClause, "AND", "Col=1");

if (condition 2)
    whereClause = AddCondition(whereClause, "AND", "Col2=2");

string finalQuery = String.Format(query, whereClause);

这样,如果没有找到条件,您甚至不必费心在查询中加载 where 语句,并在 sql 服务器解析 sql 语句时为处理垃圾 where 子句节省一微秒的时间。

于 2013-06-26T14:55:50.800 回答
15

还有另一种解决方案,它可能也不优雅,但可以工作并解决问题:

String query = "SELECT * FROM Table1";
List<string> conditions = new List<string>();
// ... fill the conditions
string joiner = " WHERE ";
foreach (string condition in conditions) {
  query += joiner + condition;
  joiner = " AND "
}

为了:

  • 空条件列表,结果将是简单的SELECT * FROM Table1
  • 它将是一个单一的条件SELECT * FROM Table1 WHERE cond1
  • 以下每个条件都会产生额外的AND condN
于 2013-06-26T13:50:14.593 回答
11

只需执行以下操作:

using (var command = connection.CreateCommand())
{
    command.CommandText = "SELECT * FROM Table1";

    var conditions = "";
    if (condition1)
    {    
        conditions += "Col1=@val1 AND ";
        command.AddParameter("val1", 1);
    }
    if (condition2)
    {    
        conditions += "Col2=@val2 AND ";
        command.AddParameter("val2", 1);
    }
    if (condition3)
    {    
        conditions += "Col3=@val3 AND ";
        command.AddParameter("val3", 1);
    }
    if (conditions != "")
        command.CommandText += " WHERE " + conditions.Remove(conditions.Length - 5);
}

这是SQL 注入安全恕我直言,它非常干净。Remove()只需删除最后一个AND;

如果没有设置条件,如果设置了一个条件,或者设置了多个条件,它都可以工作。

于 2013-07-04T06:02:33.257 回答
10

只需在后面附加两行。

string Query="SELECT * FROM Table1 WHERE 1=1 ";
if (condition1) Query+="AND Col1=0 ";
if (condition2) Query+="AND Col2=1 ";
if (condition3) Query+="AND Col3=2 ";
Query.Replace("1=1 AND ", "");
Query.Replace(" WHERE 1=1 ", "");

例如

SELECT * FROM Table1 WHERE 1=1 AND Col1=0 AND Col2=1 AND Col3=2 

会变成

SELECT * FROM Table1 WHERE Col1=0 AND Col2=1 AND Col3=2 

尽管

SELECT * FROM Table1 WHERE 1=1 

会变成

SELECT * FROM Table1

======================================

感谢您指出此解决方案的缺陷:

“如果出于任何原因,其中一个条件包含文本“1=1 AND”或“WHERE 1=1”,这可能会中断查询。如果条件包含子查询或尝试检查是否有例如,列包含此文本。也许这对您来说不是问题,但您应该牢记......“

为了摆脱这个问题,我们需要区分“main” WHERE 1=1和那些来自子查询的,这很容易:

只需使“主要” WHERE特殊:我会附加一个“$”符号

string Query="SELECT * FROM Table1 WHERE$ 1=1 ";
if (condition1) Query+="AND Col1=0 ";
if (condition2) Query+="AND Col2=1 ";
if (condition3) Query+="AND Col3=2 ";

然后仍然附加两行:

Query.Replace("WHERE$ 1=1 AND ", "WHERE ");
Query.Replace(" WHERE$ 1=1 ", "");
于 2013-06-27T03:27:30.017 回答
8

用这个:

string Query="SELECT * FROM Table1 WHERE ";
string QuerySub;
if (condition1) QuerySub+="AND Col1=0 ";
if (condition2) QuerySub+="AND Col2=1 ";
if (condition3) QuerySub+="AND Col3=2 ";

if (QuerySub.StartsWith("AND"))
    QuerySub = QuerySub.TrimStart("AND".ToCharArray());

Query = Query + QuerySub;

if (Query.EndsWith("WHERE "))
    Query = Query.TrimEnd("WHERE ".ToCharArray());
于 2013-06-26T13:35:51.613 回答
8

为什么不使用现有的查询生成器?类似Sql Kata的东西。

它支持复杂的 where 条件、连接和子查询。

var query = new Query("Users").Where("Score", ">", 100).OrderByDesc("Score").Limit(100);

if(onlyActive)
{
   query.Where("Status", "active")
}

// or you can use the when statement

query.When(onlyActive, q => q.Where("Status", "active"))

它适用于 Sql Server、MySql 和 PostgreSql。

于 2017-05-27T16:53:09.557 回答
6

如果这是SQL Server,您可以使这段代码更简洁。

这也假设了已知数量的参数,当我考虑可能性时,这可能是一个糟糕的假设。

在 C# 中,您将使用:

using (SqlConnection conn = new SqlConnection("connection string"))
{
    conn.Open();
    SqlCommand command = new SqlCommand()
    {
        CommandText = "dbo.sample_proc",
        Connection = conn,
        CommandType = CommandType.StoredProcedure
    };

    if (condition1)
        command.Parameters.Add(new SqlParameter("Condition1", condition1Value));
    if (condition2)
        command.Parameters.Add(new SqlParameter("Condition2", condition2Value));
    if (condition3)
        command.Parameters.Add(new SqlParameter("Condition3", condition3Value));

    IDataReader reader = command.ExecuteReader();

    while(reader.Read())
    {
    }

    conn.Close();
}

然后在 SQL 方面:

CREATE PROCEDURE dbo.sample_proc
(
    --using varchar(50) generically
    -- "= NULL" makes them all optional parameters
    @Condition1 varchar(50) = NULL
    @Condition2 varchar(50) = NULL
    @Condition3 varchar(50) = NULL
)
AS
BEGIN
    /*
    check that the value of the parameter 
    matches the related column or that the 
    parameter value was not specified.  This
    works as long as you are not querying for 
    a specific column to be null.*/
    SELECT *
    FROM SampleTable
    WHERE (Col1 = @Condition1 OR @Condition1 IS NULL)
    AND   (Col2 = @Condition2 OR @Condition2 IS NULL)
    AND   (Col3 = @Condition3 OR @Condition3 IS NULL)
    OPTION (RECOMPILE)
    --OPTION(RECOMPILE) forces the query plan to remain effectively uncached
END
于 2013-06-26T14:14:34.293 回答
4

我能想到的最快的文字解决方案是:

string Query="SELECT * FROM Table1";
string Conditions = "";

if (condition1) Conditions+="AND Col1=0 ";
if (condition2) Conditions+="AND Col2=1 ";
if (condition3) Conditions+="AND Col3=2 ";

if (Conditions.Length > 0) 
  Query+=" WHERE " + Conditions.Substring(3);

当然,这看起来并不优雅,我建议您参考 CodeCaster 的使用 ORM 的建议。但是如果你想想这是在做什么,你真的不担心'浪费' 4 个字符的内存,并且计算机将指针移动 4 个位置真的很快。

如果您有时间学习如何使用 ORM,它真的可以为您带来回报。但就这一点而言,如果您试图阻止该附加条件影响 SQL 数据库,这将为您完成。

于 2013-06-26T19:26:49.200 回答
3

根据条件,可以在查询中使用布尔逻辑。像这样的东西:

string Query="SELECT * FROM Table1  " +
             "WHERE (condition1 = @test1 AND Col1=0) "+
             "AND (condition2 = @test2 AND Col2=1) "+
             "AND (condition3 = @test3 AND Col3=2) ";
于 2013-06-26T13:58:00.057 回答
3

我喜欢stringbuilder的流畅界面,所以做了一些ExtensionMethods。

var query = new StringBuilder()
    .AppendLine("SELECT * FROM products")
    .AppendWhereIf(!String.IsNullOrEmpty(name), "name LIKE @name")
    .AppendWhereIf(category.HasValue, "category = @category")
    .AppendWhere("Deleted = @deleted")
    .ToString();

var p_name = GetParameter("@name", name);
var p_category = GetParameter("@category", category);
var p_deleted = GetParameter("@deleted", false);
var result = ExecuteDataTable(query, p_name, p_category, p_deleted);


// in a seperate static class for extensionmethods
public StringBuilder AppendLineIf(this StringBuilder sb, bool condition, string value)
{
    if(condition)
        sb.AppendLine(value);
    return sb;
}

public StringBuilder AppendWhereIf(this StringBuilder sb, bool condition, string value)
{
    if (condition)
        sb.AppendLineIf(condition, sb.HasWhere() ? " AND " : " WHERE " + value);
    return sb;
}

public StringBuilder AppendWhere(this StringBuilder sb, string value)
{
    sb.AppendWhereIf(true, value);
    return sb;
}

public bool HasWhere(this StringBuilder sb)
{
    var seperator = new string [] { Environment.NewLine };
    var lines = sb.ToString().Split(seperator, StringSplitOptions.None);
    return lines.Count > 0 && lines[lines.Count - 1].Contains("where", StringComparison.InvariantCultureIgnoreCase);
}

// http://stackoverflow.com/a/4217362/98491
public static bool Contains(this string source, string toCheck, StringComparison comp)
{
    return source.IndexOf(toCheck, comp) >= 0;
}
于 2013-07-04T06:16:01.330 回答
2

恕我直言,我认为您的方法是错误的:

通过连接字符串来查询数据库绝不是一个好主意(如果您在其他地方进行一些更改, SQL 注入的风险和代码很容易被破坏)。

您可以使用ORM(我使用NHibernate)或至少使用SqlCommand.Parameters

如果您绝对想使用字符串连接,我会使用 a StringBuilder(它是字符串连接的正确对象):

var query = new StringBuilder("SELECT * FROM Table1 WHERE");
int qLength = query.Length;//if you don't want to count :D
if (Condition1) query.Append(" Col1=0 AND");
if (Condition2) query.Append(" Col2=0 AND");
....
//if no condition remove WHERE or AND from query
query.Length -= query.Length == qLength ? 6 : 4;

最后一个想法,Where 1=1真的很丑,但SQL Server无论如何都会优化它。

于 2013-07-03T08:43:25.307 回答
2

Dapper SqlBuilder是一个不错的选择。它甚至用于 StackOverflow 的生产环境。

阅读Sam 的博客文章

据我所知,它不是任何 Nuget 包的一部分,因此您需要将其代码复制粘贴到您的项目中或下载 Dapper 源代码并构建 SqlBuilder 项目。无论哪种方式,您还需要为DynamicParameters该类引用 Dapper。

于 2013-11-23T23:48:30.300 回答
1
public static class Ext
{
    public static string addCondition(this string str, bool condition, string statement)
    {
        if (!condition)
            return str;

        return str + (!str.Contains(" WHERE ") ? " WHERE " : " ") + statement;
    }

    public static string cleanCondition(this string str)
    {
        if (!str.Contains(" WHERE "))
            return str;

        return str.Replace(" WHERE AND ", " WHERE ").Replace(" WHERE OR ", " WHERE ");
    }
}

用扩展方法实现。

    static void Main(string[] args)
    {
        string Query = "SELECT * FROM Table1";

        Query = Query.addCondition(true == false, "AND Column1 = 5")
            .addCondition(18 > 17, "AND Column2 = 7")
            .addCondition(42 == 1, "OR Column3 IN (5, 7, 9)")
            .addCondition(5 % 1 > 1 - 4, "AND Column4 = 67")
            .addCondition(Object.Equals(5, 5), "OR Column5 >= 0")
            .cleanCondition();

        Console.WriteLine(Query);
    }
于 2013-07-20T18:21:24.117 回答
1

我看到这在 Oracle 中一直使用,同时在存储过程中构建动态 SQL 。我在查询数据问题时使用它,只是为了更快地在不同的数据过滤器之间切换......只需注释掉一个条件或轻松地将其添加回来。

我发现审查您的代码的人很容易理解它。

于 2013-07-02T19:15:10.470 回答
0

我想到了一个解决方案,好吧,也许更具可读性:

string query = String.Format("SELECT * FROM Table1 WHERE "
                             + "Col1 = {0} AND "
                             + "Col2 = {1} AND "
                             + "Col3 = {2}",
                            (!condition1 ? "Col1" : "0"),
                            (!condition2 ? "Col2" : "1"),
                            (!condition3 ? "Col3" : "2"));

我只是不确定 SQL 解释器是否也会优化条件(当为假Col1 = Col1时打印)。condition1

于 2013-07-04T21:25:44.793 回答
0

Using string function you can also do it this way:

string Query = "select * from Table1";

if (condition1) WhereClause += " Col1 = @param1 AND "; // <---- put conditional operator at the end
if (condition2) WhereClause += " Col1 = @param2 OR ";

WhereClause = WhereClause.Trim();

if (!string.IsNullOrEmpty(WhereClause))
    Query = Query + " WHERE " + WhereClause.Remove(WhereClause.LastIndexOf(" "));
// else
// no condition meets the criteria leave the QUERY without a WHERE clause  

I personally feel easy to to remove the conditional element(s) at the end, since its position is easy to predict.

于 2013-06-26T21:03:51.363 回答
0

这是一种更优雅的方式:

    private string BuildQuery()
    {
        string MethodResult = "";
        try
        {
            StringBuilder sb = new StringBuilder();

            sb.Append("SELECT * FROM Table1");

            List<string> Clauses = new List<string>();

            Clauses.Add("Col1 = 0");
            Clauses.Add("Col2 = 1");
            Clauses.Add("Col3 = 2");

            bool FirstPass = true;

            if(Clauses != null && Clauses.Count > 0)
            {
                foreach(string Clause in Clauses)
                {
                    if (FirstPass)
                    {
                        sb.Append(" WHERE ");

                        FirstPass = false;

                    }
                    else
                    {
                        sb.Append(" AND ");

                    }

                    sb.Append(Clause);

                }

            }

            MethodResult = sb.ToString();

        }
        catch //(Exception ex)
        {
            //ex.HandleException()
        }
        return MethodResult;
    }
于 2016-03-14T16:02:20.260 回答
0

如前所述,通过连接创建 SQL 绝不是一个好主意。不仅仅是因为 SQL 注入。主要是因为它丑陋,难以维护并且完全没有必要。您必须使用跟踪或调试来运行您的程序,以查看它生成的 SQL。如果您使用QueryFirst(免责声明:我写的),那么不愉快的诱惑就会被消除,您可以直接在 SQL 中进行操作。

此页面全面介绍了用于动态添加搜索谓词的 TSQL 选项。如果您希望将搜索谓词组合的选择留给用户,则以下选项非常方便。

select * from table1
where (col1 = @param1 or @param1 is null)
and (col2 = @param2 or @param2 is null)
and (col3 = @param3 or @param3 is null)
OPTION (RECOMPILE)

QueryFirst 为您提供 C# null 到 db NULL,因此您只需在适当的时候使用 null 调用 Execute() 方法,这一切都可以正常工作。<opinion>为什么 C# 开发人员如此不愿意在 SQL 中做事,即使它更简单。令人难以置信。</opinion>

于 2016-05-31T14:39:09.540 回答
0

正如许多人所说,对于更长的过滤步骤,StringBuilder 是更好的方法。

在你的情况下,我会选择:

StringBuilder sql = new StringBuilder();

if (condition1) 
    sql.Append("AND Col1=0 ");
if (condition2) 
    sql.Append("AND Col2=1 ");
if (condition3) 
    sql.Append("AND Col3=2 ");

string Query = "SELECT * FROM Table1 ";
if(sql.Length > 0)
 Query += string.Concat("WHERE ", sql.ToString().Substring(4)); //avoid first 4 chars, which is the 1st "AND "
于 2017-08-30T08:43:07.417 回答
0

简洁、优雅、甜美,如下图所示。

在此处输入图像描述

于 2018-04-09T08:27:29.347 回答