c# - JSON_Value 错误:“JSON_VALUE 或 JSON_QUERY”的参数 2 必须是字符串文字
问题描述
我正在尝试使用 JSON_VALUE 和 SQL Server 2016。不过,我必须动态构建我的 SQL 查询。
JSON:
{"pageId":"9","moduleId":"6","moduleType":"Pages"}
这是我的错误:
“JSON_VALUE 或 JSON_QUERY”的参数 2 必须是字符串文字。
var sqlParams = new List<SqlParameter>();
StringBuilder sb = new StringBuilder();
// start the initial select query...
sb.Append("SELECT * FROM dbo.FileSystemItems WHERE ");
int counter = 0;
foreach (var item in metadata)
{
// only add an AND if we are NOT the first record...
if (counter != 0)
{
sb.Append(" AND ");
}
// setup our json path and value items...
string pathParam = string.Format(CultureInfo.CurrentCulture, "jsonPathParam{0}", counter);
string valueParam = string.Format(CultureInfo.CurrentCulture, "jsonPathValue{0}", counter);
sb.AppendFormat(CultureInfo.CurrentCulture, "JSON_VALUE(FileMetadata, @{0}) = @{1}", pathParam, valueParam);
// add in our parameters to assist with sql injection
sqlParams.Add(new SqlParameter(pathParam, string.Format(CultureInfo.CurrentCulture, "N'$.{0}'", item.Key)));
sqlParams.Add(new SqlParameter(valueParam, string.Format(CultureInfo.CurrentCulture, "N'{0}'", item.Value)));
counter++;
}
return await BIContext.FileSystemItems
.Where(x => x.ModuleId == moduleId)
.FromSql(sb.ToString(), sqlParams.ToArray())
.Select(s => new FileSystemItemJsonDTO
{
FileId = s.FileId,
FileName = s.FileName,
FileType = s.FileType,
LastWriteTime = s.LastWriteTime,
FileSystemItemDataId = s.FileSystemItemDataId,
ModuleId = moduleId,
FileMetadata = s.FileMetadata,
FileSize = s.FileSize
})
.ToListAsync().ConfigureAwait(false);
这是作为 SQL 查询生成的 StringBuilder 结果
SELECT * FROM dbo.FileSystemItems WHERE JSON_VALUE(FileMetadata, @jsonPathParam0) = @jsonPathValue0 AND JSON_VALUE(FileMetadata, @jsonPathParam1) = @jsonPathValue1
我已经尝试过这篇文章中的项目:
但它仍然给我同样的错误。
我究竟做错了什么?
我需要支持 2016 年和 2017 年。
更新:
我也发现了这个:https ://dapper-tutorial.net/knowledge-base/46860751/csharp-dapper-using-json-value-for-sql-server-2016 ->
这说不可能?
然后我发现了这个:https ://docs.microsoft.com/en-us/sql/t-sql/functions/json-value-transact-sql?view=sql-server-ver15
它说我的版本受支持:
Microsoft SQL Server 2016 (SP2-CU15-GDR) (KB4583461) - 13.0.5865.1 (X64) 2020 年 10 月 31 日 02:43:57 版权所有 (c) Windows 10 Enterprise 10.0 (Build 19041) 上的 Microsoft Corporation Developer Edition(64 位) :)
更新 2:
在下面的响应之后,我找到了适用于 SQL server 2016 和 SQL server 2017 的代码。
我只是想了解 IF/为什么它可以防止 SQL 注入,因为我没有参数化JSON_VALUE()
public async Task<IList<FileSystemItemJsonDTO>> GetFileSystemItems(int moduleId, IDictionary<string, string> metadata)
{
var sqlParams = new List<SqlParameter>();
StringBuilder sb = new StringBuilder();
// start the initial select query...
sb.Append("SELECT * FROM dbo.FileSystemItems WHERE ");
int counter = 0;
foreach (var item in metadata)
{
// only add an AND if we are NOT the first record...
if (counter != 0)
{
sb.Append(" AND ");
}
// setup our json path and value items...
string valueParam = string.Format(CultureInfo.CurrentCulture, "jsonPathValue{0}", counter);
// 2nd item for JSON_VALUE has to be string literal for SQL server 2016
sb.AppendFormat(CultureInfo.CurrentCulture, "JSON_VALUE(FileMetadata, '$.{0}') = @{1}", item.Key, valueParam);
// add in our parameters to assist with sql injection
sqlParams.Add(new SqlParameter(valueParam, string.Format(CultureInfo.CurrentCulture, "{0}", item.Value)));
counter++;
}
return await BIContext.FileSystemItems
.Where(x => x.ModuleId == moduleId)
.FromSql(sb.ToString(), sqlParams.ToArray())
.Select(s => new FileSystemItemJsonDTO
{
FileId = s.FileId,
FileName = s.FileName,
FileType = s.FileType,
LastWriteTime = s.LastWriteTime,
FileSystemItemDataId = s.FileSystemItemDataId,
ModuleId = moduleId,
FileMetadata = s.FileMetadata,
FileSize = s.FileSize
})
.ToListAsync().ConfigureAwait(false);
}
解决方案
正如@user2864740所提到的,我们可以使用表值参数和OPENJSON
.
首先创建一个表类型,我建议你仔细考虑一下确切的列类型和长度:
CREATE TYPE Metadata TABLE (jsonKey nvarchar(100) PRIMARY KEY, jsonValue nvarchar(1000) NOT NULL);
查询使用关系除法:
SELECT * -- Preferably don't use select *, specify necessary columns instead
FROM dbo.FileSystemItems
WHERE moduleId = @moduleId
AND NOT EXISTS (
SELECT m.jsonKey, m.jsonValue
FROM @metadata m
EXCEPT
SELECT j.[key], j.[value]
FROM OPENJSON(FileMetadata) j
);
这基本上是说:不能有任何没有匹配键/值对的元数据要求。
另一个,有时更有效的版本,同样的事情:
AND EXISTS (SELECT 1
FROM @metadata m
LEFT JOIN OPENJSON(FileMetadata) j
ON j.[key] = m.jsonKey AND j.[value] = m.jsonValue
HAVING COUNT(j.[Key]) = COUNT(*)
);
这就是说:如果我们将所有具有匹配键/值的元数据相加,它们必须与所有元数据的数量相同。
如果你想要一个OR
语义,一个简单的连接就足够了:
AND EXISTS (SELECT 1
FROM @metadata m
JOIN OPENJSON(FileMetadata) j
ON j.[key] = m.jsonKey AND j.[value] = m.jsonValue
);
现在让我们在 C# 中查询它。在正确的位置添加上面的查询。
我倾向于将 TVP 参数构造变成扩展方法,我将把它留给你。
public async Task<IList<FileSystemItemJsonDTO>> GetFileSystemItems(int moduleId, IDictionary<string, string> metadata)
{
var table = new DataTable();
table.Columns.Add("jsonKey", typeof(string));
table.Columns.Add("jsonValue", typeof(string));
foreach (var item in metadata)
table.Add(item.Key, item.Value);
var params = new[] {
new SqlParameter("@metadata", SqlDbType.Structured)
{
Direction = ParameterDirection.Input,
TypeName = "dbo.Metadata",
Value = table
},
new SqlParameter("@moduleId", SqlDbType.Int){Value = moduleId}
};
const string query = @"
SELECT ...
";
return await BIContext.FileSystemItems
.FromSql(MyQuery, params)
.Select(s => new FileSystemItemJsonDTO
{
FileId = s.FileId,
FileName = s.FileName,
FileType = s.FileType,
LastWriteTime = s.LastWriteTime,
FileSystemItemDataId = s.FileSystemItemDataId,
ModuleId = moduleId,
FileMetadata = s.FileMetadata,
FileSize = s.FileSize
})
.ToListAsync().ConfigureAwait(false);
}
推荐阅读
- python - 如果有 '%' 则拆分单元格
- python - 使用过滤表抓取动态页面
- pandas - 如何用 plotly 绘制以下输出?
- git - 由 Git Action 构建不正确的分支触发的 Jenkins 作业
- r - 从有条件的组中选择一行?
- javascript - 未读取 JSON 和图标文件:“对于 CORS 请求,URL 方案必须是“http”或“https”。”
- c# - 无法在 Visual Studio 2019 v16.7.6 中打开 dotnet/runtime 中任何库的 VS 解决方案
- python - Flask Mail Bad Header 错误分配变量
- java - Spring - 将外部 jar 中的异常处理程序用于其他 2 个项目
- windows - 检查Windows脚本中目录中的所有文件