#region Apache Notice /***************************************************************************** * $Header: $ * $Revision: 587946 $ * $Date$ * * iBATIS.NET Data Mapper * Copyright (C) 2008/2005 - The Apache Software Foundation * * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ********************************************************************************/ #endregion #region Using using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Diagnostics; using Apache.Ibatis.DataMapper.Data; using Apache.Ibatis.DataMapper.Model; using Apache.Ibatis.DataMapper.Model.Cache; using Apache.Ibatis.DataMapper.Model.Events; using Apache.Ibatis.DataMapper.Model.Statements; using Apache.Ibatis.DataMapper.Scope; using Apache.Ibatis.DataMapper.Session; #endregion namespace Apache.Ibatis.DataMapper.MappedStatements { /// /// Acts as a decorator arounf an to add cache functionality /// [DebuggerDisplay("MappedStatement: {mappedStatement.Id}")] public sealed class CachingStatement : MappedStatementEventSupport, IMappedStatement { // Func delegate T RequestRunner(RequestScope requestScope, ISession session, object parameter, CacheKey cacheKey, out bool cacheHit); private readonly MappedStatement mappedStatement; /// /// Event launch on Execute query /// public event EventHandler Executed = delegate { }; /// /// Constructor /// /// public CachingStatement(MappedStatement statement) { mappedStatement = statement; } #region IMappedStatement Members /// /// The IPreparedCommand to use /// public IPreparedCommand PreparedCommand { get { return mappedStatement.PreparedCommand; } } /// /// Name used to identify the MappedStatement amongst the others. /// This the name of the SQL statment by default. /// public string Id { get { return mappedStatement.Id; } } /// /// The SQL statment used by this MappedStatement /// public IStatement Statement { get { return mappedStatement.Statement; } } /// /// The used by this MappedStatement /// /// The model store. public IModelStore ModelStore { get { return mappedStatement.ModelStore; } } /// /// Executes an SQL statement that returns DataTable. /// /// The session used to execute the statement. /// The object used to set the parameters in the SQL. /// The object public DataTable ExecuteQueryForDataTable(ISession session, object parameterObject) { RequestRunner requestRunner = delegate(RequestScope requestscope, ISession session2, object parameter, CacheKey cachekey, out bool cachehit) { cachehit = true; DataTable dataTable = Statement.CacheModel[cachekey] as DataTable; if (dataTable == null) { cachehit = false; dataTable = mappedStatement.RunQueryForDataTable(requestscope, session, parameter); Statement.CacheModel[cachekey] = dataTable; } return dataTable; }; return CachingStatementExecute(PreSelectEventKey, PostSelectEventKey, session, parameterObject, "ExecuteQueryForDataTable", requestRunner); } /// /// Executes the SQL and retuns all rows selected in a map that is keyed on the property named /// in the keyProperty parameter. The value at each key will be the value of the property specified /// in the valueProperty parameter. If valueProperty is null, the entire result object will be entered. /// /// The session used to execute the statement /// The object used to set the parameters in the SQL. /// The property of the result object to be used as the key. /// The property of the result object to be used as the value (or null) /// A hashtable of object containing the rows keyed by keyProperty. ///If a transaction is not in progress, or the database throws an exception. public IDictionary ExecuteQueryForMap(ISession session, object parameterObject, string keyProperty, string valueProperty) { // this doesn't need to be in its own RunQueryForCachedMap method because the class is sealed and can't be called by anyone else RequestRunner requestRunner = delegate(RequestScope requestscope, ISession session2, object parameter, CacheKey cachekey, out bool cachehit) { if (keyProperty != null) { cachekey.Update(keyProperty); } if (valueProperty != null) { cachekey.Update(valueProperty); } cachehit = true; IDictionary map = Statement.CacheModel[cachekey] as IDictionary; if (map == null) { cachehit = false; map = mappedStatement.RunQueryForMap(requestscope, session, parameter, keyProperty, valueProperty, null); Statement.CacheModel[cachekey] = map; } return map; }; return CachingStatementExecute(PreSelectEventKey, PostSelectEventKey, session, parameterObject, "ExecuteQueryForMap", requestRunner); } #region ExecuteQueryForMap .NET 2.0 /// /// Executes the SQL and retuns all rows selected in a map that is keyed on the property named /// in the keyProperty parameter. The value at each key will be the value of the property specified /// in the valueProperty parameter. If valueProperty is null, the entire result object will be entered. /// /// The session used to execute the statement /// The object used to set the parameters in the SQL. /// The property of the result object to be used as the key. /// The property of the result object to be used as the value (or null) /// A hashtable of object containing the rows keyed by keyProperty. ///If a transaction is not in progress, or the database throws an exception. public IDictionary ExecuteQueryForDictionary(ISession session, object parameterObject, string keyProperty, string valueProperty) { // this doesn't need to be in its own RunQueryForCachedDictionary method because the class is sealed and can't be called by anyone else RequestRunner> requestRunner = delegate(RequestScope requestScope, ISession session2, object parameter, CacheKey cacheKey, out bool cacheHit) { if (keyProperty != null) { cacheKey.Update(keyProperty); } if (valueProperty != null) { cacheKey.Update(valueProperty); } cacheHit = true; IDictionary map = Statement.CacheModel[cacheKey] as IDictionary; if (map == null) { cacheHit = false; map = mappedStatement.RunQueryForDictionary(requestScope, session2, parameter, keyProperty, valueProperty, null); Statement.CacheModel[cacheKey] = map; } return map; }; return CachingStatementExecute(PreSelectEventKey, PostSelectEventKey, session, parameterObject, "ExecuteQueryForDictionary", requestRunner); } /// /// Runs a query with a custom object that gets a chance /// to deal with each row as it is processed. /// /// /// This method always bypasses the cache. /// /// The session used to execute the statement /// The object used to set the parameters in the SQL. /// The property of the result object to be used as the key. /// The property of the result object to be used as the value (or null) /// /// A hashtable of object containing the rows keyed by keyProperty. /// If a transaction is not in progress, or the database throws an exception. public IDictionary ExecuteQueryForDictionary(ISession session, object parameterObject, string keyProperty, string valueProperty, DictionaryRowDelegate rowDelegate) { return mappedStatement.ExecuteQueryForDictionary(session, parameterObject, keyProperty, valueProperty, rowDelegate); } #endregion /// /// Execute an update statement. Also used for delete statement. /// Return the number of row effected. /// /// /// This method always bypasses the cache. /// /// The session used to execute the statement. /// The object used to set the parameters in the SQL. /// The number of row effected. public int ExecuteUpdate(ISession session, object parameterObject) { return mappedStatement.ExecuteUpdate(session, parameterObject); } /// /// Execute an insert statement. Fill the parameter object with /// the ouput parameters if any, also could return the insert generated key /// /// /// This method always bypasses the cache. /// /// The session /// The parameter object used to fill the statement. /// Can return the insert generated key. public object ExecuteInsert(ISession session, object parameterObject) { return mappedStatement.ExecuteInsert(session, parameterObject); } #region ExecuteQueryForList /// /// Executes the SQL and and fill a strongly typed collection. /// /// /// This method always bypasses the cache. /// /// The session used to execute the statement. /// The object used to set the parameters in the SQL. /// A strongly typed collection of result objects. public void ExecuteQueryForList(ISession session, object parameterObject, IList resultObject) { mappedStatement.ExecuteQueryForList(session, parameterObject, resultObject); } /// /// Executes the SQL and retuns all rows selected. /// /// The session used to execute the statement. /// The object used to set the parameters in the SQL. /// A List of result objects. public IList ExecuteQueryForList(ISession session, object parameterObject) { // this doesn't need to be in its own RunQueryForCachedList method because the class is sealed and can't be called by anyone else RequestRunner requestRunner = delegate(RequestScope requestScope, ISession session2, object parameter, CacheKey cacheKey, out bool cacheHit) { cacheHit = true; IList list = Statement.CacheModel[cacheKey] as IList; if (list == null) { cacheHit = false; list = mappedStatement.RunQueryForList(requestScope, session, parameter, null, null); Statement.CacheModel[cacheKey] = list; } return list; }; return CachingStatementExecute(PreSelectEventKey, PostSelectEventKey, session, parameterObject, "ExecuteQueryForList", requestRunner); } #endregion #region ExecuteQueryForList .NET 2.0 /// /// Executes the SQL and and fill a strongly typed collection. /// /// /// This method always bypasses the cache. /// /// The session used to execute the statement. /// The object used to set the parameters in the SQL. /// A strongly typed collection of result objects. public void ExecuteQueryForList(ISession session, object parameterObject, IList resultObject) { mappedStatement.ExecuteQueryForList(session, parameterObject, resultObject); } /// /// Executes the SQL and retuns all rows selected. /// /// The session used to execute the statement. /// The object used to set the parameters in the SQL. /// A List of result objects. public IList ExecuteQueryForList(ISession session, object parameterObject) { // this doesn't need to be in its own RunQueryForCachedList method because the class is sealed and can't be called by anyone else RequestRunner> requestRunner = delegate(RequestScope requestScope, ISession session2, object parameter, CacheKey cacheKey, out bool cacheHit) { cacheHit = true; IList list = Statement.CacheModel[cacheKey] as IList; if (list == null) { cacheHit = false; list = mappedStatement.RunQueryForList(requestScope, session, parameter, null, null); Statement.CacheModel[cacheKey] = list; } return list; }; return CachingStatementExecute(PreSelectEventKey, PostSelectEventKey, session, parameterObject, "ExecuteQueryForList", requestRunner); } #endregion #region ExecuteQueryForObject /// /// Executes an SQL statement that returns a single row as an Object of the type of /// the resultObject passed in as a parameter. /// /// The session used to execute the statement. /// The object used to set the parameters in the SQL. /// The result object. /// The object public object ExecuteQueryForObject(ISession session, object parameterObject, object resultObject) { // this doesn't need to be in its own RunQueryForCachedObject method because the class is sealed and can't be called by anyone else RequestRunner requestRunner = delegate(RequestScope requestScope, ISession session2, object parameter, CacheKey cacheKey, out bool cacheHit) { cacheHit = true; object obj = Statement.CacheModel[cacheKey]; // check if this query has alreay been run if (obj == CacheModel.NULL_OBJECT) { // convert the marker object back into a null value obj = null; } else if (obj == null) { cacheHit = false; obj = mappedStatement.RunQueryForObject(requestScope, session, parameter, resultObject); Statement.CacheModel[cacheKey] = obj; } return obj; }; return CachingStatementExecute(PreSelectEventKey, PostSelectEventKey, session, parameterObject, "ExecuteQueryForObject", requestRunner); } #endregion #region ExecuteQueryForObject .NET 2.0 /// /// Executes an SQL statement that returns a single row as an Object of the type of /// the resultObject passed in as a parameter. /// /// The session used to execute the statement. /// The object used to set the parameters in the SQL. /// The result object. /// The object public T ExecuteQueryForObject(ISession session, object parameterObject, T resultObject) { // this doesn't need to be in its own RunQueryForCachedObject method because the class is sealed and can't be called by anyone else RequestRunner requestRunner = delegate(RequestScope requestScope, ISession session2, object parameter, CacheKey cacheKey, out bool cacheHit) { T obj; cacheHit = false; object cacheObjet = Statement.CacheModel[cacheKey]; // check if this query has alreay been run if (cacheObjet is T) { cacheHit = true; obj = (T)cacheObjet; } else if (cacheObjet == CacheModel.NULL_OBJECT) { // convert the marker object back into a null value cacheHit = true; obj = default(T); } else //if ((object)obj == null) { obj = mappedStatement.RunQueryForObject(requestScope, session, parameter, resultObject); Statement.CacheModel[cacheKey] = obj; } return obj; }; return CachingStatementExecute(PreSelectEventKey, PostSelectEventKey, session, parameterObject, "ExecuteQueryForObject", requestRunner); } #endregion /// /// Runs a query with a custom object that gets a chance /// to deal with each row as it is processed. /// /// /// This method always bypasses the cache. /// /// The session used to execute the statement. /// The object used to set the parameters in the SQL. /// public IList ExecuteQueryForRowDelegate(ISession session, object parameterObject, RowDelegate rowDelegate) { // TODO: investigate allow the cached data to be processed by a different rowDelegate...add rowDelegate to the CacheKey ??? return mappedStatement.ExecuteQueryForRowDelegate(session, parameterObject, rowDelegate); } /// /// Runs a query with a custom object that gets a chance /// to deal with each row as it is processed. /// /// /// This method always bypasses the cache. /// /// The session used to execute the statement. /// The object used to set the parameters in the SQL. /// public IList ExecuteQueryForRowDelegate(ISession session, object parameterObject, RowDelegate rowDelegate) { // TODO: investigate allow the cached data to be processed by a different rowDelegate...add rowDelegate to the CacheKey ??? return mappedStatement.ExecuteQueryForRowDelegate(session, parameterObject, rowDelegate); } /// /// Runs a query with a custom object that gets a chance /// to deal with each row as it is processed. /// /// /// This method always bypasses the cache. /// /// The session used to execute the statement /// The object used to set the parameters in the SQL. /// The property of the result object to be used as the key. /// The property of the result object to be used as the value (or null) /// /// A hashtable of object containing the rows keyed by keyProperty. /// If a transaction is not in progress, or the database throws an exception. public IDictionary ExecuteQueryForMapWithRowDelegate(ISession session, object parameterObject, string keyProperty, string valueProperty, DictionaryRowDelegate rowDelegate) { // TODO: investigate allow the cached data to be processed by a different rowDelegate...add rowDelegate to the CacheKey ??? return mappedStatement.ExecuteQueryForMapWithRowDelegate(session, parameterObject, keyProperty, valueProperty, rowDelegate); } #endregion /// /// Gets the cache key. /// /// The request. /// the cache key private CacheKey GetCacheKey(RequestScope request) { CacheKey cacheKey = new CacheKey(); int count = request.IDbCommand.Parameters.Count; for (int i = 0; i < count; i++) { IDataParameter dataParameter = (IDataParameter)request.IDbCommand.Parameters[i]; if (dataParameter.Value != null) { cacheKey.Update( dataParameter.Value ); } } cacheKey.Update(mappedStatement.Id); cacheKey.Update(mappedStatement.ModelStore.SessionFactory.DataSource.ConnectionString); cacheKey.Update(request.IDbCommand.CommandText); //todo a supprimer //CacheModel cacheModel = mappedStatement.Statement.CacheModel; //if (!cacheModel.IsReadOnly && !cacheModel.IsSerializable) //{ // cacheKey.Update(request); //} return cacheKey; } /// /// Ensures all the related Execute methods are run in a consistent manner with pre and post events. /// /// /// Based off of MappedStatement.Execute /// private T CachingStatementExecute(object preEvent, object postEvent, ISession session, object parameterObject, string baseCacheKey, RequestRunner requestRunner) { object paramPreEvent = RaisePreEvent(preEvent, parameterObject); RequestScope requestScope = Statement.Sql.GetRequestScope(this, paramPreEvent, session); mappedStatement.PreparedCommand.Create(requestScope, session, Statement, paramPreEvent); CacheKey cacheKey = GetCacheKey(requestScope); cacheKey.Update(baseCacheKey); bool cacheHit; T result = requestRunner(requestScope, session, paramPreEvent, cacheKey, out cacheHit); return RaisePostEvent(postEvent, paramPreEvent, result, cacheHit); } } }