Skip to content

Commit e5cd287

Browse files
authored
Merge pull request #255 from DataObjects-NET/master-query-caching-condition-fix
Fix closure type instance caching problem (issue #224)
2 parents d50cc34 + f53e8f1 commit e5cd287

File tree

10 files changed

+285
-148
lines changed

10 files changed

+285
-148
lines changed

Orm/Xtensive.Orm.Tests.Core/Reflection/TypeHelperTest.cs

Lines changed: 46 additions & 3 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) 2007-2022 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: Alex Yakunin
55
// Created: 2007.12.17
66

@@ -14,6 +14,7 @@
1414
using Xtensive.Core;
1515
using Xtensive.Reflection;
1616
using Xtensive.Orm.Tests;
17+
using System.Linq;
1718

1819
namespace Xtensive.Orm.Tests.Core.Reflection
1920
{
@@ -480,5 +481,47 @@ public void GenericIsNullableTest()
480481
Assert.IsFalse(TypeHelper.IsNullable<int>());
481482
Assert.IsFalse(TypeHelper.IsNullable<string>());
482483
}
484+
485+
[Test]
486+
public void IsValueTupleTest()
487+
{
488+
var tupleTypes = new[] {
489+
typeof (ValueTuple<int>),
490+
typeof (ValueTuple<int, int>),
491+
typeof (ValueTuple<int, int, int>),
492+
typeof (ValueTuple<int, int, int, int>),
493+
typeof (ValueTuple<int, int, int, int, int>),
494+
typeof (ValueTuple<int, int, int, int, int, int>),
495+
typeof (ValueTuple<int, int, int, int, int, int, int>),
496+
typeof (ValueTuple<int, int, int, int, int, int, int, int>)
497+
};
498+
499+
var otherTypes = new[] {
500+
typeof (string),
501+
typeof (char),
502+
typeof (bool),
503+
typeof (DateTime),
504+
typeof (TimeSpan),
505+
typeof (Guid),
506+
typeof (TypeCode),
507+
typeof (byte[]),
508+
typeof (Key),
509+
this.GetType()
510+
};
511+
512+
var startingToken = tupleTypes[0].MetadataToken;
513+
514+
Assert.That(
515+
tupleTypes.Select(t => t.MetadataToken - startingToken).SequenceEqual(Enumerable.Range(0, tupleTypes.Length)),
516+
Is.True);
517+
518+
foreach (var type in tupleTypes) {
519+
Assert.IsTrue(type.IsValueTuple());
520+
}
521+
522+
foreach (var type in otherTypes) {
523+
Assert.IsFalse(type.IsValueTuple());
524+
}
525+
}
483526
}
484527
}

Orm/Xtensive.Orm.Tests/Issues/IssueGithub0224_DelayedQueryCapture.cs

