Skip to content

Optimization: Convert QueryEndpoint to readonly struct #388

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 8 commits into from
May 30, 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
74 changes: 36 additions & 38 deletions Orm/Xtensive.Orm/Orm/QueryEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,29 @@ namespace Xtensive.Orm
/// and finally, resolve <see cref="Key"/>s to <see cref="Entity">entities</see>.
/// </summary>
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public sealed class QueryEndpoint
public readonly struct QueryEndpoint : IEquatable<QueryEndpoint>
{
private readonly Session session;

/// <summary>
/// Gets outer <see cref="QueryEndpoint"/>.
/// For root <see cref="QueryEndpoint"/> returns <see langword="null"/>.
/// </summary>
public QueryEndpoint Outer { get; private set; }

/// <summary>
/// Gets <see cref="IQueryProvider"/> implementation
/// for this session.
/// </summary>
public QueryProvider Provider { get; }

private Session Session => Provider.Session;

/// <summary>
/// Gets <see cref="IQueryRootBuilder"/> associated with this instance.
/// If <see cref="IQueryRootBuilder"/> is not set for this instance
/// returns <see langword="null"/>.
/// </summary>
public IQueryRootBuilder RootBuilder { get; private set; }
public IQueryRootBuilder RootBuilder { get; }

public bool Equals(QueryEndpoint other) =>
Provider == other.Provider && RootBuilder == other.RootBuilder;

public override bool Equals(object obj) => obj is QueryEndpoint other && Equals(other);

public override int GetHashCode() => HashCode.Combine(Provider, RootBuilder);

/// <summary>
/// The "starting point" for any LINQ query -
Expand Down Expand Up @@ -256,7 +257,7 @@ public IQueryable<FullTextMatch<T>> ContainsTable<T>(

/// <summary>
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="key"/>
/// in the current <see cref="session"/>.
/// in the current <see cref="Session"/>.
/// </summary>
/// <param name="key">The key to resolve.</param>
/// <returns>
Expand All @@ -277,7 +278,7 @@ public Entity Single(Key key)

/// <summary>
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="key"/>
/// in the current <see cref="session"/>.
/// in the current <see cref="Session"/>.
/// </summary>
/// <param name="key">The key to resolve.</param>
/// <returns>
Expand All @@ -298,7 +299,7 @@ public async ValueTask<Entity> SingleAsync(Key key, CancellationToken ct = defau

/// <summary>
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="key"/>
/// in the current <see cref="session"/>.
/// in the current <see cref="Session"/>.
/// </summary>
/// <param name="key">The key to resolve.</param>
/// <returns>
Expand All @@ -311,6 +312,7 @@ public Entity SingleOrDefault(Key key)
if (key == null) {
return null;
}
var session = Session;
using var tx = session.OpenAutoTransaction();
EntityState state;
if (!session.LookupStateInCache(key, out state)) {
Expand Down Expand Up @@ -338,7 +340,7 @@ public Entity SingleOrDefault(Key key)

/// <summary>
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="key"/>
/// in the current <see cref="session"/>.
/// in the current <see cref="Session"/>.
/// </summary>
/// <param name="key">The key to resolve.</param>
/// <returns>
Expand All @@ -350,7 +352,8 @@ public async ValueTask<Entity> SingleOrDefaultAsync(Key key, CancellationToken c
if (key == null) {
return null;
}
using var tx = session.OpenAutoTransaction();
var session = Session;
await using var tx = session.OpenAutoTransaction();
EntityState state;
if (!session.LookupStateInCache(key, out state)) {
if (session.IsDebugEventLoggingEnabled) {
Expand All @@ -377,7 +380,7 @@ public async ValueTask<Entity> SingleOrDefaultAsync(Key key, CancellationToken c

/// <summary>
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="key"/>
/// in the current <see cref="session"/>.
/// in the current <see cref="Session"/>.
/// </summary>
/// <typeparam name="T">Type of the entity.</typeparam>
/// <param name="key">The key to resolve.</param>
Expand All @@ -393,7 +396,7 @@ public T Single<T>(Key key)

/// <summary>
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="keyValues"/>
/// in the current <see cref="session"/>.
/// in the current <see cref="Session"/>.
/// </summary>
/// <typeparam name="T">Type of the entity.</typeparam>
/// <param name="keyValues">Key values.</param>
Expand All @@ -409,7 +412,7 @@ public T Single<T>(params object[] keyValues)

/// <summary>
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="key"/>
/// in the current <see cref="session"/>.
/// in the current <see cref="Session"/>.
/// </summary>
/// <typeparam name="T">Type of the entity.</typeparam>
/// <param name="key">The key to resolve.</param>
Expand All @@ -424,7 +427,7 @@ [CanBeNull] public T SingleOrDefault<T>(Key key)

/// <summary>
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="keyValues"/>
/// in the current <see cref="session"/>.
/// in the current <see cref="Session"/>.
/// </summary>
/// <typeparam name="T">Type of the entity.</typeparam>
/// <param name="keyValues">Key values.</param>
Expand All @@ -439,7 +442,7 @@ [CanBeNull] public T SingleOrDefault<T>(params object[] keyValues)

/// <summary>
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="key"/>
/// in the current <see cref="session"/>.
/// in the current <see cref="Session"/>.
/// </summary>
/// <typeparam name="T">Type of the entity.</typeparam>
/// <param name="key">The key to resolve.</param>
Expand All @@ -452,7 +455,7 @@ public async ValueTask<T> SingleAsync<T>(Key key, CancellationToken ct = default

/// <summary>
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="keyValues"/>
/// in the current <see cref="session"/>.
/// in the current <see cref="Session"/>.
/// </summary>
/// <typeparam name="T">Type of the entity.</typeparam>
/// <param name="keyValues">Key values.</param>
Expand All @@ -468,7 +471,7 @@ public async ValueTask<T> SingleAsync<T>(object key1, object key2, CancellationT

/// <summary>
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="key"/>
/// in the current <see cref="session"/>.
/// in the current <see cref="Session"/>.
/// </summary>
/// <typeparam name="T">Type of the entity.</typeparam>
/// <param name="key">The key to resolve.</param>
Expand All @@ -480,7 +483,7 @@ public async ValueTask<T> SingleOrDefaultAsync<T>(Key key, CancellationToken ct

/// <summary>
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="keyValues"/>
/// in the current <see cref="session"/>.
/// in the current <see cref="Session"/>.
/// </summary>
/// <typeparam name="T">Type of the entity.</typeparam>
/// <param name="keyValues">Key values.</param>
Expand All @@ -501,7 +504,7 @@ public async ValueTask<T> SingleOrDefaultAsync<T>(object key1, object key2, Canc
public PrefetchQuery<T> Many<T>(IEnumerable<Key> keys)
where T : class, IEntity
{
return new PrefetchQuery<T>(session, keys);
return new PrefetchQuery<T>(Session, keys);
}

/// <summary>
Expand All @@ -516,16 +519,13 @@ public PrefetchQuery<T> Many<T, TElement>(IEnumerable<TElement> keys)
where T : class, IEntity
{
var elementType = typeof (TElement);
Func<TElement, Key> selector;
if (elementType==WellKnownTypes.ObjectArray) {
selector = e => Key.Create(session.Domain, session.StorageNodeId, typeof (T), TypeReferenceAccuracy.BaseType, (object[]) (object) e);
}
else if (WellKnownOrmTypes.Tuple.IsAssignableFrom(elementType)) {
selector = e => Key.Create(session.Domain, session.StorageNodeId, typeof (T), TypeReferenceAccuracy.BaseType, (Tuple) (object) e);
}
else {
selector = e => Key.Create(session.Domain, session.StorageNodeId, typeof (T), TypeReferenceAccuracy.BaseType, new object[] {e});
}
var session = Session;
Func<TElement, Key> selector =
elementType == WellKnownTypes.ObjectArray
? e => Key.Create(session.Domain, session.StorageNodeId, typeof(T), TypeReferenceAccuracy.BaseType, (object[]) (object) e)
: WellKnownOrmTypes.Tuple.IsAssignableFrom(elementType)
? e => Key.Create(session.Domain, session.StorageNodeId, typeof(T), TypeReferenceAccuracy.BaseType, (Tuple) (object) e)
: e => Key.Create(session.Domain, session.StorageNodeId, typeof(T), TypeReferenceAccuracy.BaseType, new object[] { e });

return new PrefetchQuery<T>(session, keys.Select(selector));
}
Expand Down Expand Up @@ -968,14 +968,15 @@ private Key GetKeyByValues<T>(object[] keyValues)
return entity.Key;
}
}
var session = Session;
return Key.Create(session.Domain, session.StorageNodeId, typeof(T), TypeReferenceAccuracy.BaseType, keyValues);
}

private Expression BuildRootExpression(Type elementType)
{
return RootBuilder!=null
? RootBuilder.BuildRootExpression(elementType)
: session.Domain.RootCallExpressionsCache.GetOrAdd(elementType, (t) => Expression.Call(null, WellKnownMembers.Query.All.MakeGenericMethod(t)));
: Session.Domain.RootCallExpressionsCache.GetOrAdd(elementType, (t) => Expression.Call(null, WellKnownMembers.Query.All.MakeGenericMethod(t)));
}

private static void ThrowKeyNotFoundException(Key key) =>
Expand All @@ -990,15 +991,12 @@ internal QueryEndpoint(QueryProvider provider)
{
ArgumentNullException.ThrowIfNull(provider);
Provider = provider;
session = provider.Session;
}

internal QueryEndpoint(QueryEndpoint outerEndpoint, IQueryRootBuilder queryRootBuilder)
{
ArgumentNullException.ThrowIfNull(outerEndpoint);
ArgumentNullException.ThrowIfNull(queryRootBuilder);
Provider = outerEndpoint.Provider;
session = outerEndpoint.session;
RootBuilder = queryRootBuilder;
}
}
Expand Down
2 changes: 1 addition & 1 deletion Version.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<PropertyGroup>
<DoVersion>7.2.181</DoVersion>
<DoVersion>7.2.184</DoVersion>
<DoVersionSuffix>servicetitan</DoVersionSuffix>
</PropertyGroup>

Expand Down
Loading