Skip to content
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

Make StorageNode.EntityFetchQueryCache per-Domain #361

Merged
merged 14 commits into from
Mar 4, 2025
Merged
4 changes: 2 additions & 2 deletions Orm/Xtensive.Orm/Orm/Attributes/KeyGeneratorKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Xtensive.Orm
/// <summary>
/// Specifies key generator type to use for a particular hierarchy.
/// </summary>
public enum KeyGeneratorKind
public enum KeyGeneratorKind : byte
{
/// <summary>
/// No key generator is provided for hierarchy.
Expand All @@ -35,4 +35,4 @@ public enum KeyGeneratorKind
/// </summary>
Custom = 2
}
}
}
16 changes: 16 additions & 0 deletions Orm/Xtensive.Orm/Orm/Domain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using Xtensive.Orm.Logging;
using Xtensive.Orm.Model;
using Xtensive.Orm.Providers;
using Xtensive.Orm.Rse.Providers;
using Xtensive.Orm.Upgrade;
using Xtensive.Sql;
using Xtensive.Sql.Info;
Expand Down Expand Up @@ -134,6 +135,21 @@ public static Domain Demand()

internal ConcurrentDictionary<Type, System.Linq.Expressions.MethodCallExpression> RootCallExpressionsCache { get; } = new();

/// <summary>
/// Caches uncompiled queries used by <see cref="PrefetchManager"/> to fetch certain entities.
/// </summary>
internal ConcurrentDictionary<RecordSetCacheKey, CompilableProvider> EntityFetchQueryCache { get; } = new();

/// <summary>
/// Caches uncompiled queries used by <see cref="PrefetchManager"/> to fetch <see cref="EntitySet{TItem}"/> content.
/// </summary>
internal ConcurrentDictionary<ItemsQueryCacheKey, CompilableProvider> EntitySetFetchQueryCache { get; } = new();

/// <summary>
/// Caches queries that get references to entities for certain association.
/// </summary>
internal ConcurrentDictionary<AssociationInfo, (CompilableProvider, Parameter<Xtensive.Tuples.Tuple>)> RefsToEntityQueryCache { get; } = new();

internal object UpgradeContextCookie { get; private set; }

internal SqlConnection SingleConnection { get; private set; }
Expand Down
85 changes: 36 additions & 49 deletions Orm/Xtensive.Orm/Orm/DomainBound.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,47 @@
// Created by: Dmitri Maximov
// Created: 2007.08.10

using System;
using Xtensive.Core;

using Xtensive.IoC;
using Xtensive.Orm;

namespace Xtensive.Orm
namespace Xtensive.Orm;

/// <summary>
/// Base class for all objects that are bound to the <see cref="Domain"/> instance.
/// </summary>
public abstract class DomainBound: IContextBound<Domain>
{
/// <summary>
/// Base class for all objects that are bound to the <see cref="Domain"/> instance.
/// Gets <see cref="Domain"/> to which current instance is bound.
/// </summary>
public Domain Domain { get; internal set; }

#region IContextBound<Domain> Members

/// <inheritdoc/>
Domain IContextBound<Domain>.Context => Domain;

#endregion


// Constructors

/// <summary>
/// Initializes a new instance of this class.
/// </summary>
protected DomainBound()
{
}

/// <summary>
/// Initializes a new instance of this class.
/// </summary>
public abstract class DomainBound: IContextBound<Domain>
/// <param name="domain"><see cref="Orm.Domain"/>, to which current instance
/// is bound.</param>
/// <exception cref="ArgumentNullException"><paramref name="domain"/> is <see langword="null" />.</exception>
protected DomainBound(Domain domain)
{
private Domain domain;

/// <summary>
/// Gets <see cref="Domain"/> to which current instance is bound.
/// </summary>
public Domain Domain
{
get { return domain; }
internal set { domain = value; }
}

#region IContextBound<Domain> Members

/// <inheritdoc/>
Domain IContextBound<Domain>.Context
{
get { return domain; }
}

#endregion


// Constructors

/// <summary>
/// Initializes a new instance of this class.
/// </summary>
protected DomainBound()
{
}

/// <summary>
/// Initializes a new instance of this class.
/// </summary>
/// <param name="domain"><see cref="Orm.Domain"/>, to which current instance
/// is bound.</param>
/// <exception cref="ArgumentNullException"><paramref name="domain"/> is <see langword="null" />.</exception>
protected DomainBound(Domain domain)
{
ArgumentNullException.ThrowIfNull(domain);
this.domain = domain;
}
ArgumentNullException.ThrowIfNull(domain);
Domain = domain;
}
}
}
34 changes: 8 additions & 26 deletions Orm/Xtensive.Orm/Orm/Internals/EntitySetTypeState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,16 @@
// Created by: Alexander Nikolaev
// Created: 2009.08.04

