虽然没有直接帮助 OP,但多年来我一直回到这个问题,并开发了另一个技巧,如果您当前的架构接近旧数据库,您可以使用。
这仅适用于您可以针对 EF 上下文创建类似或相同的查询,我们将利用 Linq to Entity SQL 表别名约定,因此它可能会受到未来更新的影响。
- 定义一个与您的输出表结构非常接近的 EF 查询。
- 使用
FilterQueryOption.ApplyTo() 将$filter 应用于近似查询
- 从查询中捕获 SQL 字符串
- 从查询中提取
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。它简化了代码,并且已经在一定程度上进行了清理。如果这不能满足您的安全问题,您需要付出额外的努力。
- 您会注意到我过滤到查询中的 LAST
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 表达式时使用这个技巧,这些表达式返回 DTO 结构,可以使用比 EF ca 生成的更简单的 SQL 来实现。将传统的 EF 查询替换为DbContext.Database.SqlQuery<T>(sql, parameters) 形式的原始 SQL 查询
在此示例中,我使用了不同的 EF DbContext,但是一旦您拥有 SQL 脚本,您应该能够根据需要运行它。