您好我想知道在C#中解析OData $过滤字符串的最佳方法是什么,例如
/ API / organizations?$ filter =“name eq'Facebook'或name eq'Twitter'和订阅者gt'30'”
应归还所有名称为Facebook或Twitter且拥有超过30个订阅者的组织。我已经研究了很多,但找不到任何不围绕WCF的解决方案。我正在考虑使用正则表达式并对它们进行分组,因此我有一个列表 Filter类的结果:
Filter
Resource: Name
Operator: Eq
Value: Facebook
Filter
Resource: Name
Operator: Eq
Value: Twitter
Filter
Resource: Subscribers
Operator: gt
Value: 30
但我对如何处理ANDs / OR感到难过。
答案 0 :(得分:15)
在.NET中,有一个可以为您执行此操作的库。编写自己的正则表达式可能会遗漏一些边缘情况。
使用NuGet,引入Microsoft.Data.OData。然后,你可以这样做:
using Microsoft.Data.OData.Query;
var result = ODataUriParser.ParseFilter(
"name eq 'Facebook' or name eq 'Twitter' and subscribers gt 30",
model,
type);
result
这里将采用代表过滤子句的AST的形式。
(要获取model
和type
输入,您可以使用以下内容解析$元数据文件:
using Microsoft.Data.Edm;
using Microsoft.Data.Edm.Csdl;
IEdmModel model = EdmxReader.Parse(new XmlTextReader(/*stream of your $metadata file*/));
IEdmEntityType type = model.FindType("organisation");
)
答案 1 :(得分:13)
以Jen S所说的为基础,您可以遍历FilterClause返回的AST树。
例如,您可以从控制器的查询选项中检索FilterClause:
public IQueryable<ModelObject> GetModelObjects(ODataQueryOptions<ModelObject> queryOptions)
{
var filterClause = queryOptions.Filter.FilterClause;
然后,您可以使用以下代码(从this article借用)遍历生成的AST树:
var values = new Dictionary<string, object>();
TryNodeValue(queryOptions.Filter.FilterClause.Expression, values);
调用的函数是这样的:
public void TryNodeValue(SingleValueNode node, IDictionary<string, object> values)
{
if (node is BinaryOperatorNode )
{
var bon = (BinaryOperatorNode)node;
var left = bon.Left;
var right = bon.Right;
if (left is ConvertNode)
{
var convLeft = ((ConvertNode)left).Source;
if (convLeft is SingleValuePropertyAccessNode && right is ConstantNode)
ProcessConvertNode((SingleValuePropertyAccessNode)convLeft, right, bon.OperatorKind, values);
else
TryNodeValue(((ConvertNode)left).Source, values);
}
if (left is BinaryOperatorNode)
{
TryNodeValue(left, values);
}
if (right is BinaryOperatorNode)
{
TryNodeValue(right, values);
}
if (right is ConvertNode)
{
TryNodeValue(((ConvertNode)right).Source, values);
}
if (left is SingleValuePropertyAccessNode && right is ConstantNode)
{
ProcessConvertNode((SingleValuePropertyAccessNode)left, right, bon.OperatorKind, values);
}
}
}
public void ProcessConvertNode(SingleValuePropertyAccessNode left, SingleValueNode right, BinaryOperatorKind opKind, IDictionary<string, object> values)
{
if (left is SingleValuePropertyAccessNode && right is ConstantNode)
{
var p = (SingleValuePropertyAccessNode)left;
if (opKind == BinaryOperatorKind.Equal)
{
var value = ((ConstantNode)right).Value;
values.Add(p.Property.Name, value);
}
}
}
然后,您可以浏览列表字典并检索您的值:
if (values != null && values.Count() > 0)
{
// iterate through the filters and assign variables as required
foreach (var kvp in values)
{
switch (kvp.Key.ToUpper())
{
case "COL1":
col1 = kvp.Value.ToString();
break;
case "COL2":
col2 = kvp.Value.ToString();
break;
case "COL3":
col3 = Convert.ToInt32(kvp.Value);
break;
default: break;
}
}
}
这个例子相当简单,因为它只考虑“eq”评估,但就我的目的而言,它运作良好。因人而异。 ;)
答案 2 :(得分:11)
我认为你应该使用访问者模式提供的界面来运行AST。
考虑一下这个代表过滤器的类
public class FilterValue
{
public string ComparisonOperator { get; set; }
public string Value { get; set; }
public string FieldName { get; set; }
public string LogicalOperator { get; set; }
}
那么,我们如何&#34;提取&#34; OData参数附带到您班级的过滤器?
FilterClause对象有一个Expression属性,它是一个继承自QueryNode的SingleValueNode。 QueryNode具有Accept方法,该方法接受QueryNodeVisitor。
public virtual T Accept<T>(QueryNodeVisitor<T> visitor);
是的,所以你必须实现自己的QueryNodeVisitor并做你的事情。下面是一个未完成的示例(我不会覆盖所有可能的访问者)。
public class MyVisitor<TSource> : QueryNodeVisitor<TSource>
where TSource: class
{
List<FilterValue> filterValueList = new List<FilterValue>();
FilterValue current = new FilterValue();
public override TSource Visit(BinaryOperatorNode nodeIn)
{
if(nodeIn.OperatorKind == Microsoft.Data.OData.Query.BinaryOperatorKind.And
|| nodeIn.OperatorKind == Microsoft.Data.OData.Query.BinaryOperatorKind.Or)
{
current.LogicalOperator = nodeIn.OperatorKind.ToString();
}
else
{
current.ComparisonOperator = nodeIn.OperatorKind.ToString();
}
nodeIn.Right.Accept(this);
nodeIn.Left.Accept(this);
return null;
}
public override TSource Visit(SingleValuePropertyAccessNode nodeIn)
{
current.FieldName = nodeIn.Property.Name;
//We are finished, add current to collection.
filterValueList.Add(current);
//Reset current
current = new FilterValue();
return null;
}
public override TSource Visit(ConstantNode nodeIn)
{
current.Value = nodeIn.LiteralText;
return null;
}
}
然后,开火:)
MyVisitor<object> visitor = new MyVisitor<object>();
options.Filter.FilterClause.Expression.Accept(visitor);
当它遍历了树
visitor.filterValueList
应包含所需格式的过滤器。我确定需要做更多的工作,但如果你能够实现这一目标,我想你可以搞清楚。
答案 3 :(得分:5)
使用标记 i 和 x 检查此正则表达式。
(?<Filter>
(?<Resource>.+?)\s+
(?<Operator>eq|ne|gt|ge|lt|le|add|sub|mul|div|mod)\s+
'?(?<Value>.+?)'?
)
(?:
\s*$
|\s+(?:or|and|not)\s+
)
http://regexhero.net/tester/?id=0a26931f-aaa3-4fa0-9fc9-1a67d34c16b3
string strRegex = @"(?<Filter>" +
"\n" + @" (?<Resource>.+?)\s+" +
"\n" + @" (?<Operator>eq|ne|gt|ge|lt|le|add|sub|mul|div|mod)\s+" +
"\n" + @" '?(?<Value>.+?)'?" +
"\n" + @")" +
"\n" + @"(?:" +
"\n" + @" \s*$" +
"\n" + @" |\s+(?:or|and|not)\s+" +
"\n" + @")" +
"\n";
Regex myRegex = new Regex(strRegex, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
string strTargetString = @"name eq 'Facebook' or name eq 'Twitter' and subscribers gt '30'";
string strReplace = @"Filter >> ${Filter}" + "\n" + @" Resource : ${Resource}" + "\n" + @" Operator : ${Operator}" + "\n" + @" Value : ${Value}" + "\n\n";
return myRegex.Replace(strTargetString, strReplace);
Filter >> name eq 'Facebook'
Resource : name
Operator : eq
Value : Facebook
Filter >> name eq 'Twitter'
Resource : name
Operator : eq
Value : Twitter
Filter >> subscribers gt '30'
Resource : subscribers
Operator : gt
Value : 30
为了使资源和运算符具有大写,请使用MatchEvaluator。
但是,不支持使用(
和)
进行分组。如果您希望正则表达式支持,请留下评论。
答案 4 :(得分:0)
感谢@Stinky Buffalo 的回答。 在字典中添加重复键时,我更改了您的代码并解决了错误。
示例:
CreateDate%20gt%202021-05-22T00:00:00Z%20and%20CreateDate%20lt%202021-05-26T00:00:00Z%20
和
BookRequestType%20eq%20%27BusDomestic%27%20or%20BookRequestType%20eq%20%27TrainDomestic%27%20or%20BookRequestType%20eq%20%27FlightDomestic%27%20
以下代码对我来说效果很好:
首先安装 Install-Package Microsoft.Data.OData -Version 5.8.4
软件包。
然后创建名为“ODataHelper”的类,然后复制以下代码:
public static Dictionary<string, object> ODataUriParser(ODataQueryOptions<T> queryOptions)
{
var dictFilters = new Dictionary<string, object>();
TryNodeValue(queryOptions.Filter?.FilterClause?.Expression, dictFilters);
return dictFilters;
}
private static void TryNodeValue(SingleValueNode node, IDictionary<string, object> dictFilters)
{
if (node is null)
return;
if (node is SingleValueFunctionCallNode valueFunction)
{
ParseSingleFunctionNode(valueFunction, dictFilters);
}
if (node is BinaryOperatorNode binaryOperatorNode)
{
var left = binaryOperatorNode.Left;
var right = binaryOperatorNode.Right;
if (left is SingleValuePropertyAccessNode leftNodeRight && right is ConstantNode rightNodeRight)
{
ParseSingleValueNode(leftNodeRight, rightNodeRight, binaryOperatorNode.OperatorKind, dictFilters);
}
switch (left)
{
case ConvertNode node1:
{
var convertLeft = node1.Source;
if (convertLeft is SingleValuePropertyAccessNode leftNodeLeft &&
right is ConstantNode rightNodeLeft)
{
ParseSingleValueNode(
leftNodeLeft,
rightNodeLeft,
binaryOperatorNode.OperatorKind,
dictFilters);
}
else
TryNodeValue(node1.Source, dictFilters);
break;
}
case BinaryOperatorNode:
TryNodeValue(left, dictFilters);
break;
case SingleValueFunctionCallNode functionNode:
ParseSingleFunctionNode(functionNode, dictFilters);
break;
}
switch (right)
{
case BinaryOperatorNode:
TryNodeValue(right, dictFilters);
break;
case ConvertNode convertNode:
TryNodeValue(convertNode.Source, dictFilters);
break;
case SingleValueFunctionCallNode functionNode:
ParseSingleFunctionNode(functionNode, dictFilters);
break;
}
}
}
private static void ParseSingleValueNode(
SingleValuePropertyAccessNode left,
SingleValueNode right,
BinaryOperatorKind operatorKind,
IDictionary<string, object> dictFilters)
{
if (left is SingleValuePropertyAccessNode leftNode && right is ConstantNode rightNode)
{
var key = leftNode.Property.Name;
var value = rightNode.Value;
if (operatorKind is BinaryOperatorKind.GreaterThan
or BinaryOperatorKind.GreaterThanOrEqual
or BinaryOperatorKind.LessThan
or BinaryOperatorKind.LessThanOrEqual)
{
if (!dictFilters.TryAdd(key, value))
{
dictFilters.TryAdd($"{key}_To", value);
}
}
else if (value is ODataEnumValue enumValue)
{
if (dictFilters.TryGetValue(key, out object currentValue))
{
dictFilters[key] = $"{currentValue},{enumValue.Value}";
}
else
{
dictFilters.Add(key, enumValue.Value);
}
}
else
dictFilters.Add(key, value);
}
}
private static void ParseSingleFunctionNode(
SingleValueFunctionCallNode node,
IDictionary<string, object> dictFilters)
{
if (node is SingleValueFunctionCallNode singleNode)
{
var key = (singleNode.Parameters.First() as SingleValuePropertyAccessNode)?.Property.Name;
var value = (singleNode.Parameters.Last() as ConstantNode)?.Value;
if (string.IsNullOrEmpty(Convert.ToString(value)?.Trim()))
return;
dictFilters.TryAdd(key, value);
}
}
对于调用 ODataUriParser 方法,您需要从输入操作中获取值。
从请求 api 中获取 ODataQueryOptions
输入端点操作 => ODataQueryOptions<YourObjectModel> options
然后使用以下代码:
Dictionary<string,object> result = ODataHelper<YourObjectModel>.ODataUriParser(options);