1
1
using System ;
2
2
using System . Linq ;
3
+ using System . Reflection ;
3
4
using NHibernate . DomainModel . Northwind . Entities ;
4
5
using NHibernate . Linq ;
5
6
using NUnit . Framework ;
@@ -20,10 +21,12 @@ public void SessionGetsCollected()
20
21
21
22
private WeakReference DoLinqInSeparateSession ( )
22
23
{
24
+ // Using SessionFactory of another session dodge TestCase session tracking, which is needed for having session garbage collected
25
+ // Otherwise DebugSessionFactory session tracking should be changed to use WeakReference too.
23
26
using ( var leakTest = session . SessionFactory . OpenSession ( ) )
24
27
{
25
28
// It appears linq expressions will (or might) contain a reference to the
26
- // IQueryable. At time of writing, linq expressions are helt within NhLinqExpression,
29
+ // IQueryable. At time of writing, linq expressions are held within NhLinqExpression,
27
30
// which in turn will be held in the query plan cache. Since the IQueryable will
28
31
// be an NhQueryable, which holds a reference to the SessionImpl instance,
29
32
// we will be leaking session instances.
@@ -33,5 +36,71 @@ private WeakReference DoLinqInSeparateSession()
33
36
return new WeakReference ( leakTest , false ) ;
34
37
}
35
38
}
39
+
40
+ static readonly PropertyInfo SessionProperty = typeof ( DefaultQueryProvider ) . GetProperty (
41
+ "Session" ,
42
+ BindingFlags . NonPublic | BindingFlags . Instance ) ;
43
+
44
+ [ Theory ]
45
+ public void SessionIsNotNullOrResurrected ( bool ? disposeSession )
46
+ {
47
+ // Must do in a separated method, local variables seem not collected otherwise.
48
+ var provider = GetProviderFromSeparateSession ( disposeSession ) ;
49
+
50
+ if ( provider == null )
51
+ Assert . Ignore ( "Another query provider than NHibernate default one is used" ) ;
52
+ Assert . That ( SessionProperty , Is . Not . Null , $ "Session property on { nameof ( DefaultQueryProvider ) } is not found.") ;
53
+
54
+ // Force collection of no more strongly referenced objects.
55
+ // Do not wait for pending finalizers
56
+ GC . Collect ( ) ;
57
+
58
+ try
59
+ {
60
+ var s = SessionProperty . GetValue ( provider ) ;
61
+ Assert . Fail ( $ "Getting provider Session property did not failed. Obtained { ( s == null ? "null" : s . GetType ( ) . Name ) } .") ;
62
+ }
63
+ catch ( TargetInvocationException tie )
64
+ {
65
+ Assert . That ( tie . InnerException , Is . TypeOf < InvalidOperationException > ( ) . And . Message . Contains ( "garbage coll" ) ) ;
66
+ }
67
+ }
68
+
69
+ [ Theory ]
70
+ public void QueryFailsProperlyOnDereferencedSession ( bool ? disposeSession )
71
+ {
72
+ // Must do in a separated method, local variables seem not collected otherwise.
73
+ var query = GetQueryFromSeparateSession ( disposeSession ) ;
74
+
75
+ // Force collection of no more strongly referenced objects.
76
+ // Do not wait for pending finalizers
77
+ GC . Collect ( ) ;
78
+
79
+ Assert . That ( ( ) => query . FirstOrDefault ( ) , Throws . InvalidOperationException . And . Message . Contains ( "garbage coll" ) ) ;
80
+ }
81
+
82
+ IQueryable < Customer > GetQueryFromSeparateSession ( bool ? disposeSession )
83
+ {
84
+ // Using SessionFactory of another session dodge DebugSessionFactory session tracking, which is needed for having session garbage collected.
85
+ // Otherwise DebugSessionFactory session tracking should be changed to use WeakReference too.
86
+ var s = session . SessionFactory . OpenSession ( ) ;
87
+ try
88
+ {
89
+ return s . Query < Customer > ( ) ;
90
+ }
91
+ finally
92
+ {
93
+ if ( disposeSession == true )
94
+ s . Dispose ( ) ;
95
+ else if ( disposeSession == false )
96
+ s . Close ( ) ;
97
+ }
98
+ }
99
+
100
+ DefaultQueryProvider GetProviderFromSeparateSession ( bool ? disposeSession )
101
+ {
102
+ var queryable = GetQueryFromSeparateSession ( disposeSession ) ;
103
+ return queryable . Provider as DefaultQueryProvider ;
104
+ }
36
105
}
37
106
}
0 commit comments