Skip to content

Commit 5bbc2cd

Browse files
authored
Merge pull request #117 from DataObjects-NET/6.0-prefetch-entityset-issue
Manual Prefetch of EntitySet items
2 parents 7b70d22 + 54181fe commit 5bbc2cd

File tree

8 files changed

+1962
-436
lines changed

8 files changed

+1962
-436
lines changed

Orm/Xtensive.Orm.Tests/Issues/IssueJira0795_EntitySetPrefetchDoesNotWork.cs

Lines changed: 1321 additions & 0 deletions
Large diffs are not rendered by default.

Orm/Xtensive.Orm/Orm/EntitySetBase.cs

Lines changed: 214 additions & 157 deletions
Large diffs are not rendered by default.

Orm/Xtensive.Orm/Orm/Internals/EntitySetState.cs

Lines changed: 141 additions & 94 deletions
Large diffs are not rendered by default.

Orm/Xtensive.Orm/Orm/Internals/Prefetch/EntitySetTask.cs

Lines changed: 74 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
// Copyright (C) 2003-2010 Xtensive LLC.
2-
// All rights reserved.
3-
// For conditions of distribution and use, see license.
1+
// Copyright (C) 2009-2020 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
44
// Created by: Alexander Nikolaev
55
// Created: 2009.09.09
66

