Serialize enum filter values as numbers instead of FullName casts

This commit is contained in:
Atanas Korchev
2026-06-25 14:27:12 +03:00
parent bccdf8c42c
commit d607434cda
2 changed files with 42 additions and 6 deletions

View File

@@ -268,7 +268,9 @@ namespace Radzen.Blazor.Tests
public void Serializes_EnumArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new[] { Status.Active, Status.Inactive }.Intersect(e.Statuses).Any();
Assert.Equal("e => (new [] { (Radzen.Blazor.Tests.Status)0, (Radzen.Blazor.Tests.Status)1 }).Intersect(e.Statuses).Any()", _serializer.Serialize(expr));
// Enum elements serialize as bare numbers; the typed array prefix carries the enum type so the
// parser binds Status[] for Intersect, without per-value casts that force assembly type scanning.
Assert.Equal("e => (new Radzen.Blazor.Tests.Status[] { 0, 1 }).Intersect(e.Statuses).Any()", _serializer.Serialize(expr));
}
[Fact]

View File

@@ -122,7 +122,7 @@ public class ExpressionSerializer : ExpressionVisitor
_sb.Append(", ");
}
Visit(node.Arguments[i]);
VisitMethodArgument(node.Arguments[i]);
}
_sb.Append(")");
@@ -146,7 +146,7 @@ public class ExpressionSerializer : ExpressionVisitor
_sb.Append(", ");
}
Visit(node.Arguments[i]);
VisitMethodArgument(node.Arguments[i]);
}
_sb.Append(")");
@@ -155,6 +155,23 @@ public class ExpressionSerializer : ExpressionVisitor
return node;
}
// FormatValue serializes enums as bare numbers, but a method argument needs its enum type for the
// parser's overload resolution (e.g. DateTime.Parse(s, provider, DateTimeStyles)). Emit the cast here
// (not in FormatValue) so overloads still bind while comparison/array enums stay numeric.
private void VisitMethodArgument(Expression argument)
{
if (argument is ConstantExpression { Value: not null } constant)
{
var type = Nullable.GetUnderlyingType(constant.Type) ?? constant.Type;
if (type.IsEnum)
{
_sb.Append(CultureInfo.InvariantCulture, $"({type.DisplayName(true).Replace("+", ".", StringComparison.Ordinal)})");
}
}
Visit(argument);
}
/// <inheritdoc/>
protected override Expression VisitUnary(UnaryExpression node)
{
@@ -218,8 +235,11 @@ public class ExpressionSerializer : ExpressionVisitor
TimeOnly timeOnly => $"TimeOnly.Parse(\"{timeOnly.ToString("HH:mm:ss", CultureInfo.InvariantCulture)}\", CultureInfo.InvariantCulture)",
Guid guid => $"Guid.Parse(\"{guid.ToString("D", CultureInfo.InvariantCulture)}\")",
IEnumerable enumerable when value is not string => FormatEnumerable(enumerable),
// Emit the underlying numeric value (not a (Namespace.EnumType) cast): the cast forces
// ResolveTypeFromAssemblies (AppDomain.GetTypes) on deserialization, which is trim/AOT hostile.
// The enum type is recovered on parse from the compared member / typed array prefix.
_ => value.GetType().IsEnum
? $"({value.GetType()?.FullName?.Replace("+", ".", StringComparison.Ordinal)})" + Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture).ToString()
? Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture).ToString()
: Convert.ToString(value, CultureInfo.InvariantCulture)
};
}
@@ -236,8 +256,14 @@ public class ExpressionSerializer : ExpressionVisitor
{
var arrayType = enumerable.AsQueryable().ElementType;
// Enum elements are serialized as bare numbers (see FormatValue); emit the typed array prefix so the
// parser binds the array as the enum type (required for Intersect/Except). Nullable elements already
// need the prefix to carry the Nullable<> wrapper.
var underlying = Nullable.GetUnderlyingType(arrayType) ?? arrayType;
var needsTypePrefix = Nullable.GetUnderlyingType(arrayType) != null || underlying.IsEnum;
var items = enumerable.Cast<object>().Select(FormatValue);
return $"new {(Nullable.GetUnderlyingType(arrayType) != null ? arrayType.DisplayName(true).Replace("+", ".", StringComparison.Ordinal) : "")}[] {{ {string.Join(", ", items)} }}";
return $"new {(needsTypePrefix ? arrayType.DisplayName(true).Replace("+", ".", StringComparison.Ordinal) : "")}[] {{ {string.Join(", ", items)} }}";
}
/// <inheritdoc/>
@@ -252,7 +278,15 @@ public class ExpressionSerializer : ExpressionVisitor
_sb.Append("(");
}
_sb.Append("new [] { ");
// Enum elements serialize as bare numbers (see FormatValue); emit the typed array prefix so the
// parser binds the array as the enum type (required for Intersect/Except) instead of int[].
var elementType = node.Type.GetElementType();
var underlying = elementType != null ? Nullable.GetUnderlyingType(elementType) ?? elementType : null;
var typePrefix = underlying != null && (underlying.IsEnum || Nullable.GetUnderlyingType(elementType!) != null)
? elementType!.DisplayName(true).Replace("+", ".", StringComparison.Ordinal)
: "";
_sb.Append(CultureInfo.InvariantCulture, $"new {typePrefix}[] {{ ");
bool first = true;
foreach (var expr in node.Expressions)
{