Lines changed: 117 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace Xtensive.Orm.Tests.Issues
1919
namespace IssueGithub0224_DelayedQueryCapture_Model
2020
{
2121
[HierarchyRoot]
22-
class Item : Entity
22+
public class Item : Entity
2323
{
2424
[Field, Key]
2525
public int Id { get; private set; }
@@ -32,50 +32,133 @@ class Item : Entity
3232
[Serializable]
3333
public class IssueGithub0224_DelayedQueryCapture : AutoBuildTest
3434
{
35-
public class OtherService
35+
36+
#region Services
37+
38+
public class OtherService1
39+
{
40+
public static volatile int InstanceCount;
41+
42+
public int N;
43+
44+
public OtherService1()
45+
{
46+
_ = Interlocked.Increment(ref InstanceCount);
47+
}
48+
49+
~OtherService1()
50+
{
51+
_ = Interlocked.Decrement(ref InstanceCount);
52+
}
53+
}
54+
55+
public class OtherService2
56+
{
57+
public static volatile int InstanceCount;
58+
59+
public int N;
60+
61+
public OtherService2()
62+
{
63+
_ = Interlocked.Increment(ref InstanceCount);
64+
}
65+
66+
~OtherService2()
67+
{
68+
_ = Interlocked.Decrement(ref InstanceCount);
69+
}
70+
}
71+
72+
public class OtherService3
3673
{
3774
public static volatile int InstanceCount;
3875

3976
public int N;
4077

41-
public OtherService()
78+
public OtherService3()
4279
{
43-
Interlocked.Increment(ref InstanceCount);
80+
_ = Interlocked.Increment(ref InstanceCount);
4481
}
4582

46-
~OtherService()
83+
~OtherService3()
4784
{
48-
Interlocked.Decrement(ref InstanceCount);
85+
_ = Interlocked.Decrement(ref InstanceCount);
4986
}
5087
}
5188

89+
#endregion
5290

5391
protected override DomainConfiguration BuildConfiguration()
5492
{
5593
var config = base.BuildConfiguration();
56-
config.Types.Register(typeof(Item).Assembly, typeof(Item).Namespace);
94+
config.Types.Register(typeof(Item));
5795
return config;
5896
}
5997

6098
[Test]
61-
public void DelayedQueryCapture()
99+
public void DelayedQueryWithIncludeTest()
100+
{
101+
using (var session = Domain.OpenSession())
102+
using (var t = session.OpenTransaction()) {
103+
var item = new Item() { Tag = 10 };
104+
DelayedQueryWithInclude(session);
105+
t.Complete();
106+
}
107+
TestHelper.CollectGarbage(true);
108+
Assert.AreEqual(0, OtherService1.InstanceCount);
109+
}
110+
111+
[Test]
112+
public void DelayedQueryWithContainsTest()
113+
{
114+
using (var session = Domain.OpenSession())
115+
using (var t = session.OpenTransaction()) {
116+
var item = new Item() { Tag = 10 };
117+
DelayedQueryWithContains(session);
118+
t.Complete();
119+
}
120+
121+
TestHelper.CollectGarbage(true);
122+
Assert.AreEqual(0, OtherService2.InstanceCount);
123+
}
124+
125+
[Test]
126+
public void DelayedQueryWithEqualityTest()
62127
{
63128
using (var session = Domain.OpenSession())
64129
using (var t = session.OpenTransaction()) {
65130
var item = new Item() { Tag = 10 };
66-
DelayedQuery(session);
131+
DelayedQueryWithEquality(session);
67132
t.Complete();
68133
}
69-
GC.Collect();
70-
Thread.Sleep(1000);
71-
GC.Collect();
72-
Assert.AreEqual(0, OtherService.InstanceCount);
134+
135+
TestHelper.CollectGarbage(true);
136+
Assert.AreEqual(0, OtherService3.InstanceCount);
137+
}
138+
139+
private void DelayedQueryWithEquality(Session session)
140+
{
141+
var id = 1;
142+
var otherService = new OtherService3();
143+
144+
var items = session.Query.CreateDelayedQuery("ABC", q =>
145+
from t in q.All<Item>()
146+
where t.Id == id
147+
select t).ToArray();
148+
149+
var bb1 = items
150+
.Select(a => new {
151+
a.Id,
152+
A = new {
153+
B = otherService.N == a.Id
154+
},
155+
});
73156
}
74157

75-
private void DelayedQuery(Session session)
158+
private void DelayedQueryWithInclude(Session session)
76159
{
77160
var ids = new[] { 1, 2 };
78-
var otherService = new OtherService();
161+
var otherService = new OtherService1();
79162

80163
var items = session.Query.CreateDelayedQuery(q =>
81164
from t in q.All<Item>()
@@ -90,5 +173,24 @@ where t.Id.In(ids)
90173
},
91174
});
92175
}
176+
177+
private void DelayedQueryWithContains(Session session)
178+
{
179+
var ids = new[] { 1, 2 };
180+
var otherService = new OtherService2();
181+
182+
var items = session.Query.CreateDelayedQuery(q =>
183+
from t in q.All<Item>()
184+
where ids.Contains(t.Id)
185+
select t).ToArray();
186+
187+
var bb1 = items
188+
.Select(a => new {
189+
a.Id,
190+
A = new {
191+
B = otherService.N == a.Id
192+
},
193+
});
194+
}
93195
}
94196
}

Orm/Xtensive.Orm/Orm/Internals/CompiledQueryProcessingScope.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2009-2020 Xtensive LLC.
1+
// Copyright (C) 2009-2022 Xtensive LLC.
22
// This code is distributed under MIT license terms.
33
// See the License.txt file in the project root for more information.
44
// Created by: Alexis Kochetov

Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Created: 2012.01.27
66

