NHibernate lazy loading

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Ik map mijn classes via nhibernate in de database.
Dit werkt allemaal goed. Ik doe het op de volgende manier:

Ik heb een class Sf, waarin de factory 1 maal wordt aangemaakt.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sealed class Sf
    {
        private Configuration cfg = null;
        private ISessionFactory factory = null;

        private Sf()
        {
            cfg = new Configuration();
            cfg.AddAssembly("*****");
            factory = cfg.BuildSessionFactory();                        
        }

        public static readonly Sf SessionFactory = new Sf();
        
        public ISession OpenSession()
        {
            return factory.OpenSession();
        }        
    }


Hierna vraag ik als volgt de gegeven op / en voeg ik ze toe.

C#:
1
2
3
4
5
ISession session = Sf.SessionFactory.OpenSession();
ITransaction transaction = session.BeginTransaction();
.... .....
transaction.Commit();
session.Close();


Echter is het probleem dat als ik de classes lazy maak het niet meer werkt. Dit komt omdat er elke keer een nieuwe session wordt geopend volgens mij. Als ik dit echter oplos door het volgende in de functie opensession te zetten:

C#:
1
2
3
4
public ISession OpenSession()
        {
            return factory.GetCurrentSession();
        }  


en de volgende property toe te voegen aan de web.xml
XML:
1
<add key="hibernate.current_session_context_class" value="web" />


Krijg ik de volgende foutmelding:

No session bound to the current context

Ik heb al veel gezocht, maar het lukt me niet om deze fout op te lossen. Heeft iemand een idee hoe ik deze fout kan verhelpen.

Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 18:57
Als een collectie lazy geloaded wordt, is er idd een ISession nodig; de collectie moet nl uit de DB gehaald worden.
Als je ISession (waarmee je je 'parent' class) hebt opgehaald ondertussen gesloten is, dan zou je in principe je object (dat de collectie bevat) kunnen 'attachen' aan een nieuwe ISession (dmv Lock oid).

Gaat het hier trouwens over een WinForms project ?
Wat je ook zou kunnen doen (en dat is hetgeen ik doe ~ niet dat ik zoveel ervaring heb met nhibernate), is m'n ISession laten openstaan zolang m'n form openstaat. (De connectie met de DB sluit ik dan weer wel als ik de DB even niet nodig heb).

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • Kwistnix
  • Registratie: Juni 2001
  • Laatst online: 17:30
Geen ervaring met NHibernate, wel met Hibernate.
Het lijkt er op dat jij een session-per-operation implementatie gebruikt, waarbij je (in een DAO?) voor iedere database call een nieuwe sessie opent, daarbinnen een transactie start en daarna de transactie en de sessie weer afsluit. Daardoor wordt het opgehaalde object in een detached state terecht komt en eventuele lazy associaties met andere objecten niet traversable zijn. Daarvoor moet het object weer aan een nieuwe sessie gekoppeld worden, waardoor het weer in een persistent state komt, zodat NHibernate de associaties kan laden. Je zou het verhaal over sessies, transacties en units of work eens moeten lezen en kiezen voor een aanpak die het meest geschikt is voor jouw applicatie, bijvoorbeeld, open-session-in-view, zoals whoami voorstelt. De link verwijst niet naar specifieke documentatie over NHibernate, maar het gaat hier niet concepten en niet over een specifieke implementatie.

Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 18:57
een beetje offtopic, maar ik ben er persoonlijk niet voor dat je DAO verantwoordelijk is voor het maken / openen van ISessions en committen / rollbacken van transacties.
De DAO kan -imho- nl niet weten wanneer er een nieuwe sessie moet gestart worden, ed.

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Het is inderdaad precies zoals FallenAngel666 zegt. Bij elke call open ik een nieuwe session. Ik zal het artikel eens bekijken en kijken of ik er uit komt. Op een of andere manier moet ik dus zorgen dat de session open blijft totdat hij niet meer nodig is.

Acties:
  • 0 Henk 'm!

  • Kwistnix
  • Registratie: Juni 2001
  • Laatst online: 17:30
whoami schreef op maandag 18 februari 2008 @ 14:53:
een beetje offtopic, maar ik ben er persoonlijk niet voor dat je DAO verantwoordelijk is voor het maken / openen van ISessions en committen / rollbacken van transacties.
De DAO kan -imho- nl niet weten wanneer er een nieuwe sessie moet gestart worden, ed.
Absoluut. Session lifetime (en ook transaction demarcatie!) moet zeker niet gekoppeld zijn aan individuele DAO methoden, want dan krijg je juist last van het session-per-operation anti-pattern.
In Java los ik dat zelf meestal op m.b.v. Spring, die buiten om mijn DAO's verantwoordelijk is voor sessies/transacties.

