Skip to content

Replace slow Enum.TryParse() by dictionary lookup #276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 82 additions & 59 deletions Orm/Xtensive.Orm/Linq/QueryableVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,69 +4,92 @@
// Created by: Alexis Kochetov
// Created: 2009.02.25

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Xtensive.Reflection;

namespace Xtensive.Linq
{
/// <summary>
/// Abstract base visitor that handles methods of <see cref="IQueryable"/> and <see cref="IEnumerable{T}"/> by calling <see cref="VisitQueryableMethod"/>.
/// </summary>
[Serializable]
public abstract class QueryableVisitor : ExpressionVisitor
{
/// <inheritdoc/>
protected override Expression VisitMethodCall(MethodCallExpression mc)
{
if (mc.Arguments.Count > 0 && mc.Arguments[0].Type == WellKnownTypes.String) {
return base.VisitMethodCall(mc);
}

var method = GetQueryableMethod(mc);
if (method == null) {
return base.VisitMethodCall(mc);
}

return VisitQueryableMethod(mc, method.Value);
}

/// <summary>
/// Visits method of <see cref="IQueryable"/> or <see cref="IEnumerable{T}"/>.
/// </summary>
/// <param name="mc">The method call expression.</param>
/// <param name="methodKind">Kind of the method.</param>
protected abstract Expression VisitQueryableMethod(MethodCallExpression mc, QueryableMethodKind methodKind);
namespace Xtensive.Linq;

/// <summary>
/// Parses <see cref="QueryableMethodKind"/> for the specified expression.
/// </summary>
/// <param name="call">A call to process.</param>
/// <returns><see cref="QueryableMethodKind"/> for the specified expression,
/// or null if method is not a LINQ method.</returns>
public static QueryableMethodKind? GetQueryableMethod(MethodCallExpression call)
{
if (call == null) {
return null;
}

var declaringType = call.Method.DeclaringType;
if (declaringType == WellKnownTypes.Queryable || declaringType == WellKnownTypes.Enumerable) {
return ParseQueryableMethodKind(call.Method.Name);
}
/// <summary>
/// Abstract base visitor that handles methods of <see cref="IQueryable"/> and <see cref="IEnumerable{T}"/> by calling <see cref="VisitQueryableMethod"/>.
/// </summary>
[Serializable]
public abstract class QueryableVisitor : ExpressionVisitor
{
private static readonly Dictionary<string, QueryableMethodKind> QueryableMethodKindFromName = new() {
[nameof(Queryable.Aggregate)] = QueryableMethodKind.Aggregate,
[nameof(Queryable.All)] = QueryableMethodKind.All,
[nameof(Queryable.Any)] = QueryableMethodKind.Any,
["AsEnumerable"] = QueryableMethodKind.AsEnumerable,
["AsQueryable"] = QueryableMethodKind.AsQueryable,
[nameof(Queryable.Average)] = QueryableMethodKind.Average,
[nameof(Queryable.Cast)] = QueryableMethodKind.Cast,
[nameof(Queryable.Concat)] = QueryableMethodKind.Concat,
[nameof(Queryable.Contains)] = QueryableMethodKind.Contains,
[nameof(Queryable.Count)] = QueryableMethodKind.Count,
[nameof(Queryable.DefaultIfEmpty)] = QueryableMethodKind.DefaultIfEmpty,
[nameof(Queryable.Distinct)] = QueryableMethodKind.Distinct,
[nameof(Queryable.DistinctBy)] = QueryableMethodKind.DistinctBy,
[nameof(Queryable.ElementAt)] = QueryableMethodKind.ElementAt,
[nameof(Queryable.ElementAtOrDefault)] = QueryableMethodKind.ElementAtOrDefault,
[nameof(Queryable.Except)] = QueryableMethodKind.Except,
[nameof(Queryable.First)] = QueryableMethodKind.First,
[nameof(Queryable.FirstOrDefault)] = QueryableMethodKind.FirstOrDefault,
[nameof(Queryable.GroupBy)] = QueryableMethodKind.GroupBy,
[nameof(Queryable.GroupJoin)] = QueryableMethodKind.GroupJoin,
[nameof(Queryable.Intersect)] = QueryableMethodKind.Intersect,
[nameof(Queryable.Join)] = QueryableMethodKind.Join,
[nameof(Queryable.Last)] = QueryableMethodKind.Last,
[nameof(Queryable.LastOrDefault)] = QueryableMethodKind.LastOrDefault,
[nameof(Queryable.LongCount)] = QueryableMethodKind.LongCount,
[nameof(Queryable.Max)] = QueryableMethodKind.Max,
[nameof(Queryable.Min)] = QueryableMethodKind.Min,
[nameof(Queryable.OfType)] = QueryableMethodKind.OfType,
[nameof(Queryable.OrderBy)] = QueryableMethodKind.OrderBy,
[nameof(Queryable.OrderByDescending)] = QueryableMethodKind.OrderByDescending,
[nameof(Queryable.Reverse)] = QueryableMethodKind.Reverse,
[nameof(Queryable.Select)] = QueryableMethodKind.Select,
[nameof(Queryable.SelectMany)] = QueryableMethodKind.SelectMany,
[nameof(Queryable.SequenceEqual)] = QueryableMethodKind.SequenceEqual,
[nameof(Queryable.Single)] = QueryableMethodKind.Single,
[nameof(Queryable.SingleOrDefault)] = QueryableMethodKind.SingleOrDefault,
[nameof(Queryable.Skip)] = QueryableMethodKind.Skip,
[nameof(Queryable.SkipWhile)] = QueryableMethodKind.SkipWhile,
[nameof(Queryable.Sum)] = QueryableMethodKind.Sum,
[nameof(Queryable.Take)] = QueryableMethodKind.Take,
[nameof(Queryable.TakeWhile)] = QueryableMethodKind.TakeWhile,
[nameof(Queryable.ThenBy)] = QueryableMethodKind.ThenBy,
[nameof(Queryable.ThenByDescending)] = QueryableMethodKind.ThenByDescending,
["ToArray"] = QueryableMethodKind.ToArray,
["ToList"] = QueryableMethodKind.ToList,
[nameof(Queryable.Union)] = QueryableMethodKind.Union,
[nameof(Queryable.Where)] = QueryableMethodKind.Where
};

return null;
}
/// <inheritdoc/>
protected override Expression VisitMethodCall(MethodCallExpression mc) =>
mc.Arguments is var mcArguments
&& (mcArguments.Count > 0 && mcArguments[0].Type == WellKnownTypes.String)
|| !(GetQueryableMethod(mc) is { } method)
? base.VisitMethodCall(mc)
: VisitQueryableMethod(mc, method);

private static QueryableMethodKind? ParseQueryableMethodKind(string methodName)
{
if (Enum.TryParse(methodName, out QueryableMethodKind result)) {
return result;
}
/// <summary>
/// Visits method of <see cref="IQueryable"/> or <see cref="IEnumerable{T}"/>.
/// </summary>
/// <param name="mc">The method call expression.</param>
/// <param name="methodKind">Kind of the method.</param>
protected abstract Expression VisitQueryableMethod(MethodCallExpression mc, QueryableMethodKind methodKind);

return null;
}
}
}
/// <summary>
/// Parses <see cref="QueryableMethodKind"/> for the specified expression.
/// </summary>
/// <param name="call">A call to process.</param>
/// <returns><see cref="QueryableMethodKind"/> for the specified expression,
/// or null if method is not a LINQ method.</returns>
public static QueryableMethodKind? GetQueryableMethod(MethodCallExpression call) =>
call?.Method.DeclaringType is { } declaringType
&& (declaringType == WellKnownTypes.Queryable || declaringType == WellKnownTypes.Enumerable)
&& QueryableMethodKindFromName.TryGetValue(call.Method.Name, out var v)
? v
: null;
}
Loading