输出表达式值常量
Output Expression Value constant-like
我必须通过 http 将表达式发送到我的后端。此后端知道 enum Fun
但没有对 funs
.
的引用
我的工作是以后端仍然可以反序列化的方式序列化 exp2
有没有办法强制传递枚举值而不是对数组元素的引用?
var funs = new[] { Fun.Low, Fun.High };
Expression<Func<Funky, bool>> exp1 = x => x.Status == Fun.Low;
Expression<Func<Funky, bool>> exp2 = x => x.Status == funs[0];
Console.WriteLine(exp1);
//Expected: x => (Convert(x.Status, Int32) == 1)
Console.WriteLine(exp2);
//Actual output: x => (Convert(x.Status, Int32) == Convert(value(Program+<>c__DisplayClass0_0).funs[0], Int32))
public enum Fun : int {
Low = 1,
Middle = 2,
High = 420
}
public class Funky {
public Fun Status {get;set;} = Fun.High;
}
问题:如何让exp2和exp1的结果一样?
_____________________________________________
背景信息:
exp1
将枚举值序列化为 1
可以被后端正确解释。
exp2
将 funs[0]
序列化为对实际数组元素的引用,如下所示:Convert(value(Program+<>c__DisplayClass0_0).funs[0], Int32)
我也试过 exp3
但这输出的值仍然作为参考而不是常量枚举值。
到目前为止我尝试过的:
//another tests
var afun = new Funky();
var param = Expression.Parameter(typeof(Funky), "x");
var key = afun.GetType().GetProperty("Status");
var lhs = Expression.MakeMemberAccess(param, key);
var rhs = Expression.ArrayIndex(Expression.Constant(funs), Expression.Constant(0));
var body = Expression.Equal(lhs, rhs);
var exp3 = Expression.Lambda<Func<Funky, bool>>(body, param);
Console.WriteLine(exp3);
//x => (x.Status == value(Fun[])[0])
现实生活中的例子:
后端拥有一个将通过 EF-LINQ 查询的数据库。
前端应该向后端发送准确的 LINQ 查询。
假设前端的用户有一个清单,通过它他可以切换他可以从后端查询哪些 Funky 对象:
[x]低
[x] 中间
[_] 高
-> 输出 var funs = new[] { Fun.Low, Fun.Middle };
现在前端必须像这样将表达式放在一起:
Expression<Func<Funky, bool>> exp2 = x => x.Status == funs[0] || x.Status == funs[1];
并在将其发送到后端之前对其进行序列化。
后端无法理解 funs[0]
或 funs[1]
。但是后端知道 enum Fun
并且可以正确反序列化 1
和 2
。
基本上,您需要重写Expression
以删除所有间接并直接使用文字值。这可以用 ExpressionVisitor
来完成——下面显示了一个简化的例子(它处理你的场景)——但是如果你想处理更复杂的事情,比如方法调用(在本地评估),你需要添加更多 override
方法:
public class SimplifyingVisitor : ExpressionVisitor
{
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.NodeType == ExpressionType.ArrayIndex)
{
if (Visit(node.Left) is ConstantExpression left
&& left.Value is Array arr && arr.Rank == 1
&& Visit(node.Right) is ConstantExpression right)
{
var type = left.Type.GetElementType();
switch (right.Value)
{
case int i:
return Expression.Constant(arr.GetValue(i), type);
case long l:
return Expression.Constant(arr.GetValue(l), type);
}
}
}
return base.VisitBinary(node);
}
protected override Expression VisitUnary(UnaryExpression node)
{
if (node.NodeType == ExpressionType.Convert
&& Visit(node.Operand) is ConstantExpression arg)
{
try
{
return Expression.Constant(
Convert.ChangeType(arg.Value, node.Type), node.Type);
}
catch { } //best efforts
}
return base.VisitUnary(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.NodeType == ExpressionType.MemberAccess && Visit(node.Expression) is ConstantExpression target)
{
switch (node.Member)
{
case PropertyInfo property:
return Expression.Constant(property.GetValue(target.Value), property.PropertyType);
case FieldInfo field:
return Expression.Constant(field.GetValue(target.Value), field.FieldType);
}
}
return base.VisitMember(node);
}
}
用法:
var visitor = new SimplifyingVisitor();
exp2 = (Expression<Func<Funky, bool>>)visitor.Visit(exp2);
我必须通过 http 将表达式发送到我的后端。此后端知道 enum Fun
但没有对 funs
.
我的工作是以后端仍然可以反序列化的方式序列化 exp2
有没有办法强制传递枚举值而不是对数组元素的引用?
var funs = new[] { Fun.Low, Fun.High };
Expression<Func<Funky, bool>> exp1 = x => x.Status == Fun.Low;
Expression<Func<Funky, bool>> exp2 = x => x.Status == funs[0];
Console.WriteLine(exp1);
//Expected: x => (Convert(x.Status, Int32) == 1)
Console.WriteLine(exp2);
//Actual output: x => (Convert(x.Status, Int32) == Convert(value(Program+<>c__DisplayClass0_0).funs[0], Int32))
public enum Fun : int {
Low = 1,
Middle = 2,
High = 420
}
public class Funky {
public Fun Status {get;set;} = Fun.High;
}
问题:如何让exp2和exp1的结果一样?
_____________________________________________
背景信息:
exp1
将枚举值序列化为 1
可以被后端正确解释。
exp2
将 funs[0]
序列化为对实际数组元素的引用,如下所示:Convert(value(Program+<>c__DisplayClass0_0).funs[0], Int32)
我也试过 exp3
但这输出的值仍然作为参考而不是常量枚举值。
到目前为止我尝试过的:
//another tests
var afun = new Funky();
var param = Expression.Parameter(typeof(Funky), "x");
var key = afun.GetType().GetProperty("Status");
var lhs = Expression.MakeMemberAccess(param, key);
var rhs = Expression.ArrayIndex(Expression.Constant(funs), Expression.Constant(0));
var body = Expression.Equal(lhs, rhs);
var exp3 = Expression.Lambda<Func<Funky, bool>>(body, param);
Console.WriteLine(exp3);
//x => (x.Status == value(Fun[])[0])
现实生活中的例子:
后端拥有一个将通过 EF-LINQ 查询的数据库。 前端应该向后端发送准确的 LINQ 查询。
假设前端的用户有一个清单,通过它他可以切换他可以从后端查询哪些 Funky 对象: [x]低 [x] 中间 [_] 高
-> 输出 var funs = new[] { Fun.Low, Fun.Middle };
现在前端必须像这样将表达式放在一起:
Expression<Func<Funky, bool>> exp2 = x => x.Status == funs[0] || x.Status == funs[1];
并在将其发送到后端之前对其进行序列化。
后端无法理解 funs[0]
或 funs[1]
。但是后端知道 enum Fun
并且可以正确反序列化 1
和 2
。
基本上,您需要重写Expression
以删除所有间接并直接使用文字值。这可以用 ExpressionVisitor
来完成——下面显示了一个简化的例子(它处理你的场景)——但是如果你想处理更复杂的事情,比如方法调用(在本地评估),你需要添加更多 override
方法:
public class SimplifyingVisitor : ExpressionVisitor
{
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.NodeType == ExpressionType.ArrayIndex)
{
if (Visit(node.Left) is ConstantExpression left
&& left.Value is Array arr && arr.Rank == 1
&& Visit(node.Right) is ConstantExpression right)
{
var type = left.Type.GetElementType();
switch (right.Value)
{
case int i:
return Expression.Constant(arr.GetValue(i), type);
case long l:
return Expression.Constant(arr.GetValue(l), type);
}
}
}
return base.VisitBinary(node);
}
protected override Expression VisitUnary(UnaryExpression node)
{
if (node.NodeType == ExpressionType.Convert
&& Visit(node.Operand) is ConstantExpression arg)
{
try
{
return Expression.Constant(
Convert.ChangeType(arg.Value, node.Type), node.Type);
}
catch { } //best efforts
}
return base.VisitUnary(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.NodeType == ExpressionType.MemberAccess && Visit(node.Expression) is ConstantExpression target)
{
switch (node.Member)
{
case PropertyInfo property:
return Expression.Constant(property.GetValue(target.Value), property.PropertyType);
case FieldInfo field:
return Expression.Constant(field.GetValue(target.Value), field.FieldType);
}
}
return base.VisitMember(node);
}
}
用法:
var visitor = new SimplifyingVisitor();
exp2 = (Expression<Func<Funky, bool>>)visitor.Visit(exp2);