[ Voor 9% gewijzigd door Kwistnix op 18-02-2008 14:59 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Ik heb wat zitten zoeken en spelen, en nu heb ik het probleem opgelost dmv het volgende artikel:
http://www.hibernate.org/363.html

Ik vraag me alleen af of dit nu een goede oplossing is.

Acties:
  • 0 Henk 'm!

  • EfBe
  • Registratie: Januari 2000
  • Niet online
Verwijderd schreef op maandag 18 februari 2008 @ 15:03:
Ik heb wat zitten zoeken en spelen, en nu heb ik het probleem opgelost dmv het volgende artikel:
http://www.hibernate.org/363.html

Ik vraag me alleen af of dit nu een goede oplossing is.
Met session/context oriented o/r mappers heb je nu eenmaal het nadeel dat wanneer die session/context out-of-scope gaat, je entities het nakijken hebben, incluis hun change tracking.

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Ik heb het nu zo gemaakt dat de session open blijft. Dit werkt. Nu kan hij immers de data ophalen wanneer hij deze nodig heeft. Nu blijft echter de vraag: Wanneer sluit je de session? De session open laten lijkt me ook niet echt een goed plan. En na lazy uitzetten wordt de applicatie langzamer.

Acties:
  • 0 Henk 'm!

  • Kwistnix
  • Registratie: Juni 2001
  • Laatst online: 17:30
Verwijderd schreef op maandag 18 februari 2008 @ 15:46:
Ik heb het nu zo gemaakt dat de session open blijft. Dit werkt. Nu kan hij immers de data ophalen wanneer hij deze nodig heeft. Nu blijft echter de vraag: Wanneer sluit je de session? De session open laten lijkt me ook niet echt een goed plan. En na lazy uitzetten wordt de applicatie langzamer.
Het meest voor de hand liggende antwoord is: waar de unit of work eindigt.
Echter, dan moet je wel duidelijk units of work kunnen identificeren en dat is wellicht niet zo makkelijk.
In ieder geval moet je nooit een sessie open laten staan voor de volledige lifetime van de applicatie.
Je gaat dan onherroepelijk tegen database connection problemen aanlopen, het zij geen beschikbare connections (connection pool die opdroogt?) of time-outs van connections die met openstaande sessies geassocieerd zijn. Of je moet afzonderlijk van de sessie je database connecties gaan beheren, maar dan kan je net zo goed gelijk voor een goede sessie strategie kiezen IMO.

[ Voor 7% gewijzigd door Kwistnix op 18-02-2008 16:26 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Heb een aantal artikelen gelezen
http://www.hibernate.org/42.html
http://www.hibernate.org/43.html
http://www.codeproject.co...bernateBestPractices.aspx

Ik heb het nu op de volgende manier opgelost. Als het goed wordt nu aan het begin van een http request een session geopend en aan het einde gesloten.

Alleen hoe kan ik nu daadwerkelijk controleren of dat ook gebeurd.


De gegevens worden op de volgende manier opgehaald / geupdate
C#:
1
2
3
4
currentSession = NHibernateSessionManager.Instance.GetSession();       
NHibernateSessionManager.Instance.BeginTransaction();
IList<Medewerker> lMedewerkers = currentSession.CreateQuery(............).List<Medewerker>();
NHibernateSessionManager.Instance.CommitTransaction();           



Er is een NHibernateSessionManager.cs
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
public sealed class NHibernateSessionManager
    {
        #region Thread-safe, lazy Singleton

        /// <summary>
        /// This is a thread-safe, lazy singleton.  See http://www.yoda.arachsys.com/csharp/singleton.html
        /// for more details about its implementation.
        /// </summary>
        public static NHibernateSessionManager Instance
        {
            get
            {
                return Nested.NHibernateSessionManager;
            }
        }

        /// <summary>
        /// Initializes the NHibernate session factory upon instantiation.
        /// </summary>
        private NHibernateSessionManager()
        {
            InitSessionFactory();
        }

        /// <summary>
        /// Assists with ensuring thread-safe, lazy singleton
        /// </summary>
        private class Nested
        {
            static Nested() { }
            internal static readonly NHibernateSessionManager NHibernateSessionManager =
                new NHibernateSessionManager();
        }

        #endregion

        private void InitSessionFactory()
        {
            sessionFactory = new Configuration().Configure().BuildSessionFactory();
        }

        /// <summary>
        /// Allows you to register an interceptor on a new session.  This may not be called if there is already
        /// an open session attached to the HttpContext.  If you have an interceptor to be used, modify
        /// the HttpModule to call this before calling BeginTransaction().
        /// </summary>
        public void RegisterInterceptor(IInterceptor interceptor)
        {
            ISession session = ContextSession;

            if (session != null && session.IsOpen)
            {
                throw new CacheException("You cannot register an interceptor once a session has already been opened");
            }

            GetSession(interceptor);
        }

        public ISession GetSession()
        {
            return GetSession(null);
        }

        /// <summary>
        /// Gets a session with or without an interceptor.  This method is not called directly; instead,
        /// it gets invoked from other public methods.
        /// </summary>
        private ISession GetSession(IInterceptor interceptor)
        {
            ISession session = ContextSession;

            if (session == null)
            {
                if (interceptor != null)
                {
                    session = sessionFactory.OpenSession(interceptor);
                }
                else
                {
                    session = sessionFactory.OpenSession();
                }

                ContextSession = session;
            }

            Check.Ensure(session != null, "session was null");

            return session;
        }

        /// <summary>
        /// Flushes anything left in the session and closes the connection.
        /// </summary>
        public void CloseSession()
        {
            ISession session = ContextSession;

            if (session != null && session.IsOpen)
            {
                session.Flush();
                session.Close();
            }

            ContextSession = null;
        }

        public void BeginTransaction()
        {
            ITransaction transaction = ContextTransaction;

            if (transaction == null)
            {
                transaction = GetSession().BeginTransaction();
                ContextTransaction = transaction;
            }
        }

        public void CommitTransaction()
        {
            ITransaction transaction = ContextTransaction;

            try
            {
                if (HasOpenTransaction())
                {
                    transaction.Commit();
                    ContextTransaction = null;
                }
            }
            catch (HibernateException)
            {
                RollbackTransaction();
                throw;
            }
        }

        public bool HasOpenTransaction()
        {
            ITransaction transaction = ContextTransaction;

            return transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack;
        }

        public void RollbackTransaction()
        {
            ITransaction transaction = ContextTransaction;

            try
            {
                if (HasOpenTransaction())
                {
                    transaction.Rollback();
                }

                ContextTransaction = null;
            }
            finally
            {
                CloseSession();
            }
        }

        /// <summary>
        /// If within a web context, this uses <see cref="HttpContext" /> instead of the WinForms 
        /// specific <see cref="CallContext" />.  Discussion concerning this found at 
        /// http://forum.springframework.net/showthread.php?t=572.
        /// </summary>
        private ITransaction ContextTransaction
        {
            get
            {
                if (IsInWebContext())
                {
                    return (ITransaction)HttpContext.Current.Items[TRANSACTION_KEY];
                }
                else
                {
                    return (ITransaction)CallContext.GetData(TRANSACTION_KEY);
                }
            }
            set
            {
                if (IsInWebContext())
                {
                    HttpContext.Current.Items[TRANSACTION_KEY] = value;
                }
                else
                {
                    CallContext.SetData(TRANSACTION_KEY, value);
                }
            }
        }

        /// <summary>
        /// If within a web context, this uses <see cref="HttpContext" /> instead of the WinForms 
        /// specific <see cref="CallContext" />.  Discussion concerning this found at 
        /// http://forum.springframework.net/showthread.php?t=572.
        /// </summary>
        private ISession ContextSession
        {
            get
            {
                if (IsInWebContext())
                {
                    return (ISession)HttpContext.Current.Items[SESSION_KEY];
                }
                else
                {
                    return (ISession)CallContext.GetData(SESSION_KEY);
                }
            }
            set
            {
                if (IsInWebContext())
                {
                    HttpContext.Current.Items[SESSION_KEY] = value;
                }
                else
                {
                    CallContext.SetData(SESSION_KEY, value);
                }
            }
        }

        private bool IsInWebContext()
        {
            return HttpContext.Current != null;
        }

        private const string TRANSACTION_KEY = "CONTEXT_TRANSACTION";
        private const string SESSION_KEY = "CONTEXT_SESSION";
        private ISessionFactory sessionFactory;
    }


en een NHibernateSessionModule.cs
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class NHibernateSessionModule : IHttpModule
    {
        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(BeginTransaction);
            context.EndRequest += new EventHandler(CommitAndCloseSession);
        }

        /// <summary>
        /// Opens a session within a transaction at the beginning of the HTTP request.
        /// This doesn't actually open a connection to the database until needed.
        /// </summary>
        private void BeginTransaction(object sender, EventArgs e)
        {
            NHibernateSessionManager.Instance.BeginTransaction();
        }

        /// <summary>
        /// Commits and closes the NHibernate session provided by the supplied <see cref="NHibernateSessionManager"/>.
        /// Assumes a transaction was begun at the beginning of the request; but a transaction or session does
        /// not *have* to be opened for this to operate successfully.
        /// </summary>
        private void CommitAndCloseSession(object sender, EventArgs e)
        {
            try
            {
                NHibernateSessionManager.Instance.CommitTransaction();
            }
            finally
            {
                NHibernateSessionManager.Instance.CloseSession();
            }
        }

        public void Dispose() { }
    }
Pagina: 1