2010-10-15 76 views
3

我有一個帶參數的SqlCommand。由於某些外部要求(見PS),我需要一個T-SQL字符串代替,即我需要... @parameter ...... [email protected] ...取代(例如:O'Brien - >'O''Brien'02 Mar 2010 - >'20100302'3.5 - >3.5)。將SqlCommand轉換爲T-SQL命令

我知道我可以很容易地在家釀造這樣的解決方案,但正確的轉義是非常棘手的(確保您獲得正確的日期和數字格式,注意字符串中的引號等),我想我不是唯一一個需要這個,所以我的問題是:

是否有一個現有的解決方案,用參數將SqlCommand轉換爲嵌入參數並正確轉義的單個T-SQL語句?

PS:我們需要這樣做,因爲我們需要使用SqlCommand作爲MS Access報表的數據源......並且Access中的「pass-though querydefs」不支持參數化SQL。

PPS:我知道有similarquestions,但他們都認爲SQL服務器莫名其妙地做這種轉換(@parameter - >一些逃脫代表字符串值),所以答案(「SQL Server不這樣做「)在這裏不適用。我知道SQL Server中不存在這種轉換。

+0

我不想被否定,但正如其他問題已經澄清 - 並且正如您自己提到的那樣 - 您要查找的TSQL字符串永遠不會生成。所以我不確定你想要得到什麼答案,除了自己推出或修改你的應用程序? – Pondlife 2010-10-15 14:55:02

+0

@Pondlife:我希望得到這樣的答案:「是的,我之前遇到過這個問題,並且我已經編寫併發布了一個C#/ VB {鏈接到源代碼}的例程,它根據在{link to MSDN}找到SQL Server轉義規範。「 – Heinzi 2010-10-15 15:00:22

回答

5

我認爲這是非常清晰地寫出來與參數的SQL查詢,並有主SELECT查詢前幾DECLARE @p1 type;SET @p1 = 'value';線和留在SqlCommand實例不變原來的查詢文本。我有一些示例代碼,如果你想看到這一點,可以這樣做。

如果您確實堅持在查詢文本中簡單地進行參數值替換,那麼找到您正在與之通話的特定於服務器的T-SQL語法,解析查詢,直到找到參數引用,並根據T-SQL語法的字符串轉義規則將其替換爲文本。

編輯

因爲我這樣一個富於幻想的人,我會得到你開始用自己的方式與適當的轉義輸出格式的SQL參數。這是不完整的,因爲它不處理man和SQL Server已知的每種數據庫類型,但它爲我完成了工作,因爲它處理了我最常用的類型。正如我在我的評論中所說的,只需將單引號字符作爲連續的兩個單引號字符轉義似乎足以進行正確的字符串轉義。您必須對各種SQL Server版本使用的T-SQL語法進行雙倍和三倍的檢查,以確保這是足夠好的。

private static string FormatSqlValue(System.Data.Common.DbParameter prm) 
{ 
    if (prm.Value == DBNull.Value) return "NULL"; 
    switch (prm.DbType) 
    { 
     case System.Data.DbType.Int32: return (prm.Value.ToString()); 
     case System.Data.DbType.String: return String.Format("'{0}'", ScrubSqlString((string)prm.Value)); 
     case System.Data.DbType.AnsiString: return String.Format("'{0}'", ScrubSqlString((string)prm.Value)); 
     case System.Data.DbType.Boolean: return ((bool)prm.Value ? "1" : "0"); 
     case System.Data.DbType.DateTime: return String.Format("'{0}'", prm.Value.ToString()); 
     case System.Data.DbType.DateTime2: return String.Format("'{0}'", prm.Value.ToString()); 
     case System.Data.DbType.Decimal: return (prm.Value.ToString()); 
     case System.Data.DbType.Guid: return String.Format("'{0}'", prm.Value.ToString()); 
     case System.Data.DbType.Double: return (prm.Value.ToString()); 
     case System.Data.DbType.Byte: return (prm.Value.ToString()); 
     // TODO: more conversions. 
     default: return (prm.DbType.ToString()); 
    } 
} 

private static string FormatSqlValue(Type type, object value) 
{ 
    if (value == null) 
     return "NULL"; 
    // Handle Nullable<T> types: 
    if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) 
    { 
     // If the Nullabe<T> value has been found to have !HasValue, return "NULL": 
     if (!(bool)type.GetProperty("HasValue").GetValue(value, null)) 
      return "NULL"; 
     // Try our best to format the underlying non-nullable value now: 
     return FormatSqlValue(type.GetGenericArguments()[0], type.GetProperty("Value").GetValue(value, null)); 
    } 
    if (type == typeof(Int32)) return value.ToString(); 
    if (type == typeof(String)) return String.Format("'{0}'", ScrubSqlString((string)value)); 
    if (type == typeof(Boolean)) return ((bool)value ? "1" : "0"); 
    if (type == typeof(DateTime)) return String.Format("'{0}'", value.ToString()); 
    if (type == typeof(Decimal)) return (value.ToString()); 
    if (type == typeof(Guid)) return String.Format("'{0}'", value.ToString()); 
    if (type == typeof(Double)) return (value.ToString()); 
    if (type == typeof(Byte)) return (value.ToString()); 
    // TODO: complete the mapping... 
    return value.ToString(); 
} 

