我需要从使用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可能不是一个好主意。
有没有人写过类似的东西我可以作为起点?或建议以一种良好,可靠的方式来实现这一目标?
答案 0 :(得分:0)
您需要将一些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%'
答案 1 :(得分:0)
虽然没有直接帮助 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
被故意禁用,以内联过滤器值,而不必为每个过滤器参数跟踪和注入 SqlParameter。它简化了代码,并且已经在一定程度上进行了消毒。如果这不能满足您的安全顾虑,则需要您付出额外的努力。WHERE
子句,这是因为如果此查询涉及投影并且调用者试图将过滤器应用于辅助范围之一(连接的结果集)然后 EF 将通过过滤子查询来优化查询,而不是在最后应用所有过滤器。有很多方法可以解决这个问题或使用它,现在让我们坚持一个简单的例子。modifiedQuery
生成的 SQL:网址: ~/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)
在这个例子中,我使用了一个不同的 EF DbContext,但是一旦你有了 SQL 脚本,你应该能够以任何你需要的方式运行它。