C# JSON_值错误:";JSON“值或JSON查询”;必须是字符串文字

C# JSON_值错误:";JSON“值或JSON查询”;必须是字符串文字,c#,json,sql-server-2016,C#,Json,Sql Server 2016,我尝试使用JSON_值和SQL Server 2016。不过,我必须动态构建SQL查询 JSON: 这是我的错误: “JSON_值或JSON_查询”的参数2必须是字符串文字 var sqlParams = new List<SqlParameter>(); StringBuilder sb = new StringBuilder(); // start the initial select query...

我尝试使用JSON_值和SQL Server 2016。不过,我必须动态构建SQL查询

JSON:

这是我的错误:

“JSON_值或JSON_查询”的参数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);
我已尝试过此帖子中的项目:

但它仍然给我同样的错误

我做错了什么

我需要支持2016年和2017年

更新:

我也发现了这个:->

这说明这可能是不可能的

然后我发现:

它说我的版本受支持:

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上的Microsoft Corporation Developer Edition(64位)(内部版本19041:)

更新2:

在下面的响应之后,我找到了适用于SQL server 2016和SQL server 2017的代码

我只是想了解它是否/为什么能够防止SQL注入,因为我没有参数化
JSON\u VALUE()

公共异步任务GetFileSystemEMS(int-moduleId,IDictionary元数据)
{
var sqlParams=新列表();
StringBuilder sb=新的StringBuilder();
//开始初始选择查询。。。
sb.Append(“从dbo.filesystems中选择*,其中”);
int计数器=0;
foreach(元数据中的var项)
{
//只添加一条,如果我们不是第一条记录。。。
如果(计数器!=0)
{
某人附加(“及”);
}
//设置json路径和值项。。。
string valueParam=string.Format(CultureInfo.CurrentCulture,“jsonPathValue{0}”,计数器);
//对于SQL server 2016,JSON_值的第二项必须是字符串文字
sb.AppendFormat(CultureInfo.CurrentCulture,“JSON_值(FileMetadata,$.{0}')=@{1}”,item.Key,valueParam);
//添加我们的参数以协助sql注入
Add(新的SqlParameter(valueParam,string.Format(CultureInfo.CurrentCulture,“{0}”,item.Value));
计数器++;
}
return wait BIContext.filesystemems
.其中(x=>x.ModuleId==ModuleId)
.FromSql(sb.ToString(),sqlParams.ToArray())
.Select(s=>newfilesystememjsondto
{
FileId=s.FileId,
FileName=s.FileName,
FileType=s.FileType,
LastWriteTime=s.LastWriteTime,
filesystememdataid=s.filesystememdataid,
ModuleId=ModuleId,
FileMetadata=s.FileMetadata,
FileSize=s.FileSize
})
.ToListSync().ConfigureAwait(false);
}
正如前面提到的,我们可以使用表值参数和
OPENJSON
重写它

首先创建一个表类型,我建议您仔细考虑确切的列类型和长度:

创建类型元数据表(jsonKey-nvarchar(100)主键,jsonValue-nvarchar(1000)不为空);
查询使用关系划分:

SELECT*——最好不要使用SELECT*,而是指定必要的列
从dbo.filesystemems
其中moduleId=@moduleId
而且不存在(
选择m.jsonKey,m.jsonValue
来自@m元数据
除了
选择j.[键],j.[值]
来自OPENJSON(FileMetadata)j
);
这基本上是说:不能有任何元数据需求没有匹配的键/值对

同一事物的另一个,有时更有效的版本:

并存在(选择1
来自@m元数据
左连接OPENJSON(FileMetadata)j
关于j.[key]=m.jsonKey和j.[value]=m.jsonValue
具有计数(j[键])=COUNT(*)
);
这意味着:如果我们将所有具有匹配键/值的元数据相加,它们必须与所有元数据的数量相同

如果您想要
语义,简单的连接就足够了:

并存在(选择1
来自@m元数据
加入OPENJSON(FileMetadata)j
关于j.[key]=m.jsonKey和j.[value]=m.jsonValue
);

现在让我们用C#来查询这个问题。将上面的查询添加到正确的位置

我倾向于将TVP参数构造作为一种扩展方法,我将把它留给您

公共异步任务GetFileSystemEMS(int-moduleId,IDictionary元数据)
{
var table=新数据表();
table.Columns.Add(“jsonKey”,typeof(string));
table.Columns.Add(“jsonValue”,typeof(string));
foreach(元数据中的var项)
表.Add(item.Key,item.Value);
var params=new[]{
新的SqlParameter(“@metadata”,SqlDbType.Structured)
{
方向=参数方向。输入,
TypeName=“dbo.Metadata”,
值=表
},
新的SqlParameter(“@moduleId”,SqlDbType.Int){Value=moduleId}
};
常量字符串查询=@“
选择。。。
";
return wait BIContext.filesystemems
.FromSql(MyQuery,参数)
.Select(s=>newfilesystememjsondto
{
FileId=s.FileId,
FileName=s.FileName,
FileType=s.FileType,
LastWriteTime=s.LastWriteTime,
filesystememdataid=s.filesystememdataid,
ModuleId=ModuleId,
            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);
SELECT * FROM dbo.FileSystemItems WHERE JSON_VALUE(FileMetadata, @jsonPathParam0) = @jsonPathValue0 AND JSON_VALUE(FileMetadata, @jsonPathParam1) = @jsonPathValue1
        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);
        }