1 // Copyright 2004-2007 Castle Project - http://www.castleproject.org/
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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
18 using System
.Collections
;
19 using Castle
.ActiveRecord
.Framework
;
20 using Castle
.ActiveRecord
.Framework
.Internal
;
21 using Castle
.ActiveRecord
.Queries
;
24 using NHibernate
.Expression
;
25 using Castle
.Components
.Validator
;
28 /// Allow custom executions using the NHibernate's ISession.
30 public delegate object NHibernateDelegate(ISession session
, object instance
);
33 /// Base class for all ActiveRecord classes. Implements
34 /// all the functionality to simplify the code on the
38 public abstract class ActiveRecordBase
: ActiveRecordHooksBase
41 /// The global holder for the session factories.
43 protected internal static ISessionFactoryHolder holder
;
45 #region internal static
47 internal static void EnsureInitialized(Type type
)
51 String message
= String
.Format("An ActiveRecord class ({0}) was used but the framework seems not " +
52 "properly initialized. Did you forget about ActiveRecordStarter.Initialize() ?",
54 throw new ActiveRecordException(message
);
56 if (type
!= typeof(ActiveRecordBase
) && GetModel(type
) == null)
58 String message
= String
.Format("You have accessed an ActiveRecord class that wasn't properly initialized. " +
59 "The only explanation is that the call to ActiveRecordStarter.Initialize() didn't include {0} class",
61 throw new ActiveRecordException(message
);
68 /// <param name="arType">The type.</param>
69 /// <param name="model">The model.</param>
70 internal static void Register(Type arType
, ActiveRecordModel model
)
72 ActiveRecordModel
.Register(arType
, model
);
78 /// <param name="arType">The type.</param>
79 /// <returns>An <see cref="ActiveRecordModel"/></returns>
80 internal static ActiveRecordModel
GetModel(Type arType
)
82 return ActiveRecordModel
.GetModel(arType
);
87 #region protected internal static
89 #region Create/Update/Save/Delete/DeleteAll/Refresh
94 /// Creates (Saves) a new instance to the database.
96 /// <param name="instance">The ActiveRecord instance to be created on the database</param>
97 protected internal static void Create(object instance
)
99 InternalCreate(instance
, false);
103 /// Creates (Saves) a new instance to the database and flushes the session.
105 /// <param name="instance">The ActiveRecord instance to be created on the database</param>
106 protected internal static void CreateAndFlush(object instance
)
108 InternalCreate(instance
, true);
112 /// Creates (Saves) a new instance to the database.
114 /// <param name="instance">The ActiveRecord instance to be created on the database</param>
115 /// <param name="flush">if set to <c>true</c>, the operation will be followed by a session flush.</param>
116 private static void InternalCreate(object instance
, bool flush
)
118 if (instance
== null) throw new ArgumentNullException("instance");
120 EnsureInitialized(instance
.GetType());
122 ISession session
= holder
.CreateSession(instance
.GetType());
126 session
.Save(instance
);
135 holder
.FailSession(session
);
137 // NHibernate catches our ValidationException, and as such it is the innerexception here
138 if (ex
.InnerException
is ValidationException
)
140 throw ex
.InnerException
;
144 throw new ActiveRecordException("Could not perform Create for " + instance
.GetType().Name
, ex
);
149 holder
.ReleaseSession(session
);
158 /// Deletes the instance from the database.
160 /// <param name="instance">The ActiveRecord instance to be deleted</param>
161 protected internal static void Delete(object instance
)
163 InternalDelete(instance
, false);
167 /// Deletes the instance from the database and flushes the session.
169 /// <param name="instance">The ActiveRecord instance to be deleted</param>
170 protected internal static void DeleteAndFlush(object instance
)
172 InternalDelete(instance
, true);
176 /// Deletes the instance from the database.
178 /// <param name="instance">The ActiveRecord instance to be deleted</param>
179 /// <param name="flush">if set to <c>true</c>, the operation will be followed by a session flush.</param>
180 private static void InternalDelete(object instance
, bool flush
)
182 if (instance
== null) throw new ArgumentNullException("instance");
184 EnsureInitialized(instance
.GetType());
186 ISession session
= holder
.CreateSession(instance
.GetType());
190 session
.Delete(instance
);
197 catch (ValidationException
)
199 holder
.FailSession(session
);
206 holder
.FailSession(session
);
208 throw new ActiveRecordException("Could not perform Delete for " + instance
.GetType().Name
, ex
);
212 holder
.ReleaseSession(session
);
221 /// From NHibernate documentation:
222 /// Persist all reachable transient objects, reusing the current identifier
223 /// values. Note that this will not trigger the Interceptor of the Session.
225 /// <param name="instance">The instance.</param>
226 /// <param name="replicationMode">The replication mode.</param>
227 protected internal static void Replicate(object instance
, ReplicationMode replicationMode
)
229 if (instance
== null)
231 throw new ArgumentNullException("instance");
234 EnsureInitialized(instance
.GetType());
236 ISession session
= holder
.CreateSession(instance
.GetType());
240 session
.Replicate(instance
, replicationMode
);
244 holder
.FailSession(session
);
246 // NHibernate catches our ValidationException, and as such it is the innerexception here
247 if (ex
.InnerException
is ValidationException
)
249 throw ex
.InnerException
;
253 throw new ActiveRecordException("Could not perform Replicate for " + instance
.GetType().Name
, ex
);
258 holder
.ReleaseSession(session
);
267 /// Refresh the instance from the database.
269 /// <param name="instance">The ActiveRecord instance to be reloaded</param>
270 protected internal static void Refresh(object instance
)
272 if (instance
== null) throw new ArgumentNullException("instance");
274 EnsureInitialized(instance
.GetType());
276 ISession session
= holder
.CreateSession(instance
.GetType());
280 session
.Refresh(instance
);
284 holder
.FailSession(session
);
286 // NHibernate catches our ValidationException, and as such it is the innerexception here
287 if (ex
.InnerException
is ValidationException
)
289 throw ex
.InnerException
;
293 throw new ActiveRecordException("Could not perform Refresh for " + instance
.GetType().Name
, ex
);
298 holder
.ReleaseSession(session
);
307 /// Deletes all rows for the specified ActiveRecord type
310 /// This method is usually useful for test cases.
312 /// <param name="type">ActiveRecord type on which the rows on the database should be deleted</param>
313 protected internal static void DeleteAll(Type type
)
315 EnsureInitialized(type
);
317 ISession session
= holder
.CreateSession(type
);
321 session
.Delete(String
.Format("from {0}", type
.Name
));
325 catch (ValidationException
)
327 holder
.FailSession(session
);
333 holder
.FailSession(session
);
335 throw new ActiveRecordException("Could not perform DeleteAll for " + type
.Name
, ex
);
339 holder
.ReleaseSession(session
);
344 /// Deletes all rows for the specified ActiveRecord type that matches
345 /// the supplied HQL condition
348 /// This method is usually useful for test cases.
350 /// <param name="type">ActiveRecord type on which the rows on the database should be deleted</param>
351 /// <param name="where">HQL condition to select the rows to be deleted</param>
352 protected internal static void DeleteAll(Type type
, String where
)
354 EnsureInitialized(type
);
356 ISession session
= holder
.CreateSession(type
);
360 session
.Delete(String
.Format("from {0} where {1}", type
.Name
, where
));
364 catch (ValidationException
)
366 holder
.FailSession(session
);
372 holder
.FailSession(session
);
374 throw new ActiveRecordException("Could not perform DeleteAll for " + type
.Name
, ex
);
378 holder
.ReleaseSession(session
);
383 /// Deletes all <paramref name="targetType" /> objects, based on the primary keys
384 /// supplied on <paramref name="pkValues" />.
386 /// <param name="targetType">The target ActiveRecord type</param>
387 /// <param name="pkValues">A list of primary keys</param>
388 /// <returns>The number of objects deleted</returns>
389 protected internal static int DeleteAll(Type targetType
, IEnumerable pkValues
)
391 if (pkValues
== null)
398 foreach (object pk
in pkValues
)
400 Object obj
= FindByPrimaryKey(targetType
, pk
, false);
404 ActiveRecordBase arBase
= obj
as ActiveRecordBase
;
408 arBase
.Delete(); // in order to allow override of the virtual "Delete()" method
427 /// Persists the modification on the instance
428 /// state to the database.
430 /// <param name="instance">The ActiveRecord instance to be updated on the database</param>
431 protected internal static void Update(object instance
)
433 InternalUpdate(instance
, false);
437 /// Persists the modification on the instance
438 /// state to the database and flushes the session.
440 /// <param name="instance">The ActiveRecord instance to be updated on the database</param>
441 protected internal static void UpdateAndFlush(object instance
)
443 InternalUpdate(instance
, true);
447 /// Persists the modification on the instance
448 /// state to the database.
450 /// <param name="instance">The ActiveRecord instance to be updated on the database</param>
451 /// <param name="flush">if set to <c>true</c>, the operation will be followed by a session flush.</param>
452 private static void InternalUpdate(object instance
, bool flush
)
454 if (instance
== null) throw new ArgumentNullException("instance");
456 EnsureInitialized(instance
.GetType());
458 ISession session
= holder
.CreateSession(instance
.GetType());
462 session
.Update(instance
);
469 catch (ValidationException
)
471 holder
.FailSession(session
);
477 holder
.FailSession(session
);
479 throw new ActiveRecordException("Could not perform Update for " + instance
.GetType().Name
, ex
);
483 holder
.ReleaseSession(session
);
492 /// Saves the instance to the database. If the primary key is unitialized
493 /// it creates the instance on the database. Otherwise it updates it.
495 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
496 /// or <see cref="Update()"/> instead.
499 /// <param name="instance">The ActiveRecord instance to be saved</param>
500 protected internal static void Save(object instance
)
502 InternalSave(instance
, false);
506 /// Saves the instance to the database and flushes the session. If the primary key is unitialized
507 /// it creates the instance on the database. Otherwise it updates it.
509 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
510 /// or <see cref="Update()"/> instead.
513 /// <param name="instance">The ActiveRecord instance to be saved</param>
514 protected internal static void SaveAndFlush(object instance
)
516 InternalSave(instance
, true);
520 /// Saves a copy of the instance to the database. If the primary key is unitialized
521 /// it creates the instance on the database. Otherwise it updates it.
523 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
524 /// or <see cref="Update()"/> instead.
527 /// <param name="instance">The transient instance to be saved</param>
528 /// <returns>The saved ActiveRecord instance</returns>
529 protected internal static object SaveCopy(object instance
)
531 return InternalSaveCopy(instance
, false);
535 /// Saves a copy of the instance to the database and flushes the session. If the primary key is unitialized
536 /// it creates the instance on the database. Otherwise it updates it.
538 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
539 /// or <see cref="Update()"/> instead.
542 /// <param name="instance">The transient instance to be saved</param>
543 /// <returns>The saved ActiveRecord instance</returns>
544 protected internal static object SaveCopyAndFlush(object instance
)
546 return InternalSaveCopy(instance
, true);
550 /// Saves the instance to the database. If the primary key is unitialized
551 /// it creates the instance on the database. Otherwise it updates it.
553 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
554 /// or <see cref="Update()"/> instead.
557 /// <param name="instance">The ActiveRecord instance to be saved</param>
558 /// <param name="flush">if set to <c>true</c>, the operation will be followed by a session flush.</param>
559 private static void InternalSave(object instance
, bool flush
)
561 if (instance
== null) throw new ArgumentNullException("instance");
563 EnsureInitialized(instance
.GetType());
565 ISession session
= holder
.CreateSession(instance
.GetType());
569 session
.SaveOrUpdate(instance
);
576 catch (ValidationException
)
578 holder
.FailSession(session
);
584 holder
.FailSession(session
);
586 // NHibernate catches our ValidationException on Create so it could be the innerexception here
587 if (ex
.InnerException
is ValidationException
)
589 throw ex
.InnerException
;
593 throw new ActiveRecordException("Could not perform Save for " + instance
.GetType().Name
, ex
);
598 holder
.ReleaseSession(session
);
603 /// Saves a copy of the instance to the database. If the primary key is unitialized
604 /// it creates the instance on the database. Otherwise it updates it.
606 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
607 /// or <see cref="Update()"/> instead.
610 /// <param name="instance">The transient instance to be saved</param>
611 /// <param name="flush">if set to <c>true</c>, the operation will be followed by a session flush.</param>
612 /// <returns>The saved ActiveRecord instance.</returns>
613 private static object InternalSaveCopy(object instance
, bool flush
)
615 if (instance
== null) throw new ArgumentNullException("instance");
617 EnsureInitialized(instance
.GetType());
619 ISession session
= holder
.CreateSession(instance
.GetType());
623 object persistent
= session
.SaveOrUpdateCopy(instance
);
632 catch (ValidationException
)
634 holder
.FailSession(session
);
640 holder
.FailSession(session
);
642 // NHibernate catches our ValidationException on Create so it could be the innerexception here
643 if (ex
.InnerException
is ValidationException
)
645 throw ex
.InnerException
;
649 throw new ActiveRecordException("Could not perform SaveCopy for " + instance
.GetType().Name
, ex
);
654 holder
.ReleaseSession(session
);
665 /// Invokes the specified delegate passing a valid
666 /// NHibernate session. Used for custom NHibernate queries.
668 /// <param name="targetType">The target ActiveRecordType</param>
669 /// <param name="call">The delegate instance</param>
670 /// <param name="instance">The ActiveRecord instance</param>
671 /// <returns>Whatever is returned by the delegate invocation</returns>
672 protected internal static object Execute(Type targetType
, NHibernateDelegate call
, object instance
)
674 if (targetType
== null) throw new ArgumentNullException("targetType", "Target type must be informed");
675 if (call
== null) throw new ArgumentNullException("call", "Delegate must be passed");
677 EnsureInitialized(targetType
);
679 ISession session
= holder
.CreateSession(targetType
);
683 return call(session
, instance
);
685 catch (ValidationException
)
687 holder
.FailSession(session
);
693 holder
.FailSession(session
);
695 throw new ActiveRecordException("Error performing Execute for " + targetType
.Name
, ex
);
699 holder
.ReleaseSession(session
);
708 /// Enumerates the query
709 /// Note: only use if you expect most of the values to exist on the second level cache.
711 /// <param name="query">The query.</param>
712 /// <returns>An <see cref="IEnumerable"/></returns>
713 protected internal static IEnumerable
EnumerateQuery(IActiveRecordQuery query
)
715 Type rootType
= query
.RootType
;
717 EnsureInitialized(rootType
);
719 ISession session
= holder
.CreateSession(rootType
);
723 return query
.Enumerate(session
);
727 holder
.FailSession(session
);
729 throw new ActiveRecordException("Could not perform EnumerateQuery for " + rootType
.Name
, ex
);
733 holder
.ReleaseSession(session
);
742 /// Executes the query.
744 /// <param name="query">The query.</param>
745 /// <returns>The query result.</returns>
746 public static object ExecuteQuery(IActiveRecordQuery query
)
748 Type rootType
= query
.RootType
;
750 EnsureInitialized(rootType
);
752 ISession session
= holder
.CreateSession(rootType
);
756 return query
.Execute(session
);
760 holder
.FailSession(session
);
762 throw new ActiveRecordException("Could not perform ExecuteQuery for " + rootType
.Name
, ex
);
766 holder
.ReleaseSession(session
);
775 /// Returns the number of records of the specified
776 /// type in the database
781 /// public class User : ActiveRecordBase
785 /// public static int CountAllUsers()
787 /// return Count(typeof(User));
792 /// <param name="targetType">The target type.</param>
793 /// <returns>The count result</returns>
794 protected internal static int Count(Type targetType
)
796 CountQuery query
= new CountQuery(targetType
);
798 return (int)ExecuteQuery(query
);
802 /// Returns the number of records of the specified
803 /// type in the database
808 /// public class User : ActiveRecordBase
812 /// public static int CountAllUsersLocked()
814 /// return Count(typeof(User), "IsLocked = ?", true);
819 /// <param name="targetType">The target type.</param>
820 /// <param name="filter">A sql where string i.e. Person=? and DOB > ?</param>
821 /// <param name="args">Positional parameters for the filter string</param>
822 /// <returns>The count result</returns>
823 protected internal static int Count(Type targetType
, string filter
, params object[] args
)
825 CountQuery query
= new CountQuery(targetType
, filter
, args
);
827 return (int)ExecuteQuery(query
);
831 /// Returns the number of records of the specified
832 /// type in the database
834 /// <param name="targetType">The target type.</param>
835 /// <param name="criteria">The criteria expression</param>
836 /// <returns>The count result</returns>
837 protected internal static int Count(Type targetType
, ICriterion
[] criteria
)
839 CountQuery query
= new CountQuery(targetType
, criteria
);
841 return (int) ExecuteQuery(query
);
845 /// Returns the number of records of the specified
846 /// type in the database
848 /// <param name="targetType">The target type.</param>
849 /// <param name="detachedCriteria">The criteria expression</param>
850 /// <returns>The count result</returns>
851 protected internal static int Count(Type targetType
, DetachedCriteria detachedCriteria
)
853 CountQuery query
= new CountQuery(targetType
, detachedCriteria
);
855 return (int)ExecuteQuery(query
);
863 /// Check if there is any records in the db for the target type
865 /// <param name="targetType">The target type.</param>
866 /// <returns><c>true</c> if there's at least one row</returns>
867 protected internal static bool Exists(Type targetType
)
869 return Count(targetType
) > 0;
873 /// Check if there is any records in the db for the target type
875 /// <param name="targetType">The target type.</param>
876 /// <param name="filter">A sql where string i.e. Person=? and DOB > ?</param>
877 /// <param name="args">Positional parameters for the filter string</param>
878 /// <returns><c>true</c> if there's at least one row</returns>
879 protected internal static bool Exists(Type targetType
, string filter
, params object[] args
)
881 return Count(targetType
, filter
, args
) > 0;
885 /// Check if the <paramref name="id"/> exists in the database.
887 /// <param name="targetType">The target type.</param>
888 /// <param name="id">The id to check on</param>
889 /// <returns><c>true</c> if the ID exists; otherwise <c>false</c>.</returns>
890 protected internal static bool Exists(Type targetType
, object id
)
892 EnsureInitialized(targetType
);
893 ISession session
= holder
.CreateSession(targetType
);
897 return session
.Get(targetType
, id
) != null;
901 throw new ActiveRecordException("Could not perform Exists for " + targetType
.Name
+ ". Id: " + id
, ex
);
905 holder
.ReleaseSession(session
);
910 /// Check if any instance matching the criteria exists in the database.
912 /// <param name="targetType">The target type.</param>
913 /// <param name="criteria">The criteria expression</param>
914 /// <returns><c>true</c> if an instance is found; otherwise <c>false</c>.</returns>
915 protected internal static bool Exists(Type targetType
, params ICriterion
[] criteria
)
917 return Count(targetType
, criteria
) > 0;
921 /// Check if any instance matching the criteria exists in the database.
923 /// <param name="targetType">The target type.</param>
924 /// <param name="detachedCriteria">The criteria expression</param>
925 /// <returns><c>true</c> if an instance is found; otherwise <c>false</c>.</returns>
926 protected internal static bool Exists(Type targetType
, DetachedCriteria detachedCriteria
)
928 return Count(targetType
, detachedCriteria
) > 0;
936 /// Returns all instances found for the specified type according to the criteria
938 /// <param name="targetType">The target type.</param>
939 /// <param name="detachedCriteria">The criteria.</param>
940 /// <param name="orders">An <see cref="Array"/> of <see cref="Order"/> objects.</param>
941 /// <returns>The <see cref="Array"/> of results.</returns>
942 protected internal static Array
FindAll(Type targetType
, DetachedCriteria detachedCriteria
, params Order
[] orders
)
944 EnsureInitialized(targetType
);
946 ISession session
= holder
.CreateSession(targetType
);
950 ICriteria criteria
= detachedCriteria
.GetExecutableCriteria(session
);
952 AddOrdersToCriteria(criteria
, orders
);
954 return SupportingUtils
.BuildArray(targetType
, criteria
.List());
956 catch(ValidationException
)
958 holder
.FailSession(session
);
964 holder
.FailSession(session
);
966 throw new ActiveRecordException("Could not perform FindAll for " + targetType
.Name
, ex
);
970 holder
.ReleaseSession(session
);
975 /// Returns all instances found for the specified type.
977 /// <param name="targetType">The target type.</param>
978 /// <returns>The <see cref="Array"/> of results</returns>
979 protected internal static Array
FindAll(Type targetType
)
981 return FindAll(targetType
, (Order
[])null);
985 /// Returns all instances found for the specified type
986 /// using sort orders and criteria.
988 /// <param name="targetType">The The target type.</param>
989 /// <param name="orders">An <see cref="Array"/> of <see cref="Order"/> objects.</param>
990 /// <param name="criteria">The criteria expression</param>
991 /// <returns>The <see cref="Array"/> of results.</returns>
992 protected internal static Array
FindAll(Type targetType
, Order
[] orders
, params ICriterion
[] criteria
)
994 EnsureInitialized(targetType
);
996 ISession session
= holder
.CreateSession(targetType
);
1000 ICriteria sessionCriteria
= session
.CreateCriteria(targetType
);
1002 foreach(ICriterion cond
in criteria
)
1004 sessionCriteria
.Add(cond
);
1007 AddOrdersToCriteria(sessionCriteria
, orders
);
1009 return SupportingUtils
.BuildArray(targetType
, sessionCriteria
.List());
1011 catch (ValidationException
)
1013 holder
.FailSession(session
);
1017 catch (Exception ex
)
1019 holder
.FailSession(session
);
1021 throw new ActiveRecordException("Could not perform FindAll for " + targetType
.Name
, ex
);
1025 holder
.ReleaseSession(session
);
1029 private static void AddOrdersToCriteria(ICriteria criteria
, Order
[] orders
)
1033 foreach (Order order
in orders
)
1035 criteria
.AddOrder(order
);
1041 /// Returns all instances found for the specified type
1044 /// <param name="targetType">The target type.</param>
1045 /// <param name="criteria">The criteria expression</param>
1046 /// <returns>The <see cref="Array"/> of results.</returns>
1047 protected internal static Array
FindAll(Type targetType
, params ICriterion
[] criteria
)
1049 return FindAll(targetType
, null, criteria
);
1054 #region FindAllByProperty
1057 /// Finds records based on a property value - automatically converts null values to IS NULL style queries.
1059 /// <param name="targetType">The target type</param>
1060 /// <param name="property">A property name (not a column name)</param>
1061 /// <param name="value">The value to be equals to</param>
1062 /// <returns>The <see cref="Array"/> of results.</returns>
1063 protected internal static Array
FindAllByProperty(Type targetType
, String property
, object value)
1065 ICriterion criteria
= (value == null) ? Expression
.IsNull(property
) : Expression
.Eq(property
, value);
1066 return FindAll(targetType
, criteria
);
1070 /// Finds records based on a property value - automatically converts null values to IS NULL style queries.
1072 /// <param name="targetType">The target type</param>
1073 /// <param name="orderByColumn">The column name to be ordered ASC</param>
1074 /// <param name="property">A property name (not a column name)</param>
1075 /// <param name="value">The value to be equals to</param>
1076 /// <returns>The <see cref="Array"/> of results.</returns>
1077 protected internal static Array
FindAllByProperty(Type targetType
, String orderByColumn
, String property
, object value)
1079 ICriterion criteria
= (value == null) ? Expression
.IsNull(property
) : Expression
.Eq(property
, value);
1080 return FindAll(targetType
, new Order
[] {Order.Asc(orderByColumn)}
, criteria
);
1085 #region FindByPrimaryKey
1088 /// Finds an object instance by an unique ID
1090 /// <param name="targetType">The AR subclass type</param>
1091 /// <param name="id">ID value</param>
1092 /// <returns>The object instance.</returns>
1093 protected internal static object FindByPrimaryKey(Type targetType
, object id
)
1095 return FindByPrimaryKey(targetType
, id
, true);
1099 /// Finds an object instance by an unique ID
1101 /// <param name="targetType">The AR subclass type</param>
1102 /// <param name="id">ID value</param>
1103 /// <param name="throwOnNotFound"><c>true</c> if you want to catch an exception
1104 /// if the object is not found</param>
1105 /// <returns>The object instance.</returns>
1106 /// <exception cref="ObjectNotFoundException">if <c>throwOnNotFound</c> is set to
1107 /// <c>true</c> and the row is not found</exception>
1108 protected internal static object FindByPrimaryKey(Type targetType
, object id
, bool throwOnNotFound
)
1110 EnsureInitialized(targetType
);
1111 bool hasScope
= holder
.ThreadScopeInfo
.HasInitializedScope
;
1112 ISession session
= holder
.CreateSession(targetType
);
1117 // Load() and Get() has different semantics with regard to the way they
1118 // handle null values, Get() _must_ check that the value exists, Load() is allowed
1119 // to return an uninitialized proxy that will throw when you access it later.
1120 // in order to play well with proxies, we need to use this approach.
1121 if (throwOnNotFound
)
1123 loaded
= session
.Load(targetType
, id
);
1127 loaded
= session
.Get(targetType
, id
);
1129 //If we are not in a scope, we want to initialize the entity eagerly, since other wise the
1130 //user will get an exception when it access the entity's property, and it will try to lazy load itself and find that
1131 //it has no session.
1132 //If we are in a scope, it is the user responsability to keep the scope alive if he wants to use
1135 NHibernateUtil
.Initialize(loaded
);
1139 catch (ObjectNotFoundException ex
)
1141 holder
.FailSession(session
);
1143 String message
= String
.Format("Could not find {0} with id {1}", targetType
.Name
, id
);
1144 throw new NotFoundException(message
, ex
);
1146 catch (ValidationException
)
1148 holder
.FailSession(session
);
1152 catch (Exception ex
)
1154 holder
.FailSession(session
);
1156 throw new ActiveRecordException("Could not perform FindByPrimaryKey for " + targetType
.Name
+ ". Id: " + id
, ex
);
1160 holder
.ReleaseSession(session
);
1169 /// Searches and returns the first row.
1171 /// <param name="targetType">The target type.</param>
1172 /// <param name="detachedCriteria">The criteria.</param>
1173 /// <param name="orders">The sort order - used to determine which record is the first one.</param>
1174 /// <returns>A <c>targetType</c> instance or <c>null.</c></returns>
1175 protected internal static object FindFirst(Type targetType
, DetachedCriteria detachedCriteria
, params Order
[] orders
)
1177 Array result
= SlicedFindAll(targetType
, 0, 1, orders
, detachedCriteria
);
1178 return (result
!= null && result
.Length
> 0 ? result
.GetValue(0) : null);
1182 /// Searches and returns the first row.
1184 /// <param name="targetType">The target type</param>
1185 /// <param name="orders">The sort order - used to determine which record is the first one</param>
1186 /// <param name="criteria">The criteria expression</param>
1187 /// <returns>A <c>targetType</c> instance or <c>null</c></returns>
1188 protected internal static object FindFirst(Type targetType
, Order
[] orders
, params ICriterion
[] criteria
)
1190 Array result
= SlicedFindAll(targetType
, 0, 1, orders
, criteria
);
1191 return (result
!= null && result
.Length
> 0 ? result
.GetValue(0) : null);
1195 /// Searches and returns the first row.
1197 /// <param name="targetType">The target type</param>
1198 /// <param name="criteria">The criteria expression</param>
1199 /// <returns>A <c>targetType</c> instance or <c>null</c></returns>
1200 protected internal static object FindFirst(Type targetType
, params ICriterion
[] criteria
)
1202 return FindFirst(targetType
, null, criteria
);
1210 /// Searches and returns a row. If more than one is found,
1211 /// throws <see cref="ActiveRecordException"/>
1213 /// <param name="targetType">The target type</param>
1214 /// <param name="criteria">The criteria expression</param>
1215 /// <returns>A <c>targetType</c> instance or <c>null</c></returns>
1216 protected internal static object FindOne(Type targetType
, params ICriterion
[] criteria
)
1218 Array result
= SlicedFindAll(targetType
, 0, 2, criteria
);
1220 if (result
.Length
> 1)
1222 throw new ActiveRecordException(targetType
.Name
+ ".FindOne returned " + result
.Length
+
1223 " rows. Expecting one or none");
1226 return (result
.Length
== 0) ? null : result
.GetValue(0);
1230 /// Searches and returns a row. If more than one is found,
1231 /// throws <see cref="ActiveRecordException"/>
1233 /// <param name="targetType">The target type</param>
1234 /// <param name="criteria">The criteria</param>
1235 /// <returns>A <c>targetType</c> instance or <c>null</c></returns>
1236 protected internal static object FindOne(Type targetType
, DetachedCriteria criteria
)
1238 Array result
= SlicedFindAll(targetType
, 0, 2, criteria
);
1240 if (result
.Length
> 1)
1242 throw new ActiveRecordException(targetType
.Name
+ ".FindOne returned " + result
.Length
+
1243 " rows. Expecting one or none");
1246 return (result
.Length
== 0) ? null : result
.GetValue(0);
1251 #region SlicedFindAll
1254 /// Returns a portion of the query results (sliced)
1256 /// <param name="targetType">The target type.</param>
1257 /// <param name="firstResult">The number of the first row to retrieve.</param>
1258 /// <param name="maxResults">The maximum number of results retrieved.</param>
1259 /// <param name="orders">An <see cref="Array"/> of <see cref="Order"/> objects.</param>
1260 /// <param name="criteria">The criteria expression</param>
1261 /// <returns>The sliced query results.</returns>
1262 protected internal static Array
SlicedFindAll(Type targetType
, int firstResult
, int maxResults
,
1263 Order
[] orders
, params ICriterion
[] criteria
)
1265 EnsureInitialized(targetType
);
1267 ISession session
= holder
.CreateSession(targetType
);
1271 ICriteria sessionCriteria
= session
.CreateCriteria(targetType
);
1273 foreach(ICriterion cond
in criteria
)
1275 sessionCriteria
.Add(cond
);
1280 foreach (Order order
in orders
)
1282 sessionCriteria
.AddOrder(order
);
1286 sessionCriteria
.SetFirstResult(firstResult
);
1287 sessionCriteria
.SetMaxResults(maxResults
);
1289 return SupportingUtils
.BuildArray(targetType
, sessionCriteria
.List());
1291 catch (ValidationException
)
1293 holder
.FailSession(session
);
1297 catch (Exception ex
)
1299 holder
.FailSession(session
);
1301 throw new ActiveRecordException("Could not perform SlicedFindAll for " + targetType
.Name
, ex
);
1305 holder
.ReleaseSession(session
);
1310 /// Returns a portion of the query results (sliced)
1312 /// <param name="targetType">The target type.</param>
1313 /// <param name="firstResult">The number of the first row to retrieve.</param>
1314 /// <param name="maxResults">The maximum number of results retrieved.</param>
1315 /// <param name="orders">An <see cref="Array"/> of <see cref="Order"/> objects.</param>
1316 /// <param name="criteria">The criteria expression</param>
1317 /// <returns>The sliced query results.</returns>
1318 protected internal static Array
SlicedFindAll(Type targetType
, int firstResult
, int maxResults
,
1319 Order
[] orders
, DetachedCriteria criteria
)
1321 EnsureInitialized(targetType
);
1323 ISession session
= holder
.CreateSession(targetType
);
1327 ICriteria executableCriteria
= criteria
.GetExecutableCriteria(session
);
1328 AddOrdersToCriteria(executableCriteria
, orders
);
1329 executableCriteria
.SetFirstResult(firstResult
);
1330 executableCriteria
.SetMaxResults(maxResults
);
1332 return SupportingUtils
.BuildArray(targetType
, executableCriteria
.List());
1334 catch(ValidationException
)
1336 holder
.FailSession(session
);
1342 holder
.FailSession(session
);
1344 throw new ActiveRecordException("Could not perform SlicedFindAll for " + targetType
.Name
, ex
);
1348 holder
.ReleaseSession(session
);
1353 /// Returns a portion of the query results (sliced)
1355 /// <param name="targetType">The target type.</param>
1356 /// <param name="firstResult">The number of the first row to retrieve.</param>
1357 /// <param name="maxResults">The maximum number of results retrieved.</param>
1358 /// <param name="criteria">The criteria expression</param>
1359 /// <returns>The sliced query results.</returns>
1360 protected internal static Array
SlicedFindAll(Type targetType
, int firstResult
, int maxResults
,
1361 params ICriterion
[] criteria
)
1363 return SlicedFindAll(targetType
, firstResult
, maxResults
, null, criteria
);
1367 /// Returns a portion of the query results (sliced)
1369 /// <param name="targetType">The target type.</param>
1370 /// <param name="firstResult">The number of the first row to retrieve.</param>
1371 /// <param name="maxResults">The maximum number of results retrieved.</param>
1372 /// <param name="criteria">The criteria expression</param>
1373 /// <returns>The sliced query results.</returns>
1374 protected internal static Array
SlicedFindAll(Type targetType
, int firstResult
, int maxResults
,
1375 DetachedCriteria criteria
)
1377 return SlicedFindAll(targetType
, firstResult
, maxResults
, null, criteria
);
1384 #region protected internal
1387 /// Invokes the specified delegate passing a valid
1388 /// NHibernate session. Used for custom NHibernate queries.
1390 /// <param name="call">The delegate instance</param>
1391 /// <returns>Whatever is returned by the delegate invocation</returns>
1392 protected virtual internal object Execute(NHibernateDelegate call
)
1394 return Execute(GetType(), call
, this);
1399 #region public virtual
1402 /// Saves the instance information to the database.
1403 /// May Create or Update the instance depending
1404 /// on whether it has a valid ID.
1407 /// If within a <see cref="SessionScope"/> the operation
1408 /// is going to be on hold until NHibernate (or you) decides to flush
1411 public virtual void Save()
1417 /// Saves the instance information to the database.
1418 /// May Create or Update the instance depending
1419 /// on whether it has a valid ID.
1422 /// Even within a <see cref="SessionScope"/> the operation
1423 /// is going to be flushed immediately. This might have side effects such as
1424 /// flushing (persisting) others operations that were on hold.
1426 public virtual void SaveAndFlush()
1432 /// Saves a copy of the instance information to the database.
1433 /// May Create or Update the instance depending
1434 /// on whether it has a valid ID.
1436 /// <returns>An saved ActiveRecord instance</returns>
1438 /// If within a <see cref="SessionScope"/> the operation
1439 /// is going to be on hold until NHibernate (or you) decides to flush
1442 public virtual object SaveCopy()
1444 return SaveCopy(this);
1448 /// Saves a copy of the instance information to the database.
1449 /// May Create or Update the instance depending
1450 /// on whether it has a valid ID.
1452 /// <returns>A saved ActiveRecord instance</returns>
1454 /// Even within a <see cref="SessionScope"/> the operation
1455 /// is going to be flushed immediately. This might have side effects such as
1456 /// flushing (persisting) others operations that were on hold.
1458 public virtual object SaveCopyAndFlush()
1460 return SaveCopyAndFlush(this);
1464 /// Creates (Saves) a new instance to the database.
1467 /// If within a <see cref="SessionScope"/> the operation
1468 /// is going to be on hold until NHibernate (or you) decides to flush
1471 public virtual void Create()
1477 /// Creates (Saves) a new instance to the database.
1480 /// Even within a <see cref="SessionScope"/> the operation
1481 /// is going to be flushed immediately. This might have side effects such as
1482 /// flushing (persisting) others operations that were on hold.
1484 public virtual void CreateAndFlush()
1486 CreateAndFlush(this);
1490 /// Persists the modification on the instance
1491 /// state to the database.
1494 /// If within a <see cref="SessionScope"/> the operation
1495 /// is going to be on hold until NHibernate (or you) decides to flush
1498 public virtual void Update()
1504 /// Persists the modification on the instance
1505 /// state to the database.
1508 /// Even within a <see cref="SessionScope"/> the operation
1509 /// is going to be flushed immediately. This might have side effects such as
1510 /// flushing (persisting) others operations that were on hold.
1512 public virtual void UpdateAndFlush()
1514 UpdateAndFlush(this);
1518 /// Deletes the instance from the database.
1521 /// If within a <see cref="SessionScope"/> the operation
1522 /// is going to be on hold until NHibernate (or you) decides to flush
1525 public virtual void Delete()
1531 /// Deletes the instance from the database.
1534 /// Even within a <see cref="SessionScope"/> the operation
1535 /// is going to be flushed immediately. This might have side effects such as
1536 /// flushing (persisting) others operations that were on hold.
1538 public virtual void DeleteAndFlush()
1540 DeleteAndFlush(this);
1544 /// Refresh the instance from the database.
1546 public virtual void Refresh()
1553 #region public override
1556 /// Return the type of the object with its PK value.
1557 /// Useful for logging/debugging
1559 /// <returns>A string representation of this object.</returns>
1560 public override String
ToString()
1562 ActiveRecordModel model
= GetModel(GetType());
1564 if (model
== null || model
.PrimaryKey
== null)
1566 return base.ToString();
1569 PrimaryKeyModel pkModel
= model
.PrimaryKey
;
1571 object pkVal
= pkModel
.Property
.GetValue(this, null);
1573 return base.ToString() + "#" + pkVal
;