Skip to content

Optimize ItemToTupleConverter: avoid reflection #392

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 2 commits into from
Jun 10, 2025
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
34 changes: 13 additions & 21 deletions Orm/Xtensive.Orm/Core/AliasGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,28 @@
// Created by: Alexis Kochetov
// Created: 2009.02.10

using System.Text;

namespace Xtensive.Core
{
/// <summary>
/// Universal alias generator.
/// </summary>
[Serializable]
public struct AliasGenerator
public struct AliasGenerator(IReadOnlyList<string> prefixSequence, CompositeFormat aliasTemplate)
{
/// <summary>
/// Default alias template. Value is "{0}{1}". Where {0} - template parameter for prefix and {1} - template parameter for suffix.
/// </summary>
public const string DefaultAliasTemplate = "{0}{1}"; // prefix + suffix
public static readonly CompositeFormat DefaultAliasTemplate = CompositeFormat.Parse("{0}{1}"); // prefix + suffix

// prefix "u" is used for user defined aliases renaming, i.e. "alias" -> "ualias".
// should not contain this prefix.
private readonly static string[] DefaultPrefixSequence =
new[]
{
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "v",
"w", "x", "y", "z"
};
private static readonly string[] DefaultPrefixSequence = [
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "v",
"w", "x", "y", "z"
];

private readonly IReadOnlyList<string> prefixSequence;
private readonly string aliasTemplate;
private byte prefixIndex;
private int suffixNumber;

Expand All @@ -38,9 +36,9 @@ public string Next()
{
VerifyState();
var prefix = prefixSequence[prefixIndex++];
var suffix = (suffixNumber > 0) ? suffixNumber.ToString() : string.Empty;
string result = string.Format(aliasTemplate, prefix, suffix);
return result;
return suffixNumber > 0
? string.Format(null, aliasTemplate, prefix, suffixNumber)
: string.Format(null, aliasTemplate, prefix, string.Empty);
}

private void VerifyState()
Expand All @@ -61,7 +59,7 @@ private void VerifyState()
/// Creates generator using specified alias template.
/// </summary>
/// <param name="aliasTemplate">Alias template. Could use two template parameters: {0} - for prefix and {1} for suffix.</param>
public static AliasGenerator Create(string aliasTemplate) => new(DefaultPrefixSequence, aliasTemplate);
public static AliasGenerator Create(CompositeFormat aliasTemplate) => new(DefaultPrefixSequence, aliasTemplate);

/// <summary>
/// Creates generator using specified prefix sequence.
Expand All @@ -74,19 +72,13 @@ private void VerifyState()
/// </summary>
/// <param name="overriddenPrefixes">The overridden prefix sequence.</param>
/// <param name="aliasTemplate">The alias template.</param>
public static AliasGenerator Create(IReadOnlyList<string> overriddenPrefixes, string aliasTemplate) => new(overriddenPrefixes, aliasTemplate);
public static AliasGenerator Create(IReadOnlyList<string> overriddenPrefixes, CompositeFormat aliasTemplate) => new(overriddenPrefixes, aliasTemplate);


// Constructors

public AliasGenerator()
: this (DefaultPrefixSequence, DefaultAliasTemplate)
{}

private AliasGenerator(IReadOnlyList<string> prefixes, string aliasTemplate)
{
prefixSequence = prefixes;
this.aliasTemplate = aliasTemplate;
}
}
}
12 changes: 3 additions & 9 deletions Orm/Xtensive.Orm/Orm/Linq/ItemToTupleConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ namespace Xtensive.Orm.Linq
[Serializable]
internal abstract class ItemToTupleConverter
{
private static readonly Type ItemToTupleConverterType = typeof(ItemToTupleConverter<>);
protected static readonly Type RefOfTType = typeof(Ref<>);

public abstract Expression<Func<ParameterContext, IEnumerable<Tuple>>> GetEnumerable();
Expand All @@ -27,12 +26,7 @@ internal abstract class ItemToTupleConverter

public Expression Expression { get; protected set; }

public static ItemToTupleConverter BuildConverter(Type type, Type storedEntityType, object enumerable, DomainModel model, Expression sourceExpression)
{
return (ItemToTupleConverter) ItemToTupleConverterType
.CachedMakeGenericType(type)
.GetConstructors()[0]
.Invoke(new[] { enumerable, model, sourceExpression, storedEntityType });
}
public static ItemToTupleConverter BuildConverter<TItem>(Type storedEntityType, Func<ParameterContext, IEnumerable<TItem>> enumerable, DomainModel model, Expression sourceExpression) =>
new ItemToTupleConverter<TItem>(enumerable, model, sourceExpression, storedEntityType);
}
}
}
71 changes: 31 additions & 40 deletions Orm/Xtensive.Orm/Orm/Linq/ParameterAccessorFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.