using System;
using Xtensive.Tuples;
using Xtensive.Orm.Providers;
using Xtensive.Orm.Rse.Providers;
using Tuple = Xtensive.Tuples.Tuple;
using Xtensive.Tuples.Transform;
using Xtensive.Orm.Rse;

namespace Xtensive.Orm.Internals
{
[Serializable]
internal sealed class EntitySetTypeState
{
public readonly ExecutableProvider SeekProvider;
namespace Xtensive.Orm.Internals;

public readonly MapTransform SeekTransform;

public readonly Func<Tuple, Entity> ItemCtor;

public readonly Func<QueryEndpoint,long> ItemCountQuery;

public EntitySetTypeState(ExecutableProvider seekProvider, MapTransform seekTransform,
Func<Tuple, Entity> itemCtor, Func<QueryEndpoint, long> itemCountQuery)
{
SeekProvider = seekProvider;
SeekTransform = seekTransform;
ItemCtor = itemCtor;
ItemCountQuery = itemCountQuery;
}
}
}
[Serializable]
internal record EntitySetTypeState(
ExecutableProvider SeekProvider,
MapTransform SeekTransform,
Func<Tuple, Entity> ItemCtor,
Func<QueryEndpoint, long> ItemCountQuery
);
23 changes: 10 additions & 13 deletions Orm/Xtensive.Orm/Orm/Internals/Prefetch/EntityGroupTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
// Created by: Alexander Nikolaev
// Created: 2009.10.20

using System;
using System.Collections.Generic;
using System.Linq;
using Xtensive.Collections;
using Xtensive.Core;
using Xtensive.Orm.Model;
Expand All @@ -33,11 +30,17 @@ public override bool Equals(object obj) =>

// Constructors

public RecordSetCacheKey(IReadOnlyList<ColNum> columnIndexes, TypeInfo type, int cachedHashCode)
public RecordSetCacheKey(IReadOnlyList<ColNum> columnIndexes, TypeInfo type)
{
ColumnIndexes = columnIndexes;
Type = type;
this.cachedHashCode = cachedHashCode;

HashCode hashCode = new();
foreach (var columnIndex in columnIndexes) {
hashCode.Add(columnIndex);
}
hashCode.Add(type);
cachedHashCode = hashCode.ToHashCode();
}
}

Expand Down Expand Up @@ -125,7 +128,7 @@ private QueryTask CreateQueryTask(List<Tuple> currentKeySet)
var parameterContext = new ParameterContext();
parameterContext.SetValue(includeParameter, currentKeySet);
var session = manager.Owner.Session;
Provider = session.StorageNode.EntityFetchQueryCache.GetOrAdd(cacheKey, CreateRecordSet);
Provider = manager.Owner.Session.Domain.EntityFetchQueryCache.GetOrAdd(cacheKey, static k => CreateRecordSet(k));
if (session.Domain.TagsEnabled && session.Tags != null) {
foreach (var tag in session.Tags) {
Provider = new TagProvider(Provider, tag);
Expand Down Expand Up @@ -192,13 +195,7 @@ public EntityGroupTask(TypeInfo type, IReadOnlyList<ColNum> columnIndexes, Prefe

this.type = type;
this.manager = manager;
var cachedHashCode = 0;
foreach (var columnIndex in columnIndexes) {
cachedHashCode = unchecked (379 * cachedHashCode + columnIndex);
}

cachedHashCode ^= type.GetHashCode();
cacheKey = new RecordSetCacheKey(columnIndexes, type, cachedHashCode);
cacheKey = new RecordSetCacheKey(columnIndexes, type);
}
}
}
7 changes: 1 addition & 6 deletions Orm/Xtensive.Orm/Orm/Internals/Prefetch/EntitySetTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@
// Created by: Alexander Nikolaev
// Created: 2009.09.09

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Xtensive.Core;
using Xtensive.Orm.Linq;
using Xtensive.Orm.Model;
Expand Down Expand Up @@ -173,7 +168,7 @@ private QueryTask CreateQueryTask()

