Updates Castle to the latest NHibernate Trunk libs. This required namespace changes...
[castle.git] / ActiveRecord / Castle.ActiveRecord / Framework / ActiveRecordBase.cs
blob67c70c17c929e9de54e7af5eeffba5bcd78e3fdd
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.ActiveRecord
17 using System;
18 using System.Collections;
19 using System.Collections.Generic;
20 using Castle.ActiveRecord.Framework;
21 using Castle.ActiveRecord.Framework.Internal;
22 using Castle.ActiveRecord.Queries;
24 using NHibernate;
25 using NHibernate.Criterion;
26 using Castle.Components.Validator;
28 /// <summary>
29 /// Allow custom executions using the NHibernate's ISession.
30 /// </summary>
31 public delegate object NHibernateDelegate(ISession session, object instance);
33 /// <summary>
34 /// Base class for all ActiveRecord classes. Implements
35 /// all the functionality to simplify the code on the
36 /// subclasses.
37 /// </summary>
38 [Serializable]
39 public abstract class ActiveRecordBase : ActiveRecordHooksBase
41 /// <summary>
42 /// The global holder for the session factories.
43 /// </summary>
44 protected internal static ISessionFactoryHolder holder;
46 #region internal static
48 internal static void EnsureInitialized(Type type)
50 if (holder == null)
52 String message = String.Format("An ActiveRecord class ({0}) was used but the framework seems not " +
53 "properly initialized. Did you forget about ActiveRecordStarter.Initialize() ?",
54 type.FullName);
55 throw new ActiveRecordException(message);
57 if (type != typeof(ActiveRecordBase) && GetModel(type) == null)
59 String message = String.Format("You have accessed an ActiveRecord class that wasn't properly initialized. " +
60 "There are two possible explanations: that the call to ActiveRecordStarter.Initialize() didn't include {0} class, or that {0} class is not decorated with the [ActiveRecord] attribute.",
61 type.FullName);
62 throw new ActiveRecordException(message);
66 /// <summary>
67 /// Internally used
68 /// </summary>
69 /// <param name="arType">The type.</param>
70 /// <param name="model">The model.</param>
71 internal static void Register(Type arType, ActiveRecordModel model)
73 ActiveRecordModel.Register(arType, model);
76 /// <summary>
77 /// Internally used
78 /// </summary>
79 /// <param name="arType">The type.</param>
80 /// <returns>An <see cref="ActiveRecordModel"/></returns>
81 internal static ActiveRecordModel GetModel(Type arType)
83 return ActiveRecordModel.GetModel(arType);
86 #endregion
88 #region protected internal static
90 #region Create/Update/Save/Delete/DeleteAll/Refresh
92 #region Create
94 /// <summary>
95 /// Creates (Saves) a new instance to the database.
96 /// </summary>
97 /// <param name="instance">The ActiveRecord instance to be created on the database</param>
98 protected internal static void Create(object instance)
100 InternalCreate(instance, false);
103 /// <summary>
104 /// Creates (Saves) a new instance to the database and flushes the session.
105 /// </summary>
106 /// <param name="instance">The ActiveRecord instance to be created on the database</param>
107 protected internal static void CreateAndFlush(object instance)
109 InternalCreate(instance, true);
112 /// <summary>
113 /// Creates (Saves) a new instance to the database.
114 /// </summary>
115 /// <param name="instance">The ActiveRecord instance to be created on the database</param>
116 /// <param name="flush">if set to <c>true</c>, the operation will be followed by a session flush.</param>
117 private static void InternalCreate(object instance, bool flush)
119 if (instance == null) throw new ArgumentNullException("instance");
121 EnsureInitialized(instance.GetType());
123 ISession session = holder.CreateSession(instance.GetType());
127 session.Save(instance);
129 if (flush)
131 session.Flush();
134 catch(Exception ex)
136 holder.FailSession(session);
138 // NHibernate catches our ValidationException, and as such it is the innerexception here
139 if (ex is ValidationException)
140 throw ;
142 if (ex.InnerException is ValidationException)
143 throw ex.InnerException;
145 throw new ActiveRecordException("Could not perform Create for " + instance.GetType().Name, ex);
148 finally
150 holder.ReleaseSession(session);
154 #endregion
156 #region Delete
158 /// <summary>
159 /// Deletes the instance from the database.
160 /// </summary>
161 /// <param name="instance">The ActiveRecord instance to be deleted</param>
162 protected internal static void Delete(object instance)
164 InternalDelete(instance, false);
167 /// <summary>
168 /// Deletes the instance from the database and flushes the session.
169 /// </summary>
170 /// <param name="instance">The ActiveRecord instance to be deleted</param>
171 protected internal static void DeleteAndFlush(object instance)
173 InternalDelete(instance, true);
176 /// <summary>
177 /// Deletes the instance from the database.
178 /// </summary>
179 /// <param name="instance">The ActiveRecord instance to be deleted</param>
180 /// <param name="flush">if set to <c>true</c>, the operation will be followed by a session flush.</param>
181 private static void InternalDelete(object instance, bool flush)
183 if (instance == null) throw new ArgumentNullException("instance");
185 EnsureInitialized(instance.GetType());
187 ISession session = holder.CreateSession(instance.GetType());
191 session.Delete(instance);
193 if (flush)
195 session.Flush();
198 catch (ValidationException)
200 holder.FailSession(session);
202 throw;
205 catch (Exception ex)
207 holder.FailSession(session);
209 throw new ActiveRecordException("Could not perform Delete for " + instance.GetType().Name, ex);
211 finally
213 holder.ReleaseSession(session);
217 #endregion
219 #region Replicate
221 /// <summary>
222 /// From NHibernate documentation:
223 /// Persist all reachable transient objects, reusing the current identifier
224 /// values. Note that this will not trigger the Interceptor of the Session.
225 /// </summary>
226 /// <param name="instance">The instance.</param>
227 /// <param name="replicationMode">The replication mode.</param>
228 protected internal static void Replicate(object instance, ReplicationMode replicationMode)
230 if (instance == null)
232 throw new ArgumentNullException("instance");
235 EnsureInitialized(instance.GetType());
237 ISession session = holder.CreateSession(instance.GetType());
241 session.Replicate(instance, replicationMode);
243 catch(Exception ex)
245 holder.FailSession(session);
247 // NHibernate catches our ValidationException, and as such it is the innerexception here
248 if (ex.InnerException is ValidationException)
250 throw ex.InnerException;
252 else
254 throw new ActiveRecordException("Could not perform Replicate for " + instance.GetType().Name, ex);
257 finally
259 holder.ReleaseSession(session);
263 #endregion
265 #region Refresh
267 /// <summary>
268 /// Refresh the instance from the database.
269 /// </summary>
270 /// <param name="instance">The ActiveRecord instance to be reloaded</param>
271 protected internal static void Refresh(object instance)
273 if (instance == null) throw new ArgumentNullException("instance");
275 EnsureInitialized(instance.GetType());
277 ISession session = holder.CreateSession(instance.GetType());
281 session.Refresh(instance);
283 catch (Exception ex)
285 holder.FailSession(session);
287 // NHibernate catches our ValidationException, and as such it is the innerexception here
288 if (ex.InnerException is ValidationException)
290 throw ex.InnerException;
292 else
294 throw new ActiveRecordException("Could not perform Refresh for " + instance.GetType().Name, ex);
297 finally
299 holder.ReleaseSession(session);
303 #endregion
305 #region DeleteAll
307 /// <summary>
308 /// Deletes all rows for the specified ActiveRecord type
309 /// </summary>
310 /// <remarks>
311 /// This method is usually useful for test cases.
312 /// </remarks>
313 /// <param name="type">ActiveRecord type on which the rows on the database should be deleted</param>
314 protected internal static void DeleteAll(Type type)
316 EnsureInitialized(type);
318 ISession session = holder.CreateSession(type);
322 session.Delete(String.Format("from {0}", type.Name));
324 session.Flush();
326 catch (ValidationException)
328 holder.FailSession(session);
330 throw;
332 catch (Exception ex)
334 holder.FailSession(session);
336 throw new ActiveRecordException("Could not perform DeleteAll for " + type.Name, ex);
338 finally
340 holder.ReleaseSession(session);
344 /// <summary>
345 /// Deletes all rows for the specified ActiveRecord type that matches
346 /// the supplied HQL condition
347 /// </summary>
348 /// <remarks>
349 /// This method is usually useful for test cases.
350 /// </remarks>
351 /// <param name="type">ActiveRecord type on which the rows on the database should be deleted</param>
352 /// <param name="where">HQL condition to select the rows to be deleted</param>
353 protected internal static void DeleteAll(Type type, String where)
355 EnsureInitialized(type);
357 ISession session = holder.CreateSession(type);
361 session.Delete(String.Format("from {0} where {1}", type.Name, where));
363 session.Flush();
365 catch (ValidationException)
367 holder.FailSession(session);
369 throw;
371 catch (Exception ex)
373 holder.FailSession(session);
375 throw new ActiveRecordException("Could not perform DeleteAll for " + type.Name, ex);
377 finally
379 holder.ReleaseSession(session);
383 /// <summary>
384 /// Deletes all <paramref name="targetType" /> objects, based on the primary keys
385 /// supplied on <paramref name="pkValues" />.
386 /// </summary>
387 /// <param name="targetType">The target ActiveRecord type</param>
388 /// <param name="pkValues">A list of primary keys</param>
389 /// <returns>The number of objects deleted</returns>
390 protected internal static int DeleteAll(Type targetType, IEnumerable pkValues)
392 if (pkValues == null)
394 return 0;
397 int counter = 0;
399 foreach (object pk in pkValues)
401 Object obj = FindByPrimaryKey(targetType, pk, false);
403 if (obj != null)
405 ActiveRecordBase arBase = obj as ActiveRecordBase;
407 if (arBase != null)
409 arBase.Delete(); // in order to allow override of the virtual "Delete()" method
411 else
413 Delete(obj);
416 counter++;
420 return counter;
423 #endregion
425 #region Update
427 /// <summary>
428 /// Persists the modification on the instance
429 /// state to the database.
430 /// </summary>
431 /// <param name="instance">The ActiveRecord instance to be updated on the database</param>
432 protected internal static void Update(object instance)
434 InternalUpdate(instance, false);
437 /// <summary>
438 /// Persists the modification on the instance
439 /// state to the database and flushes the session.
440 /// </summary>
441 /// <param name="instance">The ActiveRecord instance to be updated on the database</param>
442 protected internal static void UpdateAndFlush(object instance)
444 InternalUpdate(instance, true);
447 /// <summary>
448 /// Persists the modification on the instance
449 /// state to the database.
450 /// </summary>
451 /// <param name="instance">The ActiveRecord instance to be updated on the database</param>
452 /// <param name="flush">if set to <c>true</c>, the operation will be followed by a session flush.</param>
453 private static void InternalUpdate(object instance, bool flush)
455 if (instance == null) throw new ArgumentNullException("instance");
457 EnsureInitialized(instance.GetType());
459 ISession session = holder.CreateSession(instance.GetType());
463 session.Update(instance);
465 if (flush)
467 session.Flush();
470 catch (ValidationException)
472 holder.FailSession(session);
474 throw;
476 catch (Exception ex)
478 holder.FailSession(session);
480 throw new ActiveRecordException("Could not perform Update for " + instance.GetType().Name, ex);
482 finally
484 holder.ReleaseSession(session);
488 #endregion
490 #region Save
492 /// <summary>
493 /// Saves the instance to the database. If the primary key is unitialized
494 /// it creates the instance on the database. Otherwise it updates it.
495 /// <para>
496 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
497 /// or <see cref="Update()"/> instead.
498 /// </para>
499 /// </summary>
500 /// <param name="instance">The ActiveRecord instance to be saved</param>
501 protected internal static void Save(object instance)
503 InternalSave(instance, false);
506 /// <summary>
507 /// Saves the instance to the database and flushes the session. If the primary key is unitialized
508 /// it creates the instance on the database. Otherwise it updates it.
509 /// <para>
510 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
511 /// or <see cref="Update()"/> instead.
512 /// </para>
513 /// </summary>
514 /// <param name="instance">The ActiveRecord instance to be saved</param>
515 protected internal static void SaveAndFlush(object instance)
517 InternalSave(instance, true);
520 /// <summary>
521 /// Saves a copy of the instance to the database. If the primary key is unitialized
522 /// it creates the instance on the database. Otherwise it updates it.
523 /// <para>
524 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
525 /// or <see cref="Update()"/> instead.
526 /// </para>
527 /// </summary>
528 /// <param name="instance">The transient instance to be saved</param>
529 /// <returns>The saved ActiveRecord instance</returns>
530 protected internal static object SaveCopy(object instance)
532 return InternalSaveCopy(instance, false);
535 /// <summary>
536 /// Saves a copy of the instance to the database and flushes the session. If the primary key is unitialized
537 /// it creates the instance on the database. Otherwise it updates it.
538 /// <para>
539 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
540 /// or <see cref="Update()"/> instead.
541 /// </para>
542 /// </summary>
543 /// <param name="instance">The transient instance to be saved</param>
544 /// <returns>The saved ActiveRecord instance</returns>
545 protected internal static object SaveCopyAndFlush(object instance)
547 return InternalSaveCopy(instance, true);
550 /// <summary>
551 /// Saves the instance to the database. If the primary key is unitialized
552 /// it creates the instance on the database. Otherwise it updates it.
553 /// <para>
554 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
555 /// or <see cref="Update()"/> instead.
556 /// </para>
557 /// </summary>
558 /// <param name="instance">The ActiveRecord instance to be saved</param>
559 /// <param name="flush">if set to <c>true</c>, the operation will be followed by a session flush.</param>
560 private static void InternalSave(object instance, bool flush)
562 if (instance == null) throw new ArgumentNullException("instance");
564 EnsureInitialized(instance.GetType());
566 ISession session = holder.CreateSession(instance.GetType());
570 session.SaveOrUpdate(instance);
572 if (flush)
574 session.Flush();
577 catch (ValidationException)
579 holder.FailSession(session);
581 throw;
583 catch(Exception ex)
585 holder.FailSession(session);
587 // NHibernate catches our ValidationException on Create so it could be the innerexception here
588 if (ex.InnerException is ValidationException)
590 throw ex.InnerException;
592 else
594 throw new ActiveRecordException("Could not perform Save for " + instance.GetType().Name, ex);
597 finally
599 holder.ReleaseSession(session);
603 /// <summary>
604 /// Saves a copy of the instance to the database. If the primary key is unitialized
605 /// it creates the instance on the database. Otherwise it updates it.
606 /// <para>
607 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
608 /// or <see cref="Update()"/> instead.
609 /// </para>
610 /// </summary>
611 /// <param name="instance">The transient instance to be saved</param>
612 /// <param name="flush">if set to <c>true</c>, the operation will be followed by a session flush.</param>
613 /// <returns>The saved ActiveRecord instance.</returns>
614 private static object InternalSaveCopy(object instance, bool flush)
616 if (instance == null) throw new ArgumentNullException("instance");
618 EnsureInitialized(instance.GetType());
620 ISession session = holder.CreateSession(instance.GetType());
624 object persistent = session.SaveOrUpdateCopy(instance);
626 if (flush)
628 session.Flush();
631 return persistent;
633 catch (ValidationException)
635 holder.FailSession(session);
637 throw;
639 catch (Exception ex)
641 holder.FailSession(session);
643 // NHibernate catches our ValidationException on Create so it could be the innerexception here
644 if (ex.InnerException is ValidationException)
646 throw ex.InnerException;
648 else
650 throw new ActiveRecordException("Could not perform SaveCopy for " + instance.GetType().Name, ex);
653 finally
655 holder.ReleaseSession(session);
659 #endregion
661 #endregion
663 #region Execute
665 /// <summary>
666 /// Invokes the specified delegate passing a valid
667 /// NHibernate session. Used for custom NHibernate queries.
668 /// </summary>
669 /// <param name="targetType">The target ActiveRecordType</param>
670 /// <param name="call">The delegate instance</param>
671 /// <param name="instance">The ActiveRecord instance</param>
672 /// <returns>Whatever is returned by the delegate invocation</returns>
673 protected internal static object Execute(Type targetType, NHibernateDelegate call, object instance)
675 if (targetType == null) throw new ArgumentNullException("targetType", "Target type must be informed");
676 if (call == null) throw new ArgumentNullException("call", "Delegate must be passed");
678 EnsureInitialized(targetType);
680 ISession session = holder.CreateSession(targetType);
684 return call(session, instance);
686 catch (ValidationException)
688 holder.FailSession(session);
690 throw;
692 catch (Exception ex)
694 holder.FailSession(session);
696 throw new ActiveRecordException("Error performing Execute for " + targetType.Name, ex);
698 finally
700 holder.ReleaseSession(session);
704 #endregion
706 #region ExecuteQuery
708 /// <summary>
709 /// Enumerates the query
710 /// Note: only use if you expect most of the values to exist on the second level cache.
711 /// </summary>
712 /// <param name="query">The query.</param>
713 /// <returns>An <see cref="IEnumerable"/></returns>
714 protected internal static IEnumerable EnumerateQuery(IActiveRecordQuery query)
716 Type rootType = query.RootType;
718 EnsureInitialized(rootType);
720 ISession session = holder.CreateSession(rootType);
724 return query.Enumerate(session);
726 catch (Exception ex)
728 holder.FailSession(session);
730 throw new ActiveRecordException("Could not perform EnumerateQuery for " + rootType.Name, ex);
732 finally
734 holder.ReleaseSession(session);
738 #endregion
740 #region ExecuteQuery
742 /// <summary>
743 /// Executes the query.
744 /// </summary>
745 /// <param name="query">The query.</param>
746 /// <returns>The query result.</returns>
747 public static object ExecuteQuery(IActiveRecordQuery query)
749 Type rootType = query.RootType;
751 EnsureInitialized(rootType);
753 ISession session = holder.CreateSession(rootType);
757 return query.Execute(session);
759 catch (Exception ex)
761 holder.FailSession(session);
763 throw new ActiveRecordException("Could not perform ExecuteQuery for " + rootType.Name, ex);
765 finally
767 holder.ReleaseSession(session);
771 #endregion
773 #region Count
775 /// <summary>
776 /// Returns the number of records of the specified
777 /// type in the database
778 /// </summary>
779 /// <example>
780 /// <code>
781 /// [ActiveRecord]
782 /// public class User : ActiveRecordBase
783 /// {
784 /// ...
785 ///
786 /// public static int CountAllUsers()
787 /// {
788 /// return Count(typeof(User));
789 /// }
790 /// }
791 /// </code>
792 /// </example>
793 /// <param name="targetType">The target type.</param>
794 /// <returns>The count result</returns>
795 protected internal static int Count(Type targetType)
797 CountQuery query = new CountQuery(targetType);
799 return (int)ExecuteQuery(query);
802 /// <summary>
803 /// Returns the number of records of the specified
804 /// type in the database
805 /// </summary>
806 /// <example>
807 /// <code>
808 /// [ActiveRecord]
809 /// public class User : ActiveRecordBase
810 /// {
811 /// ...
812 ///
813 /// public static int CountAllUsersLocked()
814 /// {
815 /// return Count(typeof(User), "IsLocked = ?", true);
816 /// }
817 /// }
818 /// </code>
819 /// </example>
820 /// <param name="targetType">The target type.</param>
821 /// <param name="filter">A sql where string i.e. Person=? and DOB &gt; ?</param>
822 /// <param name="args">Positional parameters for the filter string</param>
823 /// <returns>The count result</returns>
824 protected internal static int Count(Type targetType, string filter, params object[] args)
826 CountQuery query = new CountQuery(targetType, filter, args);
828 return (int)ExecuteQuery(query);
831 /// <summary>
832 /// Returns the number of records of the specified
833 /// type in the database
834 /// </summary>
835 /// <param name="targetType">The target type.</param>
836 /// <param name="criteria">The criteria expression</param>
837 /// <returns>The count result</returns>
838 protected internal static int Count(Type targetType, ICriterion[] criteria)
840 CountQuery query = new CountQuery(targetType, criteria);
842 return (int) ExecuteQuery(query);
845 /// <summary>
846 /// Returns the number of records of the specified
847 /// type in the database
848 /// </summary>
849 /// <param name="targetType">The target type.</param>
850 /// <param name="detachedCriteria">The criteria expression</param>
851 /// <returns>The count result</returns>
852 protected internal static int Count(Type targetType, DetachedCriteria detachedCriteria)
854 CountQuery query = new CountQuery(targetType, detachedCriteria);
856 return (int)ExecuteQuery(query);
859 #endregion
861 #region Exists
863 /// <summary>
864 /// Check if there is any records in the db for the target type
865 /// </summary>
866 /// <param name="targetType">The target type.</param>
867 /// <returns><c>true</c> if there's at least one row</returns>
868 protected internal static bool Exists(Type targetType)
870 return Count(targetType) > 0;
873 /// <summary>
874 /// Check if there is any records in the db for the target type
875 /// </summary>
876 /// <param name="targetType">The target type.</param>
877 /// <param name="filter">A sql where string i.e. Person=? and DOB &gt; ?</param>
878 /// <param name="args">Positional parameters for the filter string</param>
879 /// <returns><c>true</c> if there's at least one row</returns>
880 protected internal static bool Exists(Type targetType, string filter, params object[] args)
882 return Count(targetType, filter, args) > 0;
885 /// <summary>
886 /// Check if the <paramref name="id"/> exists in the database.
887 /// </summary>
888 /// <param name="targetType">The target type.</param>
889 /// <param name="id">The id to check on</param>
890 /// <returns><c>true</c> if the ID exists; otherwise <c>false</c>.</returns>
891 protected internal static bool Exists(Type targetType, object id)
893 EnsureInitialized(targetType);
894 ISession session = holder.CreateSession(targetType);
898 return session.Get(targetType, id) != null;
900 catch(Exception ex)
902 throw new ActiveRecordException("Could not perform Exists for " + targetType.Name + ". Id: " + id, ex);
904 finally
906 holder.ReleaseSession(session);
910 /// <summary>
911 /// Check if any instance matching the criteria exists in the database.
912 /// </summary>
913 /// <param name="targetType">The target type.</param>
914 /// <param name="criteria">The criteria expression</param>
915 /// <returns><c>true</c> if an instance is found; otherwise <c>false</c>.</returns>
916 protected internal static bool Exists(Type targetType, params ICriterion[] criteria)
918 return Count(targetType, criteria) > 0;
921 /// <summary>
922 /// Check if any instance matching the criteria exists in the database.
923 /// </summary>
924 /// <param name="targetType">The target type.</param>
925 /// <param name="detachedCriteria">The criteria expression</param>
926 /// <returns><c>true</c> if an instance is found; otherwise <c>false</c>.</returns>
927 protected internal static bool Exists(Type targetType, DetachedCriteria detachedCriteria)
929 return Count(targetType, detachedCriteria) > 0;
932 #endregion
934 #region FindAll
936 /// <summary>
937 /// Returns all instances found for the specified type according to the criteria
938 /// </summary>
939 /// <param name="targetType">The target type.</param>
940 /// <param name="detachedCriteria">The criteria.</param>
941 /// <param name="orders">An <see cref="Array"/> of <see cref="Order"/> objects.</param>
942 /// <returns>The <see cref="Array"/> of results.</returns>
943 protected internal static Array FindAll(Type targetType, DetachedCriteria detachedCriteria, params Order[] orders)
945 EnsureInitialized(targetType);
947 ISession session = holder.CreateSession(targetType);
951 ICriteria criteria = detachedCriteria.GetExecutableCriteria(session);
953 AddOrdersToCriteria(criteria, orders);
955 return SupportingUtils.BuildArray(targetType, criteria.List());
957 catch(ValidationException)
959 holder.FailSession(session);
961 throw;
963 catch(Exception ex)
965 holder.FailSession(session);
967 throw new ActiveRecordException("Could not perform FindAll for " + targetType.Name, ex);
969 finally
971 holder.ReleaseSession(session);
975 /// <summary>
976 /// Returns all instances found for the specified type.
977 /// </summary>
978 /// <param name="targetType">The target type.</param>
979 /// <returns>The <see cref="Array"/> of results</returns>
980 protected internal static Array FindAll(Type targetType)
982 return FindAll(targetType, (Order[])null);
985 /// <summary>
986 /// Returns all instances found for the specified type
987 /// using sort orders and criteria.
988 /// </summary>
989 /// <param name="targetType">The The target type.</param>
990 /// <param name="orders">An <see cref="Array"/> of <see cref="Order"/> objects.</param>
991 /// <param name="criteria">The criteria expression</param>
992 /// <returns>The <see cref="Array"/> of results.</returns>
993 protected internal static Array FindAll(Type targetType, Order[] orders, params ICriterion[] criteria)
995 EnsureInitialized(targetType);
997 ISession session = holder.CreateSession(targetType);
1001 ICriteria sessionCriteria = session.CreateCriteria(targetType);
1003 foreach(ICriterion cond in criteria)
1005 sessionCriteria.Add(cond);
1008 AddOrdersToCriteria(sessionCriteria, orders);
1010 return SupportingUtils.BuildArray(targetType, sessionCriteria.List());
1012 catch (ValidationException)
1014 holder.FailSession(session);
1016 throw;
1018 catch (Exception ex)
1020 holder.FailSession(session);
1022 throw new ActiveRecordException("Could not perform FindAll for " + targetType.Name, ex);
1024 finally
1026 holder.ReleaseSession(session);
1030 private static void AddOrdersToCriteria(ICriteria criteria, IEnumerable<Order> orders)
1032 if (orders != null)
1034 foreach (Order order in orders)
1036 criteria.AddOrder(order);
1041 /// <summary>
1042 /// Returns all instances found for the specified type
1043 /// using criteria.
1044 /// </summary>
1045 /// <param name="targetType">The target type.</param>
1046 /// <param name="criteria">The criteria expression</param>
1047 /// <returns>The <see cref="Array"/> of results.</returns>
1048 protected internal static Array FindAll(Type targetType, params ICriterion[] criteria)
1050 return FindAll(targetType, null, criteria);
1053 #endregion
1055 #region FindAllByProperty
1057 /// <summary>
1058 /// Finds records based on a property value - automatically converts null values to IS NULL style queries.
1059 /// </summary>
1060 /// <param name="targetType">The target type</param>
1061 /// <param name="property">A property name (not a column name)</param>
1062 /// <param name="value">The value to be equals to</param>
1063 /// <returns>The <see cref="Array"/> of results.</returns>
1064 protected internal static Array FindAllByProperty(Type targetType, String property, object value)
1066 ICriterion criteria = (value == null) ? Expression.IsNull(property) : Expression.Eq(property, value);
1067 return FindAll(targetType, criteria);
1070 /// <summary>
1071 /// Finds records based on a property value - automatically converts null values to IS NULL style queries.
1072 /// </summary>
1073 /// <param name="targetType">The target type</param>
1074 /// <param name="orderByColumn">The column name to be ordered ASC</param>
1075 /// <param name="property">A property name (not a column name)</param>
1076 /// <param name="value">The value to be equals to</param>
1077 /// <returns>The <see cref="Array"/> of results.</returns>
1078 protected internal static Array FindAllByProperty(Type targetType, String orderByColumn, String property, object value)
1080 ICriterion criteria = (value == null) ? Expression.IsNull(property) : Expression.Eq(property, value);
1081 return FindAll(targetType, new Order[] {Order.Asc(orderByColumn)}, criteria);
1084 #endregion
1086 #region FindByPrimaryKey
1088 /// <summary>
1089 /// Finds an object instance by an unique ID
1090 /// </summary>
1091 /// <param name="targetType">The AR subclass type</param>
1092 /// <param name="id">ID value</param>
1093 /// <returns>The object instance.</returns>
1094 protected internal static object FindByPrimaryKey(Type targetType, object id)
1096 return FindByPrimaryKey(targetType, id, true);
1099 /// <summary>
1100 /// Finds an object instance by an unique ID
1101 /// </summary>
1102 /// <param name="targetType">The AR subclass type</param>
1103 /// <param name="id">ID value</param>
1104 /// <param name="throwOnNotFound"><c>true</c> if you want to catch an exception
1105 /// if the object is not found</param>
1106 /// <returns>The object instance.</returns>
1107 /// <exception cref="ObjectNotFoundException">if <c>throwOnNotFound</c> is set to
1108 /// <c>true</c> and the row is not found</exception>
1109 protected internal static object FindByPrimaryKey(Type targetType, object id, bool throwOnNotFound)
1111 EnsureInitialized(targetType);
1112 bool hasScope = holder.ThreadScopeInfo.HasInitializedScope;
1113 ISession session = holder.CreateSession(targetType);
1117 object loaded;
1118 // Load() and Get() has different semantics with regard to the way they
1119 // handle null values, Get() _must_ check that the value exists, Load() is allowed
1120 // to return an uninitialized proxy that will throw when you access it later.
1121 // in order to play well with proxies, we need to use this approach.
1122 if (throwOnNotFound)
1124 loaded = session.Load(targetType, id);
1126 else
1128 loaded = session.Get(targetType, id);
1130 //If we are not in a scope, we want to initialize the entity eagerly, since other wise the
1131 //user will get an exception when it access the entity's property, and it will try to lazy load itself and find that
1132 //it has no session.
1133 //If we are in a scope, it is the user responsability to keep the scope alive if he wants to use
1134 if (!hasScope)
1136 NHibernateUtil.Initialize(loaded);
1138 return loaded;
1140 catch (ObjectNotFoundException ex)
1142 holder.FailSession(session);
1144 String message = String.Format("Could not find {0} with id {1}", targetType.Name, id);
1145 throw new NotFoundException(message, ex);
1147 catch (ValidationException)
1149 holder.FailSession(session);
1151 throw;
1153 catch (Exception ex)
1155 holder.FailSession(session);
1157 throw new ActiveRecordException("Could not perform FindByPrimaryKey for " + targetType.Name + ". Id: " + id, ex);
1159 finally
1161 holder.ReleaseSession(session);
1165 #endregion
1167 #region FindFirst
1169 /// <summary>
1170 /// Searches and returns the first row.
1171 /// </summary>
1172 /// <param name="targetType">The target type.</param>
1173 /// <param name="detachedCriteria">The criteria.</param>
1174 /// <param name="orders">The sort order - used to determine which record is the first one.</param>
1175 /// <returns>A <c>targetType</c> instance or <c>null.</c></returns>
1176 protected internal static object FindFirst(Type targetType, DetachedCriteria detachedCriteria, params Order[] orders)
1178 Array result = SlicedFindAll(targetType, 0, 1, orders, detachedCriteria);
1179 return (result != null && result.Length > 0 ? result.GetValue(0) : null);
1182 /// <summary>
1183 /// Searches and returns the first row.
1184 /// </summary>
1185 /// <param name="targetType">The target type</param>
1186 /// <param name="orders">The sort order - used to determine which record is the first one</param>
1187 /// <param name="criteria">The criteria expression</param>
1188 /// <returns>A <c>targetType</c> instance or <c>null</c></returns>
1189 protected internal static object FindFirst(Type targetType, Order[] orders, params ICriterion[] criteria)
1191 Array result = SlicedFindAll(targetType, 0, 1, orders, criteria);
1192 return (result != null && result.Length > 0 ? result.GetValue(0) : null);
1195 /// <summary>
1196 /// Searches and returns the first row.
1197 /// </summary>
1198 /// <param name="targetType">The target type</param>
1199 /// <param name="criteria">The criteria expression</param>
1200 /// <returns>A <c>targetType</c> instance or <c>null</c></returns>
1201 protected internal static object FindFirst(Type targetType, params ICriterion[] criteria)
1203 return FindFirst(targetType, null, criteria);
1206 #endregion
1208 #region FindOne
1210 /// <summary>
1211 /// Searches and returns a row. If more than one is found,
1212 /// throws <see cref="ActiveRecordException"/>
1213 /// </summary>
1214 /// <param name="targetType">The target type</param>
1215 /// <param name="criteria">The criteria expression</param>
1216 /// <returns>A <c>targetType</c> instance or <c>null</c></returns>
1217 protected internal static object FindOne(Type targetType, params ICriterion[] criteria)
1219 Array result = SlicedFindAll(targetType, 0, 2, criteria);
1221 if (result.Length > 1)
1223 throw new ActiveRecordException(targetType.Name + ".FindOne returned " + result.Length +
1224 " rows. Expecting one or none");
1227 return (result.Length == 0) ? null : result.GetValue(0);
1230 /// <summary>
1231 /// Searches and returns a row. If more than one is found,
1232 /// throws <see cref="ActiveRecordException"/>
1233 /// </summary>
1234 /// <param name="targetType">The target type</param>
1235 /// <param name="criteria">The criteria</param>
1236 /// <returns>A <c>targetType</c> instance or <c>null</c></returns>
1237 protected internal static object FindOne(Type targetType, DetachedCriteria criteria)
1239 Array result = SlicedFindAll(targetType, 0, 2, criteria);
1241 if (result.Length > 1)
1243 throw new ActiveRecordException(targetType.Name + ".FindOne returned " + result.Length +
1244 " rows. Expecting one or none");
1247 return (result.Length == 0) ? null : result.GetValue(0);
1250 #endregion
1252 #region SlicedFindAll
1254 /// <summary>
1255 /// Returns a portion of the query results (sliced)
1256 /// </summary>
1257 /// <param name="targetType">The target type.</param>
1258 /// <param name="firstResult">The number of the first row to retrieve.</param>
1259 /// <param name="maxResults">The maximum number of results retrieved.</param>
1260 /// <param name="orders">An <see cref="Array"/> of <see cref="Order"/> objects.</param>
1261 /// <param name="criteria">The criteria expression</param>
1262 /// <returns>The sliced query results.</returns>
1263 protected internal static Array SlicedFindAll(Type targetType, int firstResult, int maxResults,
1264 Order[] orders, params ICriterion[] criteria)
1266 EnsureInitialized(targetType);
1268 ISession session = holder.CreateSession(targetType);
1272 ICriteria sessionCriteria = session.CreateCriteria(targetType);
1274 foreach(ICriterion cond in criteria)
1276 sessionCriteria.Add(cond);
1279 if (orders != null)
1281 foreach (Order order in orders)
1283 sessionCriteria.AddOrder(order);
1287 sessionCriteria.SetFirstResult(firstResult);
1288 sessionCriteria.SetMaxResults(maxResults);
1290 return SupportingUtils.BuildArray(targetType, sessionCriteria.List());
1292 catch (ValidationException)
1294 holder.FailSession(session);
1296 throw;
1298 catch (Exception ex)
1300 holder.FailSession(session);
1302 throw new ActiveRecordException("Could not perform SlicedFindAll for " + targetType.Name, ex);
1304 finally
1306 holder.ReleaseSession(session);
1310 /// <summary>
1311 /// Returns a portion of the query results (sliced)
1312 /// </summary>
1313 /// <param name="targetType">The target type.</param>
1314 /// <param name="firstResult">The number of the first row to retrieve.</param>
1315 /// <param name="maxResults">The maximum number of results retrieved.</param>
1316 /// <param name="orders">An <see cref="Array"/> of <see cref="Order"/> objects.</param>
1317 /// <param name="criteria">The criteria expression</param>
1318 /// <returns>The sliced query results.</returns>
1319 protected internal static Array SlicedFindAll(Type targetType, int firstResult, int maxResults,
1320 Order[] orders, DetachedCriteria criteria)
1322 EnsureInitialized(targetType);
1324 ISession session = holder.CreateSession(targetType);
1328 ICriteria executableCriteria = criteria.GetExecutableCriteria(session);
1329 AddOrdersToCriteria(executableCriteria, orders);
1330 executableCriteria.SetFirstResult(firstResult);
1331 executableCriteria.SetMaxResults(maxResults);
1333 return SupportingUtils.BuildArray(targetType, executableCriteria.List());
1335 catch(ValidationException)
1337 holder.FailSession(session);
1339 throw;
1341 catch(Exception ex)
1343 holder.FailSession(session);
1345 throw new ActiveRecordException("Could not perform SlicedFindAll for " + targetType.Name, ex);
1347 finally
1349 holder.ReleaseSession(session);
1353 /// <summary>
1354 /// Returns a portion of the query results (sliced)
1355 /// </summary>
1356 /// <param name="targetType">The target type.</param>
1357 /// <param name="firstResult">The number of the first row to retrieve.</param>
1358 /// <param name="maxResults">The maximum number of results retrieved.</param>
1359 /// <param name="criteria">The criteria expression</param>
1360 /// <returns>The sliced query results.</returns>
1361 protected internal static Array SlicedFindAll(Type targetType, int firstResult, int maxResults,
1362 params ICriterion[] criteria)
1364 return SlicedFindAll(targetType, firstResult, maxResults, null, criteria);
1367 /// <summary>
1368 /// Returns a portion of the query results (sliced)
1369 /// </summary>
1370 /// <param name="targetType">The target type.</param>
1371 /// <param name="firstResult">The number of the first row to retrieve.</param>
1372 /// <param name="maxResults">The maximum number of results retrieved.</param>
1373 /// <param name="criteria">The criteria expression</param>
1374 /// <returns>The sliced query results.</returns>
1375 protected internal static Array SlicedFindAll(Type targetType, int firstResult, int maxResults,
1376 DetachedCriteria criteria)
1378 return SlicedFindAll(targetType, firstResult, maxResults, null, criteria);
1381 #endregion
1383 #endregion
1385 #region protected internal
1387 /// <summary>
1388 /// Invokes the specified delegate passing a valid
1389 /// NHibernate session. Used for custom NHibernate queries.
1390 /// </summary>
1391 /// <param name="call">The delegate instance</param>
1392 /// <returns>Whatever is returned by the delegate invocation</returns>
1393 protected virtual internal object Execute(NHibernateDelegate call)
1395 return Execute(GetType(), call, this);
1398 #endregion
1400 #region public virtual
1402 /// <summary>
1403 /// Saves the instance information to the database.
1404 /// May Create or Update the instance depending
1405 /// on whether it has a valid ID.
1406 /// </summary>
1407 /// <remarks>
1408 /// If within a <see cref="SessionScope"/> the operation
1409 /// is going to be on hold until NHibernate (or you) decides to flush
1410 /// the session.
1411 /// </remarks>
1412 public virtual void Save()
1414 Save(this);
1417 /// <summary>
1418 /// Saves the instance information to the database.
1419 /// May Create or Update the instance depending
1420 /// on whether it has a valid ID.
1421 /// </summary>
1422 /// <remarks>
1423 /// Even within a <see cref="SessionScope"/> the operation
1424 /// is going to be flushed immediately. This might have side effects such as
1425 /// flushing (persisting) others operations that were on hold.
1426 /// </remarks>
1427 public virtual void SaveAndFlush()
1429 SaveAndFlush(this);
1432 /// <summary>
1433 /// Saves a copy of the instance information to the database.
1434 /// May Create or Update the instance depending
1435 /// on whether it has a valid ID.
1436 /// </summary>
1437 /// <returns>An saved ActiveRecord instance</returns>
1438 /// <remarks>
1439 /// If within a <see cref="SessionScope"/> the operation
1440 /// is going to be on hold until NHibernate (or you) decides to flush
1441 /// the session.
1442 /// </remarks>
1443 public virtual object SaveCopy()
1445 return SaveCopy(this);
1448 /// <summary>
1449 /// Saves a copy of the instance information to the database.
1450 /// May Create or Update the instance depending
1451 /// on whether it has a valid ID.
1452 /// </summary>
1453 /// <returns>A saved ActiveRecord instance</returns>
1454 /// <remarks>
1455 /// Even within a <see cref="SessionScope"/> the operation
1456 /// is going to be flushed immediately. This might have side effects such as
1457 /// flushing (persisting) others operations that were on hold.
1458 /// </remarks>
1459 public virtual object SaveCopyAndFlush()
1461 return SaveCopyAndFlush(this);
1464 /// <summary>
1465 /// Creates (Saves) a new instance to the database.
1466 /// </summary>
1467 /// <remarks>
1468 /// If within a <see cref="SessionScope"/> the operation
1469 /// is going to be on hold until NHibernate (or you) decides to flush
1470 /// the session.
1471 /// </remarks>
1472 public virtual void Create()
1474 Create(this);
1477 /// <summary>
1478 /// Creates (Saves) a new instance to the database.
1479 /// </summary>
1480 /// <remarks>
1481 /// Even within a <see cref="SessionScope"/> the operation
1482 /// is going to be flushed immediately. This might have side effects such as
1483 /// flushing (persisting) others operations that were on hold.
1484 /// </remarks>
1485 public virtual void CreateAndFlush()
1487 CreateAndFlush(this);
1490 /// <summary>
1491 /// Persists the modification on the instance
1492 /// state to the database.
1493 /// </summary>
1494 /// <remarks>
1495 /// If within a <see cref="SessionScope"/> the operation
1496 /// is going to be on hold until NHibernate (or you) decides to flush
1497 /// the session.
1498 /// </remarks>
1499 public virtual void Update()
1501 Update(this);
1504 /// <summary>
1505 /// Persists the modification on the instance
1506 /// state to the database.
1507 /// </summary>
1508 /// <remarks>
1509 /// Even within a <see cref="SessionScope"/> the operation
1510 /// is going to be flushed immediately. This might have side effects such as
1511 /// flushing (persisting) others operations that were on hold.
1512 /// </remarks>
1513 public virtual void UpdateAndFlush()
1515 UpdateAndFlush(this);
1518 /// <summary>
1519 /// Deletes the instance from the database.
1520 /// </summary>
1521 /// <remarks>
1522 /// If within a <see cref="SessionScope"/> the operation
1523 /// is going to be on hold until NHibernate (or you) decides to flush
1524 /// the session.
1525 /// </remarks>
1526 public virtual void Delete()
1528 Delete(this);
1531 /// <summary>
1532 /// Deletes the instance from the database.
1533 /// </summary>
1534 /// <remarks>
1535 /// Even within a <see cref="SessionScope"/> the operation
1536 /// is going to be flushed immediately. This might have side effects such as
1537 /// flushing (persisting) others operations that were on hold.
1538 /// </remarks>
1539 public virtual void DeleteAndFlush()
1541 DeleteAndFlush(this);
1544 /// <summary>
1545 /// Refresh the instance from the database.
1546 /// </summary>
1547 public virtual void Refresh()
1549 Refresh(this);
1552 #endregion
1554 #region public override
1556 /// <summary>
1557 /// Return the type of the object with its PK value.
1558 /// Useful for logging/debugging
1559 /// </summary>
1560 /// <returns>A string representation of this object.</returns>
1561 public override String ToString()
1563 ActiveRecordModel model = GetModel(GetType());
1565 if (model == null || model.PrimaryKey == null)
1567 return base.ToString();
1570 PrimaryKeyModel pkModel = model.PrimaryKey;
1572 object pkVal = pkModel.Property.GetValue(this, null);
1574 return base.ToString() + "#" + pkVal;
1577 #endregion
1579 #region Sort Order
1581 internal static Order[] PropertyNamesToOrderArray(bool asc, params string[] propertyNames)
1583 Order[] orders = new Order[propertyNames.Length];
1585 for (int i = 0; i < propertyNames.Length; i++)
1587 orders[i] = new Order(propertyNames[i], asc);
1589 return orders;
1592 /// <summary>
1593 /// Ascending Order
1594 /// </summary>
1595 /// <remarks>
1596 /// Returns an array of Ascending <see cref="Order"/> instances specifing which properties to use to
1597 /// order a result.
1598 /// </remarks>
1599 /// <param name="propertyNames">List of property names to order by ascending</param>
1600 /// <returns>Array of <see cref="Order"/> objects suitable for passing to FindAll and variants</returns>
1601 public static Order[] Asc(params string[] propertyNames)
1603 return PropertyNamesToOrderArray(true, propertyNames);
1606 /// <summary>
1607 /// Descending Order
1608 /// </summary>
1609 /// <remarks>
1610 /// Returns an array of Descending <see cref="Order"/> instances specifing which properties to use to
1611 /// order a result.
1612 /// </remarks>
1613 /// <param name="propertyNames">List of property names to order by descending</param>
1614 /// <returns>Array of <see cref="Order"/> objects suitable for passing to FindAll and variants</returns>
1615 public static Order[] Desc(params string[] propertyNames)
1617 return PropertyNamesToOrderArray(false, propertyNames);
1620 #endregion