@@ -28,35 +28,33 @@ private struct CacheKey : IEquatable<CacheKey>
2828
{
2929
public readonly FieldInfo ReferencingField;
3030
public readonly int? ItemCountLimit;
31-
private int cachedHashCode;
31+
private readonly int cachedHashCode;
3232

3333
public bool Equals(CacheKey other)
3434
{
35-
return (ItemCountLimit==null) == (other.ItemCountLimit==null)
35+
return (ItemCountLimit == null) == (other.ItemCountLimit == null)
3636
&& Equals(other.ReferencingField, ReferencingField);
3737
}
3838

3939
public override bool Equals(object obj)
4040
{
41-
if (ReferenceEquals(null, obj))
41+
if (ReferenceEquals(null, obj)) {
4242
return false;
43-
if (obj.GetType()!=typeof (CacheKey))
43+
}
44+
if (obj.GetType() != typeof (CacheKey)) {
4445
return false;
46+
}
4547
return Equals((CacheKey) obj);
4648
}
4749

48-
public override int GetHashCode()
49-
{
50-
return cachedHashCode;
51-
}
52-
50+
public override int GetHashCode() => cachedHashCode;
5351

5452
// Constructors
5553

5654
public CacheKey(FieldInfo referencingField, int? itemCountLimit)
5755
{
58-
this.ReferencingField = referencingField;
59-
this.ItemCountLimit = itemCountLimit;
56+
ReferencingField = referencingField;
57+
ItemCountLimit = itemCountLimit;
6058
unchecked {
6159
cachedHashCode = (ReferencingField.GetHashCode()*397)
6260
^ (ItemCountLimit.HasValue ? 1 : 0);
@@ -69,16 +67,15 @@ public CacheKey(FieldInfo referencingField, int? itemCountLimit)
6967
private static readonly object itemsQueryCachingRegion = new object();
7068
private static readonly Parameter<Tuple> ownerParameter = new Parameter<Tuple>(WellKnown.KeyFieldName);
7169
private static readonly Parameter<int> itemCountLimitParameter = new Parameter<int>("ItemCountLimit");
72-
private static readonly MethodInfo getValueMethodDefinition = typeof (Tuple)
73-
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
74-
.Where(method => method.Name=="GetValue" && method.GetParameters().Length == 1
75-
&& method.IsGenericMethodDefinition).Single();
70+
//private static readonly MethodInfo getValueMethodDefinition = typeof (Tuple)
71+
// .GetMethods(BindingFlags.Public | BindingFlags.Instance)
72+
// .Where(method => method.Name=="GetValue" && method.GetParameters().Length == 1
73+
// && method.IsGenericMethodDefinition).Single();
7674

7775
private readonly Key ownerKey;
7876
private readonly bool isOwnerCached;
7977
private readonly PrefetchManager manager;
8078
private QueryTask itemsQueryTask;
81-
private int? cachedHashCode;
8279
private readonly PrefetchFieldDescriptor referencingFieldDescriptor;
8380
private readonly CacheKey cacheKey;
8481

@@ -90,80 +87,96 @@ public CacheKey(FieldInfo referencingField, int? itemCountLimit)
9087

9188
public void RegisterQueryTask()
9289
{
93-
EntitySetState state;
94-
if (isOwnerCached && manager.Owner.LookupState(ownerKey, ReferencingField, out state))
95-
if (state==null || state.IsFullyLoaded)
90+
if (isOwnerCached && manager.Owner.LookupState(ownerKey, ReferencingField, out var state)) {
91+
if (state == null || (state.IsFullyLoaded && !state.ShouldUseForcePrefetch(referencingFieldDescriptor.PrefetchOperationId))) {
9692
return;
93+
}
94+
}
95+
9796
itemsQueryTask = CreateQueryTask();
9897
manager.Owner.Session.RegisterInternalDelayedQuery(itemsQueryTask);
9998
}
10099

101100
public void UpdateCache()
102101
{
103-
if (itemsQueryTask==null)
102+
if (itemsQueryTask == null) {
104103
return;
104+
}
105+
105106
var areToNotifyAboutKeys = !manager.Owner.Session.Domain.Model
106107
.Types[referencingFieldDescriptor.Field.ItemType].IsLeaf;
107108
var reader = manager.Owner.Session.Domain.RecordSetReader;
108109
var records = reader.Read(itemsQueryTask.Result, QueryProvider.Header, manager.Owner.Session);
109110
var entityKeys = new List<Key>(itemsQueryTask.Result.Count);
110-
List<Pair<Key, Tuple>> auxEntities = null;
111111
var association = ReferencingField.Associations.Last();
112-
if (association.AuxiliaryType!=null)
113-
auxEntities = new List<Pair<Key, Tuple>>(itemsQueryTask.Result.Count);
112+
var auxEntities = (association.AuxiliaryType != null)
113+
? new List<Pair<Key, Tuple>>(itemsQueryTask.Result.Count)
114+
: null;
115+
114116
foreach (var record in records) {
115-
for (int i = 0; i < record.Count; i++) {
117+
for (var i = 0; i < record.Count; i++) {
116118
var key = record.GetKey(i);
117-
if (key==null)
119+
if (key == null) {
118120
continue;
121+
}
119122
var tuple = record.GetTuple(i);
120-
if (tuple==null)
123+
if (tuple == null) {
121124
continue;
122-
if (association.AuxiliaryType!=null)
123-
if (i==0)
125+
}
126+
if (association.AuxiliaryType != null) {
127+
if (i == 0) {
124128
auxEntities.Add(new Pair<Key, Tuple>(key, tuple));
129+
}
125130
else {
126131
manager.SaveStrongReference(manager.Owner.UpdateState(key, tuple));
127132
entityKeys.Add(key);
128-
if (areToNotifyAboutKeys)
133+
if (areToNotifyAboutKeys) {
129134
referencingFieldDescriptor.NotifySubscriber(ownerKey, key);
135+
}
130136
}
137+
}
131138
else {
132139
manager.SaveStrongReference(manager.Owner.UpdateState(key, tuple));
133140
entityKeys.Add(key);
134-
if (areToNotifyAboutKeys)
141+
if (areToNotifyAboutKeys) {
135142
referencingFieldDescriptor.NotifySubscriber(ownerKey, key);
143+
}
136144
}
137145
}
138146
}
139-
manager.Owner.UpdateState(ownerKey, ReferencingField,
140-
ItemCountLimit==null || entityKeys.Count < ItemCountLimit, entityKeys, auxEntities);
147+
var updatedState = manager.Owner.UpdateState(ownerKey, ReferencingField,
148+
ItemCountLimit == null || entityKeys.Count < ItemCountLimit, entityKeys, auxEntities);
149+
if (updatedState != null) {
150+
updatedState.SetLastManualPrefetchId(referencingFieldDescriptor.PrefetchOperationId);
151+
}
141152
}
142153

143154
public bool Equals(EntitySetTask other)
144155
{
145-
if (ReferenceEquals(null, other))
156+
if (ReferenceEquals(null, other)) {
146157
return false;
147-
if (ReferenceEquals(this, other))
158+
}
159+
if (ReferenceEquals(this, other)) {
148160
return true;
161+
}
149162
return other.cacheKey.Equals(cacheKey);
150163
}
151164

152165
public override bool Equals(object obj)
153166
{
154-
if (ReferenceEquals(null, obj))
167+
if (ReferenceEquals(null, obj)) {
155168
return false;
156-
if (ReferenceEquals(this, obj))
169+
}
170+
if (ReferenceEquals(this, obj)) {
157171
return true;
158-
if (obj.GetType()!=typeof (EntitySetTask))
172+
}
173+
if (obj.GetType() != typeof (EntitySetTask)) {
159174
return false;
175+
}
160176
return Equals((EntitySetTask) obj);
161177
}
162178

163-
public override int GetHashCode()
164-
{
165-
return cacheKey.GetHashCode();
166-
}
179+
public override int GetHashCode() => cacheKey.GetHashCode();
167180

168181
#region Private / internal methods
169182

@@ -172,8 +185,9 @@ private QueryTask CreateQueryTask()
172185
var parameterContext = new ParameterContext();
173186
using (parameterContext.Activate()) {
174187
ownerParameter.Value = ownerKey.Value;
175-
if (ItemCountLimit != null)
188+
if (ItemCountLimit != null) {
176189
itemCountLimitParameter.Value = ItemCountLimit.Value;
190+
}
177191
object key = new Pair<object, CacheKey>(itemsQueryCachingRegion, cacheKey);
178192
Func<object, object> generator = CreateRecordSetLoadingItems;
179193
var session = manager.Owner.Session;
@@ -190,14 +204,13 @@ private static CompilableProvider CreateRecordSetLoadingItems(object cachingKey)
190204
var primaryTargetIndex = association.TargetType.Indexes.PrimaryIndex;
191205
var resultColumns = new List<int>(primaryTargetIndex.Columns.Count);
192206
ParameterExpression tupleParameter;
193-
CompilableProvider result;
194-
if (association.AuxiliaryType == null)
195-
result = CreateQueryForDirectAssociation(pair, primaryTargetIndex, resultColumns);
196-
else
197-
result = CreateQueryForAssociationViaAuxType(pair, primaryTargetIndex, resultColumns);
207+
var result = association.AuxiliaryType == null
208+
? CreateQueryForDirectAssociation(pair, primaryTargetIndex, resultColumns)
209+
: CreateQueryForAssociationViaAuxType(pair, primaryTargetIndex, resultColumns);
198210
result = result.Select(resultColumns.ToArray());
199-
if (pair.Second.ItemCountLimit != null)
211+
if (pair.Second.ItemCountLimit != null) {
200212
result = result.Take(() => itemCountLimitParameter.Value);
213+
}
201214
return result;
202215
}
203216

@@ -236,26 +249,31 @@ private static CompilableProvider CreateQueryForDirectAssociation(Pair<object, C
236249
private static void AddResultColumnIndexes(ICollection<int> indexes, IndexInfo index,
237250
int columnIndexOffset)
238251
{
239-
for (int i = 0; i < index.Columns.Count; i++) {
252+
for (var i = 0; i < index.Columns.Count; i++) {
240253
var column = index.Columns[i];
241-
if (PrefetchHelper.IsFieldToBeLoadedByDefault(column.Field))
254+
if (PrefetchHelper.IsFieldToBeLoadedByDefault(column.Field)) {
242255
indexes.Add(i + columnIndexOffset);
256+
}
243257
}
244258
}
245259

246260
private static Pair<int>[] GetJoiningColumnIndexes(IndexInfo primaryIndex, IndexInfo associationIndex, bool hasAuxType)
247261
{
248262
var joiningColumns = new Pair<int>[primaryIndex.KeyColumns.Count];
249263
var firstColumnIndex = primaryIndex.Columns.IndexOf(primaryIndex.KeyColumns[0].Key);
250-
for (int i = 0; i < joiningColumns.Length; i++)
251-
if (hasAuxType)
264+
for (var i = 0; i < joiningColumns.Length; i++) {
265+
if (hasAuxType) {
252266
joiningColumns[i] =
253267
new Pair<int>(associationIndex.Columns.IndexOf(associationIndex.ValueColumns[i]),
254268
firstColumnIndex + i);
255-
else
269+
}
270+
else {
256271
joiningColumns[i] =
257272
new Pair<int>(associationIndex.Columns.IndexOf(primaryIndex.KeyColumns[i].Key),
258273
firstColumnIndex + i);
274+
}
275+
}
276+
259277
return joiningColumns;
260278
}
261279

Orm/Xtensive.Orm/Orm/Internals/Prefetch/Fetcher.cs

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
// Copyright (C) 2003-2010 Xtensive LLC.
2-
// All rights reserved.
3-
// For conditions of distribution and use, see license.
1+
// Copyright (C) 2009-2020 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
44
// Created by: Alexander Nikolaev
55
// Created: 2009.10.20
66

@@ -24,10 +24,12 @@ public int ExecuteTasks(IEnumerable<GraphContainer> containers, bool skipPersist
2424
var batchExecuted = 0;
2525
try {
2626
var rootEntityContainers = containers
27-
.Where(container => container.RootEntityContainer!=null)
27+
.Where(container => container.RootEntityContainer != null)
2828
.Select(container => container.RootEntityContainer);
29-
foreach (var container in rootEntityContainers)
29+
foreach (var container in rootEntityContainers) {
3030
AddTask(container);
31+
}
32+
3133
RegisterAllEntityGroupTasks();
3234
RegisterAllEntitySetTasks(containers);
3335

@@ -39,13 +41,16 @@ public int ExecuteTasks(IEnumerable<GraphContainer> containers, bool skipPersist
3941

4042
var referencedEntityContainers = containers
4143
.Select(container => container.ReferencedEntityContainers)
42-
.Where(referencedEntityPrefetchContainers => referencedEntityPrefetchContainers!=null)
44+
.Where(referencedEntityPrefetchContainers => referencedEntityPrefetchContainers != null)
4345
.SelectMany(referencedEntityPrefetchContainers => referencedEntityPrefetchContainers);
44-
foreach (var container in referencedEntityContainers)
46+
foreach (var container in referencedEntityContainers) {
4547
AddTask(container);
48+
}
4649

47-
if (tasks.Count==0)
50+
if (tasks.Count == 0) {
4851
return batchExecuted;
52+
}
53+
4954
RegisterAllEntityGroupTasks();
5055

5156
batchExecuted += manager.Owner.Session.ExecuteInternalDelayedQueries(skipPersist) ? 1 : 0;
@@ -60,33 +65,31 @@ public int ExecuteTasks(IEnumerable<GraphContainer> containers, bool skipPersist
6065

6166
private static void UpdateCacheFromAllEntitySetTasks(IEnumerable<GraphContainer> containers)
6267
{
63-
foreach (var container in containers) {
68+
foreach (var container in containers.Where(c => c.EntitySetTasks != null)) {
6469
var entitySetPrefetchTasks = container.EntitySetTasks;
65-
if (entitySetPrefetchTasks!=null) {
66-
foreach (var entitySetPrefetchTask in entitySetPrefetchTasks)
67-
entitySetPrefetchTask.UpdateCache();
70+
foreach (var entitySetPrefetchTask in entitySetPrefetchTasks) {
71+
entitySetPrefetchTask.UpdateCache();
6872
}
6973
}
7074
}
7175

7276
private static void RegisterAllEntitySetTasks(IEnumerable<GraphContainer> containers)
7377
{
74-
foreach (var container in containers) {
78+
foreach (var container in containers.Where(c => c.EntitySetTasks != null)) {
7579
var entitySetPrefetchTasks = container.EntitySetTasks;
76-
if (entitySetPrefetchTasks!=null) {
77-
foreach (var entitySetPrefetchTask in entitySetPrefetchTasks)
78-
entitySetPrefetchTask.RegisterQueryTask();
80+
foreach (var entitySetPrefetchTask in entitySetPrefetchTasks) {
81+
entitySetPrefetchTask.RegisterQueryTask();
7982
}
8083
}
8184
}
8285

8386
private void AddTask(EntityContainer container)
8487
{
8588
var newTask = container.GetTask();
86-
if (newTask!=null) {
89+
if (newTask != null) {
8790
var existingTask = tasks[newTask];
8891
if (existingTask == null) {
89-
tasks.Add(newTask);
92+
_ = tasks.Add(newTask);
9093
existingTask = newTask;
9194
}
9295
existingTask.AddKey(container.Key, container.ExactType);
@@ -103,8 +106,9 @@ private void UpdateCacheFromAllEntityGroupTasks()
103106

104107
private void RegisterAllEntityGroupTasks()
105108
{
106-
foreach (var task in tasks)
109+
foreach (var task in tasks) {
107110
task.RegisterQueryTasks();
111+
}
108112
}
109113

110114

0 commit comments

Comments
 (0)