Skip to content
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
20 changes: 8 additions & 12 deletions Orm/Xtensive.Orm/Orm/Entity.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2007-2020 Xtensive LLC.
// Copyright (C) 2007-2021 Xtensive LLC.
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.
// Created by: Dmitri Maximov
Expand Down Expand Up @@ -66,6 +66,12 @@ public abstract class Entity : Persistent,
ISerializable,
IDeserializationCallback
{
private static readonly Func<(TypeInfo typeInfo, LockMode lockMode, LockBehavior lockBehavior), Session, ExecutableProvider> ExecutableProviderGenerator = ((TypeInfo typeInfo, LockMode lockMode, LockBehavior lockBehavior) t, Session session) =>
session.Compile(t.typeInfo.Indexes.PrimaryIndex.GetQuery()
.Seek(context => context.GetValue(keyParameter))
.Lock(() => t.lockMode, () => t.lockBehavior)
.Select(Array.Empty<int>()));

private static readonly Parameter<Tuple> keyParameter = new Parameter<Tuple>(WellKnown.KeyFieldName);
private readonly bool changeVersionOnSetAttempt;
private EntityState state;
Expand Down Expand Up @@ -265,17 +271,7 @@ public void Lock(LockMode lockMode, LockBehavior lockBehavior)
{
var parameterContext = new ParameterContext();
parameterContext.SetValue(keyParameter, Key.Value);
object key = (TypeInfo, lockMode, lockBehavior);
Func<object, object> generator = tripletObj => {
var triplet = ((TypeInfo, LockMode, LockBehavior)) tripletObj;
var index = triplet.Item1.Indexes.PrimaryIndex;
var query = index.GetQuery()
.Seek(context => context.GetValue(keyParameter))
.Lock(() => triplet.Item2, () => triplet.Item3)
.Select(Array.Empty<int>());
return Session.Compile(query);
};
var source = (ExecutableProvider) Session.StorageNode.InternalQueryCache.GetOrAdd(key, generator);
var source = Session.StorageNode.EntityLockProviderCache.GetOrAdd((TypeInfo, lockMode, lockBehavior), ExecutableProviderGenerator, Session);
using var recordSetReader = source.GetRecordSetReader(Session, parameterContext);
recordSetReader.MoveNext();
}
Expand Down
10 changes: 4 additions & 6 deletions Orm/Xtensive.Orm/Orm/EntitySetBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ public abstract class EntitySetBase : SessionBound,
#if DEBUG
private static readonly string storageTestsAssemblyPrefix = "Xtensive.Orm.Tests";
#endif
private static readonly object entitySetCachingRegion = new object();
private static readonly Parameter<Tuple> keyParameter = new Parameter<Tuple>(WellKnown.KeyFieldName);
internal static readonly Parameter<Entity> ownerParameter = new Parameter<Entity>("Owner");

private static readonly Func<FieldInfo, EntitySetBase, EntitySetTypeState> EntitySetTypeStateFactory = BuildEntitySetTypeState;

private readonly Entity owner;
private readonly CombineTransform auxilaryTypeKeyTransform;
private readonly bool skipOwnerVersionChange;
Expand Down Expand Up @@ -923,14 +924,11 @@ private static Key GetOwnerKey(Persistent owner)
private EntitySetTypeState GetEntitySetTypeState()
{
EnsureOwnerIsNotRemoved();
object key = new Pair<object, FieldInfo>(entitySetCachingRegion, Field);
Func<object, object> generator = k => BuildEntitySetTypeState(k, this);
return (EntitySetTypeState) Session.StorageNode.InternalQueryCache.GetOrAdd(key, generator);
return Session.StorageNode.EntitySetTypeStateCache.GetOrAdd(Field, EntitySetTypeStateFactory, this);
}

private static EntitySetTypeState BuildEntitySetTypeState(object key, EntitySetBase entitySet)
private static EntitySetTypeState BuildEntitySetTypeState(FieldInfo field, EntitySetBase entitySet)
{
var field = ((Pair<object, FieldInfo>) key).Second;
var association = field.Associations.Last();
var query = association.UnderlyingIndex.GetQuery().Seek(context => context.GetValue(keyParameter));
var seek = entitySet.Session.Compile(query);
Expand Down
1 change: 0 additions & 1 deletion Orm/Xtensive.Orm/Orm/Internals/Prefetch/EntityContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ namespace Xtensive.Orm.Internals.Prefetch
[Serializable]
internal abstract class EntityContainer
{
private static readonly object indexSeekCachingRegion = new object();
private static readonly Parameter<Tuple> seekParameter = new Parameter<Tuple>(WellKnown.KeyFieldName);

private SortedDictionary<int, ColumnInfo> columns;
Expand Down
102 changes: 47 additions & 55 deletions Orm/Xtensive.Orm/Orm/Internals/Prefetch/EntityGroupTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,64 +15,74 @@

namespace Xtensive.Orm.Internals.Prefetch
{
[Serializable]
internal sealed class EntityGroupTask : IEquatable<EntityGroupTask>
internal readonly struct RecordSetCacheKey : IEquatable<RecordSetCacheKey>
{
#region Nested classes
public readonly int[] ColumnIndexes;
public readonly TypeInfo Type;
private readonly int cachedHashCode;

private struct CacheKey : IEquatable<CacheKey>
public bool Equals(RecordSetCacheKey other)
{
public readonly int[] ColumnIndexes;
public readonly TypeInfo Type;
private readonly int cachedHashCode;
if (!Type.Equals(other.Type)) {
return false;
}

public bool Equals(CacheKey other)
{
if (!Type.Equals(other.Type)) {
return false;
}
if (ColumnIndexes.Length != other.ColumnIndexes.Length) {
return false;
}

if (ColumnIndexes.Length != other.ColumnIndexes.Length) {
for (var i = ColumnIndexes.Length - 1; i >= 0; i--) {
if (ColumnIndexes[i] != other.ColumnIndexes[i]) {
return false;
}

for (var i = ColumnIndexes.Length - 1; i >= 0; i--) {
if (ColumnIndexes[i] != other.ColumnIndexes[i]) {
return false;
}
}

return true;
}

public override bool Equals(object obj) =>
obj is CacheKey other && Equals(other);
return true;
}

public override int GetHashCode() => cachedHashCode;
public override bool Equals(object obj) =>
obj is RecordSetCacheKey other && Equals(other);

public override int GetHashCode() => cachedHashCode;

// Constructors

public CacheKey(int[] columnIndexes, TypeInfo type, int cachedHashCode)
{
ColumnIndexes = columnIndexes;
Type = type;
this.cachedHashCode = cachedHashCode;
}
}
// Constructors

#endregion
public RecordSetCacheKey(int[] columnIndexes, TypeInfo type, int cachedHashCode)
{
ColumnIndexes = columnIndexes;
Type = type;
this.cachedHashCode = cachedHashCode;
}
}

[Serializable]
internal sealed class EntityGroupTask : IEquatable<EntityGroupTask>
{
private const int MaxKeyCountInOneStatement = 40;
private static readonly object recordSetCachingRegion = new object();
private static readonly Parameter<IEnumerable<Tuple>> includeParameter =
new Parameter<IEnumerable<Tuple>>("Keys");

private static readonly Func<RecordSetCacheKey, CompilableProvider> CreateRecordSet = cachingKey => {
var selectedColumnIndexes = cachingKey.ColumnIndexes;
var primaryIndex = cachingKey.Type.Indexes.PrimaryIndex;
var keyColumnsCount = primaryIndex.KeyColumns.Count;
var keyColumnIndexes = new int[keyColumnsCount];
foreach (var index in Enumerable.Range(0, keyColumnsCount)) {
keyColumnIndexes[index] = index;
}

var columnCollectionLength = primaryIndex.Columns.Count;
return primaryIndex.GetQuery().Include(IncludeAlgorithm.ComplexCondition,
true, context => context.GetValue(includeParameter), $"includeColumnName-{Guid.NewGuid()}",
keyColumnIndexes).Filter(t => t.GetValue<bool>(columnCollectionLength)).Select(selectedColumnIndexes);
};

private Dictionary<Key, bool> keys;
private readonly TypeInfo type;
private readonly PrefetchManager manager;
private List<QueryTask> queryTasks;
private readonly CacheKey cacheKey;
private readonly RecordSetCacheKey cacheKey;

public CompilableProvider Provider { get; private set; }

Expand Down Expand Up @@ -132,30 +142,12 @@ private QueryTask CreateQueryTask(List<Tuple> currentKeySet)
{
var parameterContext = new ParameterContext();
parameterContext.SetValue(includeParameter, currentKeySet);
object key = new Pair<object, CacheKey>(recordSetCachingRegion, cacheKey);
Func<object, object> generator = CreateRecordSet;
var session = manager.Owner.Session;
Provider = (CompilableProvider) session.StorageNode.InternalQueryCache.GetOrAdd(key, generator);
Provider = session.StorageNode.EntityFetchQueryCache.GetOrAdd(cacheKey, CreateRecordSet);
var executableProvider = session.Compile(Provider);
return new QueryTask(executableProvider, session.GetLifetimeToken(), parameterContext);
}

private static CompilableProvider CreateRecordSet(object cachingKey)
{
var pair = (Pair<object, CacheKey>) cachingKey;
var selectedColumnIndexes = pair.Second.ColumnIndexes;
var keyColumnsCount = pair.Second.Type.Indexes.PrimaryIndex.KeyColumns.Count;
var keyColumnIndexes = new int[keyColumnsCount];
foreach (var index in Enumerable.Range(0, keyColumnsCount)) {
keyColumnIndexes[index] = index;
}

var columnCollectionLength = pair.Second.Type.Indexes.PrimaryIndex.Columns.Count;
return pair.Second.Type.Indexes.PrimaryIndex.GetQuery().Include(IncludeAlgorithm.ComplexCondition,
true, context => context.GetValue(includeParameter), $"includeColumnName-{Guid.NewGuid()}",
keyColumnIndexes).Filter(t => t.GetValue<bool>(columnCollectionLength)).Select(selectedColumnIndexes);
}

private void PutLoadedStatesInCache(IEnumerable<Tuple> queryResult, EntityDataReader reader,
HashSet<Key> foundedKeys)
{
Expand Down Expand Up @@ -221,7 +213,7 @@ public EntityGroupTask(TypeInfo type, int[] columnIndexes, PrefetchManager manag
}

cachedHashCode ^= type.GetHashCode();
cacheKey = new CacheKey(columnIndexes, type, cachedHashCode);
cacheKey = new RecordSetCacheKey(columnIndexes, type, cachedHashCode);
}
}
}
97 changes: 44 additions & 53 deletions Orm/Xtensive.Orm/Orm/Internals/Prefetch/EntitySetTask.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2009-2020 Xtensive LLC.
// Copyright (C) 2009-2021 Xtensive LLC.
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.
// Created by: Alexander Nikolaev
Expand All @@ -19,53 +19,61 @@

namespace Xtensive.Orm.Internals.Prefetch
{
[Serializable]
internal sealed class EntitySetTask : IEquatable<EntitySetTask>
internal readonly struct ItemsQueryCacheKey : IEquatable<ItemsQueryCacheKey>
{
#region Nested classes
public readonly FieldInfo ReferencingField;
public readonly int? ItemCountLimit;
private readonly int cachedHashCode;

private struct CacheKey : IEquatable<CacheKey>
public bool Equals(ItemsQueryCacheKey other)
{
public readonly FieldInfo ReferencingField;
public readonly int? ItemCountLimit;
private readonly int cachedHashCode;

public bool Equals(CacheKey other)
{
return (ItemCountLimit == null) == (other.ItemCountLimit == null)
&& Equals(other.ReferencingField, ReferencingField);
}
return (ItemCountLimit == null) == (other.ItemCountLimit == null)
&& Equals(other.ReferencingField, ReferencingField);
}

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

public override int GetHashCode() => cachedHashCode;
public override int GetHashCode() => cachedHashCode;

// Constructors
// Constructors

public CacheKey(FieldInfo referencingField, int? itemCountLimit)
{
ReferencingField = referencingField;
ItemCountLimit = itemCountLimit;
unchecked {
cachedHashCode = (ReferencingField.GetHashCode()*397)
^ (ItemCountLimit.HasValue ? 1 : 0);
}
public ItemsQueryCacheKey(FieldInfo referencingField, int? itemCountLimit)
{
ReferencingField = referencingField;
ItemCountLimit = itemCountLimit;
unchecked {
cachedHashCode = (ReferencingField.GetHashCode() * 397)
^ (ItemCountLimit.HasValue ? 1 : 0);
}
}
}

#endregion

private static readonly object itemsQueryCachingRegion = new object();
[Serializable]
internal sealed class EntitySetTask : IEquatable<EntitySetTask>
{
private static readonly Parameter<Tuple> ownerParameter = new Parameter<Tuple>(WellKnown.KeyFieldName);
private static readonly Parameter<int> itemCountLimitParameter = new Parameter<int>("ItemCountLimit");

private static readonly Func<ItemsQueryCacheKey, CompilableProvider> CreateRecordSetLoadingItems = cachingKey => {
var association = cachingKey.ReferencingField.Associations.Last();
var primaryTargetIndex = association.TargetType.Indexes.PrimaryIndex;
var resultColumns = new List<int>(primaryTargetIndex.Columns.Count);
var result = association.AuxiliaryType == null
? CreateQueryForDirectAssociation(cachingKey, primaryTargetIndex, resultColumns)
: CreateQueryForAssociationViaAuxType(cachingKey, primaryTargetIndex, resultColumns);
result = result.Select(resultColumns);
if (cachingKey.ItemCountLimit != null)
result = result.Take(context => context.GetValue(itemCountLimitParameter));
return result;
};

private readonly Key ownerKey;
private readonly bool isOwnerCached;
private readonly PrefetchManager manager;
private QueryTask itemsQueryTask;
private readonly PrefetchFieldDescriptor referencingFieldDescriptor;
private readonly CacheKey cacheKey;
private readonly ItemsQueryCacheKey cacheKey;

public FieldInfo ReferencingField {get { return referencingFieldDescriptor.Field; } }

Expand Down Expand Up @@ -166,36 +174,19 @@ private QueryTask CreateQueryTask()
parameterContext.SetValue(itemCountLimitParameter, ItemCountLimit.Value);
}

object key = new Pair<object, CacheKey>(itemsQueryCachingRegion, cacheKey);
Func<object, object> generator = CreateRecordSetLoadingItems;
var session = manager.Owner.Session;
var scope = new CompiledQueryProcessingScope(null, null, parameterContext, false);
QueryProvider = (CompilableProvider) session.StorageNode.InternalQueryCache.GetOrAdd(key, generator);
QueryProvider = session.StorageNode.EntitySetFetchQueryCache.GetOrAdd(cacheKey, CreateRecordSetLoadingItems);
ExecutableProvider executableProvider;
using (scope.Enter()) {
executableProvider = session.Compile(QueryProvider);
}
return new QueryTask(executableProvider, session.GetLifetimeToken(), parameterContext);
}

private static CompilableProvider CreateRecordSetLoadingItems(object cachingKey)
{
var pair = (Pair<object, CacheKey>) cachingKey;
var association = pair.Second.ReferencingField.Associations.Last();
var primaryTargetIndex = association.TargetType.Indexes.PrimaryIndex;
var resultColumns = new List<int>(primaryTargetIndex.Columns.Count);
var result = association.AuxiliaryType == null
? CreateQueryForDirectAssociation(pair, primaryTargetIndex, resultColumns)
: CreateQueryForAssociationViaAuxType(pair, primaryTargetIndex, resultColumns);
result = result.Select(resultColumns);
if (pair.Second.ItemCountLimit != null)
result = result.Take(context => context.GetValue(itemCountLimitParameter));
return result;
}

private static CompilableProvider CreateQueryForAssociationViaAuxType(Pair<object, CacheKey> pair, IndexInfo primaryTargetIndex, List<int> resultColumns)
private static CompilableProvider CreateQueryForAssociationViaAuxType(in ItemsQueryCacheKey cachingKey, IndexInfo primaryTargetIndex, List<int> resultColumns)
{
var association = pair.Second.ReferencingField.Associations.Last();
var association = cachingKey.ReferencingField.Associations.Last();
var associationIndex = association.UnderlyingIndex;
var joiningColumns = GetJoiningColumnIndexes(primaryTargetIndex, associationIndex,
association.AuxiliaryType != null);
Expand All @@ -214,10 +205,10 @@ private static CompilableProvider CreateQueryForAssociationViaAuxType(Pair<objec
.Join(primaryTargetIndex.GetQuery(), joiningColumns);
}

private static CompilableProvider CreateQueryForDirectAssociation(Pair<object, CacheKey> pair, IndexInfo primaryTargetIndex, List<int> resultColumns)
private static CompilableProvider CreateQueryForDirectAssociation(in ItemsQueryCacheKey cachingKey, IndexInfo primaryTargetIndex, List<int> resultColumns)
{
AddResultColumnIndexes(resultColumns, primaryTargetIndex, 0);
var association = pair.Second.ReferencingField.Associations.Last();
var association = cachingKey.ReferencingField.Associations.Last();
var field = association.Reversed.OwnerField;
var keyColumnTypes = field.Columns.Select(column => column.ValueType).ToList();
return primaryTargetIndex
Expand Down Expand Up @@ -273,7 +264,7 @@ public EntitySetTask(Key ownerKey, PrefetchFieldDescriptor referencingFieldDescr
this.isOwnerCached = isOwnerCached;
ItemCountLimit = referencingFieldDescriptor.EntitySetItemCountLimit;
this.manager = manager;
cacheKey = new CacheKey(ReferencingField, ItemCountLimit);
cacheKey = new ItemsQueryCacheKey(ReferencingField, ItemCountLimit);
}
}
}
Loading