1 // Copyright 2004-2008 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 System
.Collections
.Generic
;
20 using Castle
.ActiveRecord
.Framework
;
21 using Castle
.ActiveRecord
.Framework
.Internal
;
22 using Castle
.ActiveRecord
.Queries
;
25 using NHibernate
.Criterion
;
26 using Castle
.Components
.Validator
;
29 /// Allow custom executions using the NHibernate's ISession.
31 public delegate object NHibernateDelegate(ISession session
, object instance
);
34 /// Base class for all ActiveRecord classes. Implements
35 /// all the functionality to simplify the code on the
39 public abstract class ActiveRecordBase
: ActiveRecordHooksBase
42 /// The global holder for the session factories.
44 protected internal static ISessionFactoryHolder holder
;
46 #region internal static
48 internal static void EnsureInitialized(Type type
)
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() ?",
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.",
62 throw new ActiveRecordException(message
);
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
);
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
);
88 #region protected internal static
90 #region Create/Update/Save/Delete/DeleteAll/Refresh
95 /// Creates (Saves) a new instance to the database.
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);
104 /// Creates (Saves) a new instance to the database and flushes the session.
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);
113 /// Creates (Saves) a new instance to the database.
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
);
136 holder
.FailSession(session
);
138 // NHibernate catches our ValidationException, and as such it is the innerexception here
139 if (ex
is ValidationException
)
142 if (ex
.InnerException
is ValidationException
)
143 throw ex
.InnerException
;
145 throw new ActiveRecordException("Could not perform Create for " + instance
.GetType().Name
, ex
);
150 holder
.ReleaseSession(session
);
159 /// Deletes the instance from the database.
161 /// <param name="instance">The ActiveRecord instance to be deleted</param>
162 protected internal static void Delete(object instance
)
164 InternalDelete(instance
, false);
168 /// Deletes the instance from the database and flushes the session.
170 /// <param name="instance">The ActiveRecord instance to be deleted</param>
171 protected internal static void DeleteAndFlush(object instance
)
173 InternalDelete(instance
, true);
177 /// Deletes the instance from the database.
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
);
198 catch (ValidationException
)
200 holder
.FailSession(session
);
207 holder
.FailSession(session
);
209 throw new ActiveRecordException("Could not perform Delete for " + instance
.GetType().Name
, ex
);
213 holder
.ReleaseSession(session
);
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.
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
);
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
;
254 throw new ActiveRecordException("Could not perform Replicate for " + instance
.GetType().Name
, ex
);
259 holder
.ReleaseSession(session
);
268 /// Refresh the instance from the database.
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
);
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
;
294 throw new ActiveRecordException("Could not perform Refresh for " + instance
.GetType().Name
, ex
);
299 holder
.ReleaseSession(session
);
308 /// Deletes all rows for the specified ActiveRecord type
311 /// This method is usually useful for test cases.
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
));
326 catch (ValidationException
)
328 holder
.FailSession(session
);
334 holder
.FailSession(session
);
336 throw new ActiveRecordException("Could not perform DeleteAll for " + type
.Name
, ex
);
340 holder
.ReleaseSession(session
);
345 /// Deletes all rows for the specified ActiveRecord type that matches
346 /// the supplied HQL condition
349 /// This method is usually useful for test cases.
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
));
365 catch (ValidationException
)
367 holder
.FailSession(session
);
373 holder
.FailSession(session
);
375 throw new ActiveRecordException("Could not perform DeleteAll for " + type
.Name
, ex
);
379 holder
.ReleaseSession(session
);
384 /// Deletes all <paramref name="targetType" /> objects, based on the primary keys
385 /// supplied on <paramref name="pkValues" />.
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)
399 foreach (object pk
in pkValues
)
401 Object obj
= FindByPrimaryKey(targetType
, pk
, false);
405 ActiveRecordBase arBase
= obj
as ActiveRecordBase
;
409 arBase
.Delete(); // in order to allow override of the virtual "Delete()" method
428 /// Persists the modification on the instance
429 /// state to the database.
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);
438 /// Persists the modification on the instance
439 /// state to the database and flushes the session.
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);
448 /// Persists the modification on the instance
449 /// state to the database.
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
);
470 catch (ValidationException
)
472 holder
.FailSession(session
);
478 holder
.FailSession(session
);
480 throw new ActiveRecordException("Could not perform Update for " + instance
.GetType().Name
, ex
);
484 holder
.ReleaseSession(session
);
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.
496 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
497 /// or <see cref="Update()"/> instead.
500 /// <param name="instance">The ActiveRecord instance to be saved</param>
501 protected internal static void Save(object instance
)
503 InternalSave(instance
, false);
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.
510 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
511 /// or <see cref="Update()"/> instead.
514 /// <param name="instance">The ActiveRecord instance to be saved</param>
515 protected internal static void SaveAndFlush(object instance
)
517 InternalSave(instance
, true);
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.
524 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
525 /// or <see cref="Update()"/> instead.
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);
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.
539 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
540 /// or <see cref="Update()"/> instead.
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);
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.
554 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
555 /// or <see cref="Update()"/> instead.
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
);
577 catch (ValidationException
)
579 holder
.FailSession(session
);
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
;
594 throw new ActiveRecordException("Could not perform Save for " + instance
.GetType().Name
, ex
);
599 holder
.ReleaseSession(session
);
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.
607 /// If the primary key is assigned, then you must invoke <see cref="Create()"/>
608 /// or <see cref="Update()"/> instead.
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
);
633 catch (ValidationException
)
635 holder
.FailSession(session
);
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
;
650 throw new ActiveRecordException("Could not perform SaveCopy for " + instance
.GetType().Name
, ex
);
655 holder
.ReleaseSession(session
);
666 /// Invokes the specified delegate passing a valid
667 /// NHibernate session. Used for custom NHibernate queries.
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
);
694 holder
.FailSession(session
);
696 throw new ActiveRecordException("Error performing Execute for " + targetType
.Name
, ex
);
700 holder
.ReleaseSession(session
);
709 /// Enumerates the query
710 /// Note: only use if you expect most of the values to exist on the second level cache.
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
);
728 holder
.FailSession(session
);
730 throw new ActiveRecordException("Could not perform EnumerateQuery for " + rootType
.Name
, ex
);
734 holder
.ReleaseSession(session
);
743 /// Executes the query.
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
);
761 holder
.FailSession(session
);
763 throw new ActiveRecordException("Could not perform ExecuteQuery for " + rootType
.Name
, ex
);
767 holder
.ReleaseSession(session
);
776 /// Returns the number of records of the specified
777 /// type in the database
782 /// public class User : ActiveRecordBase
786 /// public static int CountAllUsers()
788 /// return Count(typeof(User));
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
);
803 /// Returns the number of records of the specified
804 /// type in the database
809 /// public class User : ActiveRecordBase
813 /// public static int CountAllUsersLocked()
815 /// return Count(typeof(User), "IsLocked = ?", true);
820 /// <param name="targetType">The target type.</param>
821 /// <param name="filter">A sql where string i.e. Person=? and DOB > ?</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
);
832 /// Returns the number of records of the specified
833 /// type in the database
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
);
846 /// Returns the number of records of the specified
847 /// type in the database
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
);
864 /// Check if there is any records in the db for the target type
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;
874 /// Check if there is any records in the db for the target type
876 /// <param name="targetType">The target type.</param>
877 /// <param name="filter">A sql where string i.e. Person=? and DOB > ?</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;
886 /// Check if the <paramref name="id"/> exists in the database.
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;
902 throw new ActiveRecordException("Could not perform Exists for " + targetType
.Name
+ ". Id: " + id
, ex
);
906 holder
.ReleaseSession(session
);
911 /// Check if any instance matching the criteria exists in the database.
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;
922 /// Check if any instance matching the criteria exists in the database.
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;
937 /// Returns all instances found for the specified type according to the criteria
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
);
965 holder
.FailSession(session
);
967 throw new ActiveRecordException("Could not perform FindAll for " + targetType
.Name
, ex
);
971 holder
.ReleaseSession(session
);
976 /// Returns all instances found for the specified type.
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);
986 /// Returns all instances found for the specified type
987 /// using sort orders and criteria.
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
);
1018 catch (Exception ex
)
1020 holder
.FailSession(session
);
1022 throw new ActiveRecordException("Could not perform FindAll for " + targetType
.Name
, ex
);
1026 holder
.ReleaseSession(session
);
1030 private static void AddOrdersToCriteria(ICriteria criteria
, IEnumerable
<Order
> orders
)
1034 foreach (Order order
in orders
)
1036 criteria
.AddOrder(order
);
1042 /// Returns all instances found for the specified type
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
);
1055 #region FindAllByProperty
1058 /// Finds records based on a property value - automatically converts null values to IS NULL style queries.
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
);
1071 /// Finds records based on a property value - automatically converts null values to IS NULL style queries.
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
);
1086 #region FindByPrimaryKey
1089 /// Finds an object instance by an unique ID
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);
1100 /// Finds an object instance by an unique ID
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
);
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
);
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
1136 NHibernateUtil
.Initialize(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
);
1153 catch (Exception ex
)
1155 holder
.FailSession(session
);
1157 throw new ActiveRecordException("Could not perform FindByPrimaryKey for " + targetType
.Name
+ ". Id: " + id
, ex
);
1161 holder
.ReleaseSession(session
);
1170 /// Searches and returns the first row.
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);
1183 /// Searches and returns the first row.
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);
1196 /// Searches and returns the first row.
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
);
1211 /// Searches and returns a row. If more than one is found,
1212 /// throws <see cref="ActiveRecordException"/>
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);
1231 /// Searches and returns a row. If more than one is found,
1232 /// throws <see cref="ActiveRecordException"/>
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);
1252 #region SlicedFindAll
1255 /// Returns a portion of the query results (sliced)
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
);
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
);
1298 catch (Exception ex
)
1300 holder
.FailSession(session
);
1302 throw new ActiveRecordException("Could not perform SlicedFindAll for " + targetType
.Name
, ex
);
1306 holder
.ReleaseSession(session
);
1311 /// Returns a portion of the query results (sliced)
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
);
1343 holder
.FailSession(session
);
1345 throw new ActiveRecordException("Could not perform SlicedFindAll for " + targetType
.Name
, ex
);
1349 holder
.ReleaseSession(session
);
1354 /// Returns a portion of the query results (sliced)
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
);
1368 /// Returns a portion of the query results (sliced)
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
);
1385 #region protected internal
1388 /// Invokes the specified delegate passing a valid
1389 /// NHibernate session. Used for custom NHibernate queries.
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);
1400 #region public virtual
1403 /// Saves the instance information to the database.
1404 /// May Create or Update the instance depending
1405 /// on whether it has a valid ID.
1408 /// If within a <see cref="SessionScope"/> the operation
1409 /// is going to be on hold until NHibernate (or you) decides to flush
1412 public virtual void Save()
1418 /// Saves the instance information to the database.
1419 /// May Create or Update the instance depending
1420 /// on whether it has a valid ID.
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.
1427 public virtual void SaveAndFlush()
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.
1437 /// <returns>An saved ActiveRecord instance</returns>
1439 /// If within a <see cref="SessionScope"/> the operation
1440 /// is going to be on hold until NHibernate (or you) decides to flush
1443 public virtual object SaveCopy()
1445 return SaveCopy(this);
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.
1453 /// <returns>A saved ActiveRecord instance</returns>
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.
1459 public virtual object SaveCopyAndFlush()
1461 return SaveCopyAndFlush(this);
1465 /// Creates (Saves) a new instance to the database.
1468 /// If within a <see cref="SessionScope"/> the operation
1469 /// is going to be on hold until NHibernate (or you) decides to flush
1472 public virtual void Create()
1478 /// Creates (Saves) a new instance to the database.
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.
1485 public virtual void CreateAndFlush()
1487 CreateAndFlush(this);
1491 /// Persists the modification on the instance
1492 /// state to the database.
1495 /// If within a <see cref="SessionScope"/> the operation
1496 /// is going to be on hold until NHibernate (or you) decides to flush
1499 public virtual void Update()
1505 /// Persists the modification on the instance
1506 /// state to the database.
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.
1513 public virtual void UpdateAndFlush()
1515 UpdateAndFlush(this);
1519 /// Deletes the instance from the database.
1522 /// If within a <see cref="SessionScope"/> the operation
1523 /// is going to be on hold until NHibernate (or you) decides to flush
1526 public virtual void Delete()
1532 /// Deletes the instance from the database.
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.
1539 public virtual void DeleteAndFlush()
1541 DeleteAndFlush(this);
1545 /// Refresh the instance from the database.
1547 public virtual void Refresh()
1554 #region public override
1557 /// Return the type of the object with its PK value.
1558 /// Useful for logging/debugging
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
;
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
);
1596 /// Returns an array of Ascending <see cref="Order"/> instances specifing which properties to use to
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
);
1607 /// Descending Order
1610 /// Returns an array of Descending <see cref="Order"/> instances specifing which properties to use to
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
);