Skip to content

Commit

Permalink
Add Select extension method for IQueryable.
Browse files Browse the repository at this point in the history
  • Loading branch information
akorchev committed Feb 24, 2025
1 parent ba6ced5 commit 518ad83
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 9 deletions.
11 changes: 11 additions & 0 deletions Radzen.Blazor.Tests/ExpressionParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,17 @@ public void Should_SelectByString()
Assert.Equal("Chai", result.ElementType.GetProperty("ProductName").GetValue(result.FirstOrDefault()));
}

[Fact]
public void Should_SelectByWithUntypedIQueryableString()
{
IQueryable list = new List<OrderDetail> { new OrderDetail { Product = new Product { ProductName = "Chai" } } }.AsQueryable();

var result = DynamicExtensions.Select(list, "Product.ProductName as ProductName");

Assert.Equal("Chai", result.ElementType.GetProperty("ProductName").GetValue(result.FirstOrDefault()));
}


[Fact]
public void Should_SupportDictionaryIndexAccess()
{
Expand Down
20 changes: 15 additions & 5 deletions Radzen.Blazor/DynamicExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,20 @@ public static IOrderedQueryable<T> OrderBy<T>(
/// <summary>
/// Projects each element of a sequence into a collection of property values.
/// </summary>
public static IQueryable Select<T>(
this IQueryable<T> source,
string selector,
object[] parameters = null)
public static IQueryable Select<T>(this IQueryable<T> source, string selector, object[] parameters = null)
{
return source.Select(selector, expression => ExpressionParser.ParseLambda<T>(expression));
}

/// <summary>
/// Projects each element of a sequence into a collection of property values.
/// </summary>
public static IQueryable Select(this IQueryable source, string selector, object[] parameters = null)
{
return source.Select(selector, expression => ExpressionParser.ParseLambda(expression, source.ElementType));
}

private static IQueryable Select(this IQueryable source, string selector, Func<string, LambdaExpression> lambdaCreator)
{
try
{
Expand All @@ -102,7 +112,7 @@ public static IQueryable Select<T>(
return source;
}

var lambda = ExpressionParser.ParseLambda<T>($"it => new {{ {selector} }}");
var lambda = lambdaCreator($"it => new {{ {selector} }}");

return source.Provider.CreateQuery(Expression.Call(typeof(Queryable), nameof(Queryable.Select),
[source.ElementType, lambda.Body.Type], source.Expression, Expression.Quote(lambda)));
Expand Down
30 changes: 26 additions & 4 deletions Radzen.Blazor/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static Type CreateType(string typeName, string[] propertyNames, Type[] pr
}
}

class ExpressionSyntaxVisitor<T> : CSharpSyntaxVisitor<Expression>
class ExpressionSyntaxVisitor : CSharpSyntaxVisitor<Expression>
{
private readonly ParameterExpression parameter;
private readonly Func<string, Type> typeLocator;
Expand Down Expand Up @@ -253,7 +253,7 @@ private Expression VisitArgument(Expression instance, ArgumentSyntax argument)
{
var itemType = GetItemType(instance.Type);

var visitor = new ExpressionSyntaxVisitor<T>(Expression.Parameter(itemType, lambda.Parameter.Identifier.Text), typeLocator);
var visitor = new ExpressionSyntaxVisitor(Expression.Parameter(itemType, lambda.Parameter.Identifier.Text), typeLocator);

return visitor.Visit(lambda);
}
Expand Down Expand Up @@ -399,7 +399,7 @@ public override Expression VisitAnonymousObjectCreationExpression(AnonymousObjec

var propertyNames = properties.Select(p => p.Name).ToArray();
var propertyTypes = properties.Select(p => p.Value.Type).ToArray();
var dynamicType = DynamicTypeFactory.CreateType(typeof(T).Name, propertyNames, propertyTypes);
var dynamicType = DynamicTypeFactory.CreateType(parameter.Type.Name, propertyNames, propertyTypes);

var bindings = properties.Select(p => Expression.Bind(dynamicType.GetProperty(p.Name), p.Value));
return Expression.MemberInit(Expression.New(dynamicType), bindings);
Expand Down Expand Up @@ -499,6 +499,7 @@ public static Expression<Func<T, TResult>> ParseLambda<T, TResult>(string expres
return Expression.Lambda<Func<T, TResult>>(body, parameter);
}


private static (ParameterExpression, Expression) Parse<T>(string expression, Func<string, Type> typeLocator)
{
var syntaxTree = CSharpSyntaxTree.ParseText(expression);
Expand All @@ -509,7 +510,7 @@ private static (ParameterExpression, Expression) Parse<T>(string expression, Fun
throw new ArgumentException("Invalid lambda expression.");
}
var parameter = Expression.Parameter(typeof(T), lambdaExpression.Parameter.Identifier.Text);
var visitor = new ExpressionSyntaxVisitor<T>(parameter, typeLocator);
var visitor = new ExpressionSyntaxVisitor(parameter, typeLocator);
var body = visitor.Visit(lambdaExpression.Body);
return (parameter, body);
}
Expand All @@ -523,4 +524,25 @@ public static LambdaExpression ParseLambda<T>(string expression, Func<string, Ty

return Expression.Lambda(body, parameter);
}

/// <summary>
/// Parses a lambda expression that returns untyped result.
/// </summary>
public static LambdaExpression ParseLambda(string expression, Type type, Func<string, Type> typeLocator = null)
{
var syntaxTree = CSharpSyntaxTree.ParseText(expression);
var root = syntaxTree.GetRoot();
var lambdaExpression = root.DescendantNodes().OfType<SimpleLambdaExpressionSyntax>().FirstOrDefault();

if (lambdaExpression == null)
{
throw new ArgumentException("Invalid lambda expression.");
}

var parameter = Expression.Parameter(type, lambdaExpression.Parameter.Identifier.Text);
var visitor = new ExpressionSyntaxVisitor(parameter, typeLocator);
var body = visitor.Visit(lambdaExpression.Body);

return Expression.Lambda(body, parameter);
}
}

0 comments on commit 518ad83

Please sign in to comment.