using System;
using System.Linq.Expressions;
using System.Reflection;
using Xtensive.Core;
Expand All @@ -11,53 +10,45 @@
using Xtensive.Reflection;
using ExpressionVisitor = Xtensive.Linq.ExpressionVisitor;

namespace Xtensive.Orm.Linq
{
internal static class ParameterAccessorFactory
{
private static readonly MethodInfo GetParameterValueMethod =
WellKnownOrmTypes.ParameterContext.GetMethod(nameof(ParameterContext.GetValue));
namespace Xtensive.Orm.Linq;

private static readonly ParameterExpression ParameterContextArgument = Expression.Parameter(WellKnownOrmTypes.ParameterContext, "context");
internal static class ParameterAccessorFactory
{
private static readonly MethodInfo GetParameterValueMethod =
WellKnownOrmTypes.ParameterContext.GetMethod(nameof(ParameterContext.GetValue));

private class ParameterAccessorFactoryImpl<T>: ExpressionVisitor
{
private readonly ParameterExpression parameterContextArgument;

public Expression<Func<ParameterContext,T>> BindToParameterContext(Expression parameterExpression)
{
var body = Visit(parameterExpression);
var resultType = typeof(T);
if (resultType != body.Type) {
body = Expression.Convert(body, resultType);
}
return FastExpression.Lambda<Func<ParameterContext, T>>(body, parameterContextArgument);
}
private static readonly ParameterExpression ParameterContextArgument = Expression.Parameter(WellKnownOrmTypes.ParameterContext, "context");

protected override Expression VisitMember(MemberExpression ma)
{
if (string.Equals(nameof(Parameter<T>.Value), ma.Member.Name, StringComparison.Ordinal)
&& WellKnownOrmTypes.Parameter.IsAssignableFrom(ma.Expression.Type)) {
var parameterType = ma.Expression.Type;
var parameterValueType = parameterType.IsGenericType
? parameterType.GetGenericArguments()[0]
: WellKnownTypes.Object;
return Expression.Call(parameterContextArgument,
GetParameterValueMethod.CachedMakeGenericMethod(parameterValueType), ma.Expression);
}

return base.VisitMember(ma);
}
private class ParameterAccessorFactoryImpl<T>(ParameterExpression parameterContextArgument) : ExpressionVisitor
{
public static readonly ParameterAccessorFactoryImpl<T> Instance = new(ParameterContextArgument);

public ParameterAccessorFactoryImpl(ParameterExpression parameterContextArgument)
{
this.parameterContextArgument = parameterContextArgument;
public Expression<Func<ParameterContext,T>> BindToParameterContext(Expression parameterExpression)
{
var body = Visit(parameterExpression);
var resultType = typeof(T);
if (resultType != body.Type) {
body = Expression.Convert(body, resultType);
}
return FastExpression.Lambda<Func<ParameterContext, T>>(body, parameterContextArgument);
}

public static Expression<Func<ParameterContext, T>> CreateAccessorExpression<T>(Expression parameterExpression)
protected override Expression VisitMember(MemberExpression ma)
{
return new ParameterAccessorFactoryImpl<T>(ParameterContextArgument).BindToParameterContext(parameterExpression);
if (string.Equals(nameof(Parameter<T>.Value), ma.Member.Name, StringComparison.Ordinal)
&& WellKnownOrmTypes.Parameter.IsAssignableFrom(ma.Expression.Type)) {
var parameterType = ma.Expression.Type;
var parameterValueType = parameterType.IsGenericType
? parameterType.GetGenericArguments()[0]
: WellKnownTypes.Object;
return Expression.Call(parameterContextArgument,
GetParameterValueMethod.CachedMakeGenericMethod(parameterValueType), ma.Expression);
}

return base.VisitMember(ma);
}
}

public static Expression<Func<ParameterContext, T>> CreateAccessorExpression<T>(Expression parameterExpression) =>
ParameterAccessorFactoryImpl<T>.Instance.BindToParameterContext(parameterExpression);
}
9 changes: 4 additions & 5 deletions Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1787,23 +1787,22 @@ private static Expression MakeBooleanExpression(Expression previous, Expression
};
}

private static ProjectionExpression CreateLocalCollectionProjectionExpression(Type itemType, object value, Translator translator, Expression sourceExpression)
private static ProjectionExpression CreateLocalCollectionProjectionExpression<TItem>(Func<ParameterContext, IEnumerable<TItem>> enumerable, Translator translator, Expression sourceExpression)
{
var storedEntityType = translator.State.TypeOfEntityStoredInKey;
var translatorContext = translator.context;
var itemToTupleConverter = ItemToTupleConverter.BuildConverter(itemType, storedEntityType, value, translatorContext.Model, sourceExpression);
var itemToTupleConverter = ItemToTupleConverter.BuildConverter(storedEntityType, enumerable, translatorContext.Model, sourceExpression);
var tupleDescriptor = itemToTupleConverter.TupleDescriptor;
var columns = tupleDescriptor
Column[] columns = tupleDescriptor
.Select(x => new SystemColumn(translatorContext.GetNextColumnAlias(), 0, x))
.Cast<Column>()
.ToArray(tupleDescriptor.Count);
var rsHeader = new RecordSetHeader(tupleDescriptor, columns);
var rawProvider = new RawProvider(rsHeader, itemToTupleConverter.GetEnumerable());
var recordset = new StoreProvider(rawProvider);
var itemProjector = new ItemProjectorExpression(itemToTupleConverter.Expression, recordset, translatorContext);
if (translator.State.JoinLocalCollectionEntity)
itemProjector = EntityExpressionJoiner.JoinEntities(translator, itemProjector);
return new ProjectionExpression(itemType, itemProjector, TranslatedQuery.EmptyTupleParameterBindings);
return new ProjectionExpression(typeof(TItem), itemProjector, TranslatedQuery.EmptyTupleParameterBindings);
}

private Expression BuildInterfaceExpression(MemberExpression ma)
Expand Down
2 changes: 1 addition & 1 deletion Orm/Xtensive.Orm/Orm/Linq/Translator.Queryable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1777,7 +1777,7 @@ private ProjectionExpression VisitLocalCollectionSequence<TItem>(Expression sequ
sequence = Expression.Call(sequence, type.GetMethod("ToArray"));
}

return CreateLocalCollectionProjectionExpression(typeof(TItem), ParameterAccessorFactory.CreateAccessorExpression<IEnumerable<TItem>>(
return CreateLocalCollectionProjectionExpression<TItem>(ParameterAccessorFactory.CreateAccessorExpression<IEnumerable<TItem>>(
compiledQueryScope is not null ? compiledQueryScope.QueryParameterReplacer.Replace(sequence) : sequence).CachingCompile(), this, sequence);
}

Expand Down
8 changes: 4 additions & 4 deletions Orm/Xtensive.Orm/Orm/Linq/TranslatorContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
// Created by: Alexis Kochetov
// Created: 2009.02.10

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using Xtensive.Core;
Expand All @@ -27,7 +25,9 @@ internal sealed class TranslatorContext
{
private static readonly IReadOnlyList<string> ColumnAliasPrefixes = ["c01umn"];

private AliasGenerator resultAliasGenerator = AliasGenerator.Create("#{0}{1}");
public static readonly CompositeFormat ResultAliasFormat = CompositeFormat.Parse("#{0}{1}");

private AliasGenerator resultAliasGenerator = AliasGenerator.Create(ResultAliasFormat);
private AliasGenerator columnAliasGenerator = AliasGenerator.Create(ColumnAliasPrefixes);
private readonly Dictionary<ParameterExpression, Parameter<Tuple>> tupleParameters = new();
private readonly Dictionary<CompilableProvider, ApplyParameter> applyParameters = new();
Expand Down