diff --git a/Orm/Xtensive.Orm.Tests/Issues/IssueGithub0224_DelayedQueryCapture.cs b/Orm/Xtensive.Orm.Tests/Issues/IssueGithub0224_DelayedQueryCapture.cs new file mode 100755 index 0000000000..47a71904d9 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Issues/IssueGithub0224_DelayedQueryCapture.cs @@ -0,0 +1,94 @@ +// Copyright (C) 2022 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections; +using System.Diagnostics; +using System.Linq; +using System.Linq.Dynamic; +using System.Reflection; +using System.Threading; +using NUnit.Framework; +using Xtensive.Caching; +using Xtensive.Orm.Configuration; +using Xtensive.Orm.Tests.Issues.IssueGithub0224_DelayedQueryCapture_Model; + +namespace Xtensive.Orm.Tests.Issues +{ + namespace IssueGithub0224_DelayedQueryCapture_Model + { + [HierarchyRoot] + class Item : Entity + { + [Field, Key] + public int Id { get; private set; } + + [Field] + public int Tag { get; set; } + } + } + + [Serializable] + public class IssueGithub0224_DelayedQueryCapture : AutoBuildTest + { + public class OtherService + { + public static volatile int InstanceCount; + + public int N; + + public OtherService() + { + Interlocked.Increment(ref InstanceCount); + } + + ~OtherService() + { + Interlocked.Decrement(ref InstanceCount); + } + } + + + protected override DomainConfiguration BuildConfiguration() + { + var config = base.BuildConfiguration(); + config.Types.Register(typeof(Item).Assembly, typeof(Item).Namespace); + return config; + } + + [Test] + public void DelayedQueryCapture() + { + using (var session = Domain.OpenSession()) + using (var t = session.OpenTransaction()) { + var item = new Item() { Tag = 10 }; + DelayedQuery(session); + t.Complete(); + } + GC.Collect(); + Thread.Sleep(1000); + GC.Collect(); + Assert.AreEqual(0, OtherService.InstanceCount); + } + + private void DelayedQuery(Session session) + { + var ids = new[] { 1, 2 }; + var otherService = new OtherService(); + + var items = session.Query.CreateDelayedQuery(q => + from t in q.All() + where t.Id.In(ids) + select t).ToArray(); + + var bb1 = items + .Select(a => new { + a.Id, + A = new { + B = otherService.N == a.Id + }, + }); + } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs index bc036269a3..e499166a64 100644 --- a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs +++ b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2021 Xtensive LLC. +// Copyright (C) 2012-2022 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. // Created by: Denis Krjuchkov @@ -7,6 +7,7 @@ using System; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Xtensive.Caching; @@ -19,6 +20,8 @@ namespace Xtensive.Orm.Internals { internal class CompiledQueryRunner { + private static readonly Func FieldIsSimple = fieldInfo => IsSimpleType(fieldInfo.FieldType); + private readonly Domain domain; private readonly Session session; private readonly QueryEndpoint endpoint; @@ -108,7 +111,7 @@ private DelayedQuery CreateDelayedSequenceQuery( private ParameterizedQuery GetScalarQuery( Func query, bool executeAsSideEffect, out TResult result) { - AllocateParameterAndReplacer(); + var cacheable = AllocateParameterAndReplacer(); var parameterContext = new ParameterContext(outerContext); parameterContext.SetValue(queryParameter, queryTarget); @@ -123,7 +126,9 @@ private ParameterizedQuery GetScalarQuery( throw new NotSupportedException(Strings.ExNonLinqCallsAreNotSupportedWithinQueryExecuteDelayed); } - PutCachedQuery(parameterizedQuery); + if (cacheable) { + PutCachedQuery(parameterizedQuery); + } return parameterizedQuery; } @@ -135,7 +140,7 @@ private ParameterizedQuery GetSequenceQuery( return parameterizedQuery; } - AllocateParameterAndReplacer(); + var cacheable = AllocateParameterAndReplacer(); var scope = new CompiledQueryProcessingScope(queryParameter, queryParameterReplacer); using (scope.Enter()) { var result = query.Invoke(endpoint); @@ -143,16 +148,18 @@ private ParameterizedQuery GetSequenceQuery( parameterizedQuery = (ParameterizedQuery) translatedQuery; } - PutCachedQuery(parameterizedQuery); + if (cacheable) { + PutCachedQuery(parameterizedQuery); + } return parameterizedQuery; } - private void AllocateParameterAndReplacer() + private bool AllocateParameterAndReplacer() { if (queryTarget == null) { queryParameter = null; queryParameterReplacer = new ExtendedExpressionReplacer(e => e); - return; + return true; } var closureType = queryTarget.GetType(); @@ -192,6 +199,25 @@ private void AllocateParameterAndReplacer() } return null; }); + + return !TypeHelper.IsClosure(closureType) + || closureType.GetFields().All(FieldIsSimple); + } + + private static bool IsSimpleType(Type type) + { + var typeInfo = type.GetTypeInfo(); + if (typeInfo.IsGenericType) { + var genericDef = typeInfo.GetGenericTypeDefinition(); + return (genericDef == WellKnownTypes.NullableOfT || genericDef.IsAssignableTo(WellKnownTypes.IReadOnlyListOfT)) + && IsSimpleType(typeInfo.GetGenericArguments()[0]); + } + else if (typeInfo.IsArray) { + return IsSimpleType(typeInfo.GetElementType()); + } + else { + return typeInfo.IsPrimitive || typeInfo.IsEnum || type == WellKnownTypes.String || type == WellKnownTypes.Decimal; + } } private ParameterizedQuery GetCachedQuery() => diff --git a/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs b/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs index d8378b5a7d..6c4c876b67 100644 --- a/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs +++ b/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs @@ -1,8 +1,9 @@ -// Copyright (C) 2020 Xtensive LLC. +// Copyright (C) 2020-2022 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -73,6 +74,7 @@ internal static class WellKnownTypes public static readonly Type ByteArray = typeof(byte[]); public static readonly Type ObjectArray = typeof(object[]); + public static readonly Type IReadOnlyListOfT = typeof(IReadOnlyList<>); public static readonly Type DefaultMemberAttribute = typeof(DefaultMemberAttribute); }