1
+ using System . Linq ;
2
+ using NHibernate . Cfg . MappingSchema ;
3
+ using NHibernate . Linq ;
4
+ using NHibernate . Mapping . ByCode ;
5
+ using NUnit . Framework ;
6
+ using System . Collections . Generic ;
7
+ using System ;
8
+
9
+ namespace NHibernate . Test . NHSpecificTest . NH3050
10
+ {
11
+ /// <summary>
12
+ /// Fixture using 'by code' mappings
13
+ /// </summary>
14
+ /// <remarks>
15
+ /// This fixture is identical to <see cref="Fixture" /> except the <see cref="Entity" /> mapping is performed
16
+ /// by code in the GetMappings method, and does not require the <c>Mappings.hbm.xml</c> file. Use this approach
17
+ /// if you prefer.
18
+ /// </remarks>
19
+ [ TestFixture ]
20
+ public class FixtureByCode : TestCaseMappingByCode
21
+ {
22
+ protected override HbmMapping GetMappings ( )
23
+ {
24
+ var mapper = new ModelMapper ( ) ;
25
+ mapper . Class < Entity > ( rc =>
26
+ {
27
+ rc . Id ( x => x . Id , m => m . Generator ( Generators . GuidComb ) ) ;
28
+ rc . Property ( x => x . Name ) ;
29
+ } ) ;
30
+
31
+ return mapper . CompileMappingForAllExplicitlyAddedEntities ( ) ;
32
+ }
33
+
34
+ protected override void OnSetUp ( )
35
+ {
36
+ using ( ISession session = OpenSession ( ) )
37
+ using ( ITransaction transaction = session . BeginTransaction ( ) )
38
+ {
39
+ var e1 = new Entity { Name = "Bob" } ;
40
+ session . Save ( e1 ) ;
41
+
42
+ var e2 = new Entity { Name = "Sally" } ;
43
+ session . Save ( e2 ) ;
44
+
45
+ session . Flush ( ) ;
46
+ transaction . Commit ( ) ;
47
+ }
48
+ }
49
+
50
+ protected override void OnTearDown ( )
51
+ {
52
+ using ( ISession session = OpenSession ( ) )
53
+ using ( ITransaction transaction = session . BeginTransaction ( ) )
54
+ {
55
+ session . Delete ( "from System.Object" ) ;
56
+
57
+ session . Flush ( ) ;
58
+ transaction . Commit ( ) ;
59
+ }
60
+ }
61
+
62
+ [ Test ]
63
+ public void NH3050_Reproduction ( )
64
+ {
65
+ //firstly to make things simpler, we set the query plan cache size to 1
66
+ Assert . IsTrue ( TrySetQueryPlanCacheSize ( Sfi , 1 ) ) ;
67
+
68
+ using ( ISession session = OpenSession ( ) )
69
+ using ( session . BeginTransaction ( ) )
70
+ {
71
+ var names = new List < string > ( ) { "Bob" } ;
72
+ var query = from e in session . Query < Entity > ( )
73
+ where names . Contains ( e . Name )
74
+ select e ;
75
+
76
+ //create a future, which will prepare a linq query plan and add it to the cache (NhLinqExpression)
77
+ var future = query . ToFuture ( ) ;
78
+
79
+ //we need enough unique queries (different to our main query here) to fill the plan cache so that our previous plan is evicted
80
+ //in this case we only need one as we have limited the cache size to 1
81
+ ( from e in session . Query < Entity > ( )
82
+ where e . Name == ""
83
+ select e ) . ToList ( ) ;
84
+
85
+ //garbage collection runs so that the query plan for our future which is a weak reference now in the plan cache is collected.
86
+ GC . Collect ( ) ;
87
+
88
+ //execute future which creates an ExpandedQueryExpression and adds it to the plan cache (generates the same cache plan key as the NhLinqExpression)
89
+ future . ToList ( ) ;
90
+
91
+ //execute original query again which will look for a NhLinqExpression in the plan cache but because it has already been evicted
92
+ //and because the ExpandedQueryExpression generates the same cache key, the ExpandedQueryExpression is returned and
93
+ //an exception is thrown as it tries to cast to a NhLinqExpression.
94
+ query . ToList ( ) ;
95
+ }
96
+ }
97
+
98
+ /// <summary>
99
+ /// Uses reflection to create a new SoftLimitMRUCache with a specified size and sets session factory query plan chache to it.
100
+ /// This is done like this as NHibernate does not currently provide any way to specify the query plan cache size through configuration.
101
+ /// </summary>
102
+ /// <param name="factory"></param>
103
+ /// <param name="size"></param>
104
+ /// <returns></returns>
105
+ private static bool TrySetQueryPlanCacheSize ( NHibernate . ISessionFactory factory , int size )
106
+ {
107
+ var factoryImpl = factory as NHibernate . Impl . SessionFactoryImpl ;
108
+ if ( factoryImpl != null )
109
+ {
110
+ var queryPlanCacheFieldInfo = typeof ( NHibernate . Impl . SessionFactoryImpl ) . GetField ( "queryPlanCache" , System . Reflection . BindingFlags . Instance | System . Reflection . BindingFlags . NonPublic ) ;
111
+ if ( queryPlanCacheFieldInfo != null )
112
+ {
113
+ var queryPlanCache = ( NHibernate . Engine . Query . QueryPlanCache ) queryPlanCacheFieldInfo . GetValue ( factoryImpl ) ;
114
+
115
+ var planCacheFieldInfo = typeof ( NHibernate . Engine . Query . QueryPlanCache ) . GetField ( "planCache" , System . Reflection . BindingFlags . Instance | System . Reflection . BindingFlags . NonPublic ) ;
116
+ if ( planCacheFieldInfo != null )
117
+ {
118
+ var softLimitMRUCache = new NHibernate . Util . SoftLimitMRUCache ( size ) ;
119
+
120
+ planCacheFieldInfo . SetValue ( queryPlanCache , softLimitMRUCache ) ;
121
+ return true ;
122
+ }
123
+ }
124
+ }
125
+ return false ;
126
+ }
127
+ }
128
+ }
0 commit comments