var session = manager.Owner.Session;
var scope = new CompiledQueryProcessingScope(null, null, parameterContext, false);
QueryProvider = session.StorageNode.EntitySetFetchQueryCache.GetOrAdd(cacheKey, CreateRecordSetLoadingItems);
QueryProvider = session.Domain.EntitySetFetchQueryCache.GetOrAdd(cacheKey, static k => CreateRecordSetLoadingItems(k));
if (session.Domain.TagsEnabled && session.Tags != null) {
foreach (var tag in session.Tags) {
QueryProvider = new TagProvider(QueryProvider, tag);
Expand Down
15 changes: 5 additions & 10 deletions Orm/Xtensive.Orm/Orm/Internals/Prefetch/PrefetchHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,9 @@ public static bool AddColumns(IEnumerable<ColumnInfo> candidateColumns,
return result;
}

public static List<ColNum> GetColumnsToBeLoaded(SortedDictionary<ColNum, ColumnInfo> userColumnIndexes,
TypeInfo type)
{
var result = new List<ColNum>(userColumnIndexes.Count);
result.AddRange(type.Indexes.PrimaryIndex.ColumnIndexMap.System);
result.AddRange(userColumnIndexes.Where(pair => !pair.Value.IsPrimaryKey
&& !pair.Value.IsSystem).Select(pair => pair.Key));
return result;
}
public static IReadOnlyList<ColNum> GetColumnsToBeLoaded(SortedDictionary<ColNum, ColumnInfo> userColumnIndexes, TypeInfo type) =>
type.Indexes.PrimaryIndex.ColumnIndexMap.System
.Concat(userColumnIndexes.Where(pair => !pair.Value.IsPrimaryKey && !pair.Value.IsSystem).Select(pair => pair.Key))
.ToArray();
}
}
}
26 changes: 6 additions & 20 deletions Orm/Xtensive.Orm/Orm/Internals/Prefetch/PrefetchManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,11 @@ public RootContainerCacheKey(TypeInfo type, IEnumerable<PrefetchFieldDescriptor>
}
}

private class RootContainerCacheEntry
{
public readonly RootContainerCacheKey Key;

public readonly SortedDictionary<ColNum, ColumnInfo> Columns;

public readonly IReadOnlyList<ColNum> ColumnsToBeLoaded;


// Constructors

public RootContainerCacheEntry(in RootContainerCacheKey key, SortedDictionary<ColNum, ColumnInfo> columns,
IReadOnlyList<ColNum> columnsToBeLoaded)
{
Key = key;
Columns = columns;
ColumnsToBeLoaded = columnsToBeLoaded;
}
}
private record struct RootContainerCacheEntry(
RootContainerCacheKey Key,
SortedDictionary<ColNum, ColumnInfo> Columns,
IReadOnlyList<ColNum> ColumnsToBeLoaded
);

#endregion

Expand Down Expand Up @@ -257,7 +243,7 @@ public void GetCachedColumnIndexes(TypeInfo type,
{
var cacheKey = new RootContainerCacheKey(type, descriptors);
var cacheEntry = columnsCache[cacheKey, true];
if (cacheEntry == null) {
if (cacheEntry == default) {
columns = PrefetchHelper.GetColumns(ExtractColumns(descriptors),type);
columnsToBeLoaded = PrefetchHelper.GetColumnsToBeLoaded(columns, type);
cacheEntry = new RootContainerCacheEntry(cacheKey, columns, columnsToBeLoaded);
Expand Down
4 changes: 2 additions & 2 deletions Orm/Xtensive.Orm/Orm/Model/InheritanceSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Xtensive.Orm.Model
/// Enumerates all supported 'class to tables mapping' schemes.
/// </summary>
/// <remarks>See M.Fowler - "Patterns of Enterprise Application Architecture".</remarks>
public enum InheritanceSchema
public enum InheritanceSchema : byte
{
/// <summary>
/// Is equal to <see cref="ClassTable"/>.
Expand All @@ -30,4 +30,4 @@ public enum InheritanceSchema
/// </summary>
ConcreteTable = 2
}
}
}
4 changes: 2 additions & 2 deletions Orm/Xtensive.Orm/Orm/PersistenceState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Xtensive.Orm
/// <summary>
/// Defines possible persistence states of the entities.
/// </summary>
public enum PersistenceState
public enum PersistenceState : byte
{
/// <summary>
/// The entity is synchronized with the database (there are no unsaved changes).
Expand All @@ -28,4 +28,4 @@ public enum PersistenceState
/// </summary>
Removed = 3,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public virtual IEnumerable<ReferenceInfo> GetReferencesTo(Entity target, Associa
{
if (association.IsPaired)
return FindReferences(target, association, true);
var (recordSet, parameter) = Session.StorageNode.RefsToEntityQueryCache .GetOrAdd(association, BuildReferencingQuery);
var (recordSet, parameter) = Session.Domain.RefsToEntityQueryCache.GetOrAdd(association, static k => BuildReferencingQuery(k));
var parameterContext = new ParameterContext();
parameterContext.SetValue(parameter, target.Key.Value);
ExecutableProvider executableProvider = Session.Compile(recordSet);
Expand Down
5 changes: 2 additions & 3 deletions Orm/Xtensive.Orm/Orm/Providers/SessionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public abstract partial class SessionHandler : IDisposable, IAsyncDisposable
/// <summary>
/// Gets <see cref="HandlerAccessor"/>.
/// </summary>
protected HandlerAccessor Handlers { get; private set; }
protected HandlerAccessor Handlers => Session.Handlers;

/// <summary>
/// Gets the current <see cref="Session"/>.
Expand Down Expand Up @@ -117,7 +117,6 @@ public virtual void Dispose()
protected SessionHandler(Session session)
{
Session = session;
Handlers = session.Handlers;
}
}
}
}
Loading