77
using System;
8+
using System.Collections.Generic;
89
using System.Linq;
910
using System.Linq.Expressions;
1011
using System.Reflection;
@@ -20,8 +21,6 @@ namespace Xtensive.Orm.Internals
2021
{
2122
internal class CompiledQueryRunner
2223
{
23-
private static readonly Func<FieldInfo, bool> FieldIsSimple = fieldInfo => IsSimpleType(fieldInfo.FieldType);
24-
2524
private readonly Domain domain;
2625
private readonly Session session;
2726
private readonly QueryEndpoint endpoint;
@@ -111,24 +110,24 @@ private DelayedQuery<TElement> CreateDelayedSequenceQuery<TElement>(
111110
private ParameterizedQuery GetScalarQuery<TResult>(
112111
Func<QueryEndpoint, TResult> query, bool executeAsSideEffect, out TResult result)
113112
{
114-
var cacheable = AllocateParameterAndReplacer();
113+
AllocateParameterAndReplacer();
115114

116115
var parameterContext = new ParameterContext(outerContext);
117116
parameterContext.SetValue(queryParameter, queryTarget);
118117
var scope = new CompiledQueryProcessingScope(
119118
queryParameter, queryParameterReplacer, parameterContext, executeAsSideEffect);
119+
120120
using (scope.Enter()) {
121121
result = query.Invoke(endpoint);
122122
}
123123

124124
var parameterizedQuery = (ParameterizedQuery) scope.ParameterizedQuery;
125-
if (parameterizedQuery==null && queryTarget!=null) {
125+
if (parameterizedQuery == null && queryTarget != null) {
126126
throw new NotSupportedException(Strings.ExNonLinqCallsAreNotSupportedWithinQueryExecuteDelayed);
127127
}
128128

129-
if (cacheable) {
130-
PutCachedQuery(parameterizedQuery);
131-
}
129+
PutQueryToCache(parameterizedQuery);
130+
132131
return parameterizedQuery;
133132
}
134133

@@ -140,26 +139,25 @@ private ParameterizedQuery GetSequenceQuery<TElement>(
140139
return parameterizedQuery;
141140
}
142141

143-
var cacheable = AllocateParameterAndReplacer();
142+
AllocateParameterAndReplacer();
144143
var scope = new CompiledQueryProcessingScope(queryParameter, queryParameterReplacer);
145144
using (scope.Enter()) {
146145
var result = query.Invoke(endpoint);
147146
var translatedQuery = endpoint.Provider.Translate(result.Expression);
148147
parameterizedQuery = (ParameterizedQuery) translatedQuery;
149148
}
150149

151-
if (cacheable) {
152-
PutCachedQuery(parameterizedQuery);
153-
}
150+
PutQueryToCache(parameterizedQuery);
151+
154152
return parameterizedQuery;
155153
}
156154

157-
private bool AllocateParameterAndReplacer()
155+
private void AllocateParameterAndReplacer()
158156
{
159157
if (queryTarget == null) {
160158
queryParameter = null;
161159
queryParameterReplacer = new ExtendedExpressionReplacer(e => e);
162-
return true;
160+
return;
163161
}
164162

165163
var closureType = queryTarget.GetType();
@@ -199,31 +197,12 @@ private bool AllocateParameterAndReplacer()
199197
}
200198
return null;
201199
});
202-
203-
return !TypeHelper.IsClosure(closureType)
204-
|| closureType.GetFields().All(FieldIsSimple);
205-
}
206-
207-
private static bool IsSimpleType(Type type)
208-
{
209-
var typeInfo = type.GetTypeInfo();
210-
if (typeInfo.IsGenericType) {
211-
var genericDef = typeInfo.GetGenericTypeDefinition();
212-
return (genericDef == WellKnownTypes.NullableOfT || genericDef.IsAssignableTo(WellKnownTypes.IReadOnlyListOfT))
213-
&& IsSimpleType(typeInfo.GetGenericArguments()[0]);
214-
}
215-
else if (typeInfo.IsArray) {
216-
return IsSimpleType(typeInfo.GetElementType());
217-
}
218-
else {
219-
return typeInfo.IsPrimitive || typeInfo.IsEnum || type == WellKnownTypes.String || type == WellKnownTypes.Decimal;
220-
}
221200
}
222201

223202
private ParameterizedQuery GetCachedQuery() =>
224203
domain.QueryCache.TryGetItem(queryKey, true, out var item) ? item.Second : null;
225204

226-
private void PutCachedQuery(ParameterizedQuery parameterizedQuery) =>
205+
private void PutQueryToCache(ParameterizedQuery parameterizedQuery) =>
227206
domain.QueryCache.Add(new Pair<object, ParameterizedQuery>(queryKey, parameterizedQuery));
228207

229208
private ParameterContext CreateParameterContext(ParameterizedQuery query)

0 commit comments

Comments
 (0)