private static string ScrubSqlString(string value) 
{ 
    StringBuilder sb = new StringBuilder(); 
    int i = 0; 
    while (i < value.Length) 
    { 
     if (value[i] == '\'') 
     { 
      sb.Append("\'\'"); 
      ++i; 
     } 
     else 
     { 
      sb.Append(value[i]); 
      ++i; 
     } 
    } 
    return sb.ToString(); 
} 

private static string FormatSqlParameter(System.Data.Common.DbParameter prm) 
{ 
    StringBuilder sbDecl = new StringBuilder(); 
    sbDecl.Append(prm.ParameterName); 
    sbDecl.Append(' '); 
    switch (prm.DbType) 
    { 
     case System.Data.DbType.Int32: sbDecl.Append("int"); break; 
     // SQL does not like defining nvarchar(0). 
     case System.Data.DbType.String: sbDecl.AppendFormat("nvarchar({0})", prm.Size == -1 ? "max" : prm.Size == 0 ? "1" : prm.Size.ToString()); break; 
     // SQL does not like defining varchar(0). 
     case System.Data.DbType.AnsiString: sbDecl.AppendFormat("varchar({0})", prm.Size == -1 ? "max" : prm.Size == 0 ? "1" : prm.Size.ToString()); break; 
     case System.Data.DbType.Boolean: sbDecl.Append("bit"); break; 
     case System.Data.DbType.DateTime: sbDecl.Append("datetime"); break; 
     case System.Data.DbType.DateTime2: sbDecl.Append("datetime2"); break; 
     case System.Data.DbType.Decimal: sbDecl.Append("decimal"); break; // FIXME: no precision info in DbParameter! 
     case System.Data.DbType.Guid: sbDecl.Append("uniqueidentifier"); break; 
     case System.Data.DbType.Double: sbDecl.Append("double"); break; 
     case System.Data.DbType.Byte: sbDecl.Append("tinyint"); break; 
     // TODO: more conversions. 
     default: sbDecl.Append(prm.DbType.ToString()); break; 
    } 
    return sbDecl.ToString(); 
} 
+0

+1,謝謝,這是一個不錯的方法。但是,原來的問題仍然存在,因爲SET @ p1 ='value''需要與'WHERE myfield ='value''相同的轉義類型。所以「*根據語法規則逃避」在*兩種情況下都是必需的。你有鏈接到SQL Server的「轉義規則」嗎? (SO上的這類「逃生規則」的問題立即用「僅使用參數化查詢」來回答,這在這種情況下不是非常有用......) – Heinzi 2010-10-15 14:57:52

+0

我意識到在發佈後:)對不起。我只做了基本的字符串轉義規則,這些規則對於我的顯示目的來說「足夠好」。如果你只是掩飾轉義字符的轉義,我認爲你已經設定好了。 – 2010-10-15 16:17:58

+0

@Heinzi:我用我爲我的SQL審計工具開發的代碼更新了答案,該工具向人類用戶顯示SQL代碼,但不打算用T-SQL引擎進行分析。如果可能的話,你會發現TODO和FIXME指出可以改進的地方。希望有所幫助! – 2010-10-15 16:33:24