[英]Parsing OData $filter into SQL Where clause
我需要從使用 ODATA 的 Web API 服務器 (C#) 查詢舊數據庫中的表。 我有一個用於舊數據庫的基本 ODBC 驅動程序,此時我只需要支持基本過濾(eq、startswith 和 substringof)。 例如:
queryOptions.Filter.RawValue:
( (startswith(Name,'Bill')) and
(substringof('sunset',Address)) and
(substringof('7421',Phone)) )
應該轉換成這樣的(我只關心這里的 WHERE 子句):
SELECT CustName, Address1, Address2, ...
FROM Customers
WHERE CustName like 'Bill%' AND
Address1 like '%sunset% AND
Phone like '%7421%'
我意識到解析 RawValue 可能不是一個好主意。
有沒有人已經寫過類似的東西,我可以用它作為起點? 或者就一個好的、可靠的方法來實現這一目標提供建議?
您需要對原始值應用一些 Regex,獲取匹配項並應用一些邏輯來進行轉換。 基本上,搜索帶有參數的函數,刪除函數文本,獲取參數並將它們轉換為 like 子句。 像這樣的東西:
string str = @"( (startswith(Name,'Bill')) and
(substringof('sunset',Address)) and
(substringof('7421',Phone)) )";
System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(@"startswith\(([^\)]+)\)");
System.Text.RegularExpressions.Match match = regex.Match(str);
if (match.Success)
{
string tmp = match.Value;
string destination = "@field LIKE '@val%'";
tmp = tmp.Replace( "startswith(","");
tmp = tmp.Replace( ")","");
string[] keyvalue = tmp.Split(',');
string field = keyvalue[0];
string val = keyvalue[1];
destination = destination.Replace("@field", field);
destination = destination.Replace("@val", val.Replace("'",""));
Console.WriteLine( destination );
}
這輸出:
Name LIKE 'Bill%'
雖然沒有直接幫助 OP,但多年來我一直回到這個問題,並開發了另一個技巧,如果您當前的架構接近遺留數據庫,您可以使用它。
這僅適用於您可以針對 EF 上下文創建相似或相同的查詢時,我們將利用 Linq to Entity SQL 表別名約定,因此它可能會受到未來更新的影響。
FilterQueryOption.ApplyTo()
將$filter
僅應用於近似查詢WHERE
子句WHERE
子句注入您的自定義查詢。除了綁定到 EF 注入的表別名約束之外,這比單獨使用 REGEX 提供了很多安全性和靈活性。 您可能會發現可以使用正則表達式進一步增強此輸出,但是 OData 解析器已經將 URL 表達式驗證並清理為有效的 SQL 語法,包括將表達式轉換為 SQL 函數調用。
以下基於 EF6 和 OData v4,因此 URL 語法略有不同,但相同的概念也應適用於以前版本的 ODataLib。
CustomDTO
是一個自定義類,未在 EF DbContext 模型中定義。Customer
IS定義在 EF DbContext 中,它具有與遺留數據庫相似的字段/// <summary>Return a list of customer summaries for a given Account</summary>
[EnableQuery, HttpGet]
public IQueryable<CustomDTO> Customers([FromODataUri] int key, ODataQueryOptions<CustomDTO> _queryOptions)
{
// The custom query we want to apply to the legacy database.
// NOTE: If the fields are identical to the current DbContext, then we don't have to go this far.
// We MUST alias the table to match the generated SQL
string sql = "SELECT CustName, IsNull(Address1,'') + IsNull(Address2,'') as Address, Phone " +
"FROM Customers AS [Extent1]" +
"WHERE AccountId = @AccountId";
if (!String.IsNullOrWhiteSpace(_queryOptions.Filter?.RawValue))
{
var criteriaQuery = from x in db.Customers
select new CustomDTO
{
Name = CustName,
Address = Address1 + Address2
Phone = Phone
};
var modifiedQuery = _queryOptions.Filter.ApplyTo(criteriaQuery, new ODataQuerySettings({ EnableConstantParameterization = false });
string modifiedSql = modifiedQuery.ToString();
modifiedSql = modifiedSql.Substring(modifiedSql.LastIndexOf("WHERE ") + 5);
sql += $" AND ({modifiedSql})";
}
var customers = aDifferentContext.Database.SqlQuery<CustomDTO>(sql, new SqlParameter("@AccountId", key)).ToList();
return customers.AsQueryable();
}
[Extent1]
的另一種方法是使用字符串替換,但這已經足夠了。EnableConstantParameterization
被故意禁用, EnableConstantParameterization
聯過濾器值,而不必為每個過濾器參數跟蹤和注入 SqlParameter。 它簡化了代碼,並且已經在一定程度上進行了消毒。 如果這不能滿足您的安全問題,則取決於您是否付出額外的努力。WHERE
子句,這是因為如果此查詢涉及投影並且調用者嘗試將過濾器應用於輔助范圍之一(連接的結果集),那么 EF 將通過過濾子查詢,而不是在最后應用所有過濾器。 有辦法解決這個問題或使用它,現在讓我們堅持一個簡單的例子。modifiedQuery
生成的 SQL: URL: ~/OData/Accounts(1102)/Customers?$filter=startswith(Name, 'Bill') and contains(Address, 'sunset') and contains(Phone, '7421')
Filter.RawValue: startswith(Name, 'Bill') and contains(Address, 'sunset') and contains(Phone, '7421')
SELECT
[Extent1].[CustName] AS [Name],
CASE WHEN ([Extent1].[Address1] IS NULL) THEN N'' ELSE [Extent1].[Address1] END + CASE WHEN ([Extent1].[Address2] IS NULL) THEN N'' ELSE [Extent1].[Address2] END AS [C1],
[Extent1].[Phone] AS [Phone]
FROM [dbo].[Customer] AS [Extent1]
WHERE ([Extent1].[CustName] LIKE 'Bill%')
AND (CASE WHEN ([Extent1].[Address1] IS NULL) THEN N'' ELSE [Extent1].[Address1] END
+ CASE WHEN ([Extent1].[Address2] IS NULL) THEN N'' ELSE [Extent1].[Address2] END
LIKE N'%sunset%')
AND ([Extent1].[Phone] LIKE '%7421%')
執行的最終 SQL:
SELECT CustName as Name, IsNull(Address1,'') + IsNull(Address2,'') as Address, Phone
FROM [dbo].[Customer] AS [Extent1]
WHERE AccountId = @AccountId AND (([Extent1].[CustName] LIKE 'Bill%')
AND (CASE WHEN ([Extent1].[Address1] IS NULL) THEN N'' ELSE [Extent1].[Address1] END
+ CASE WHEN ([Extent1].[Address2] IS NULL) THEN N'' ELSE [Extent1].[Address2] END
LIKE N'%sunset%')
AND ([Extent1].[Phone] LIKE '%7421%'))
public class CustomDTO
{
public string Name { get;set; }
public string Address { get;set; }
public string Phone { get;set; }
}
public class Customer
{
public int AccountId { get;set; }
public string CustName { get;set; }
public string Address1 { get;set; }
public string Address2 { get;set; }
public string Phone { get;set; }
}
我主要在優化復雜的 Linq 表達式時使用這個技巧,這些表達式返回可以使用比 EF ca 生成的更簡單的 SQL 實現的 DTO 結構。 什么是傳統的 EF 查詢被替換為DbContext.Database.SqlQuery<T>(sql, parameters)
形式的原始 SQL 查詢
在這個例子中,我使用了一個不同的 EF DbContext,但是一旦你有了 SQL 腳本,你應該能夠運行它,但是你需要。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.