I develop this functionality:

   - Lazy load for strong type collection (actually only supports Ilist)
   - Lazy load for "Item" a select that return an object not collection.

Its on "alpha" and only is a suggestion of implementation (core Ibatis
developers excuse my bugs).

To test this new functionalty I have this model:

   Public class Worker
   {
                private Guid _id;
              private string _name;
                private Worker _boss;
            private WorkerCollection _slaves; 

        //Properies must be virtual in order to DinamicProxy works 

        public class Worker()
      {
                _id = Guid.Empty;
        }

        public class Worker(Guid id)
        {
                _id = id;       
        }
   }

   public class WorkerCollection:BaseCollection
   {

        public virtual Worker this[int index]
      {
                //Implementation ... it important the "virtual" in
signature
        }

        //In documentation BaseCollection.Count is virtual but only in 
        // docs !!!
//http://weblogs.asp.net/bsimser/archive/2005/06/06/410592.aspx
        public virtual new Count
        {
                get
                {
                        return InnerList.Count;
                }
        }

        public void virtual Add(Worker w)
        {
                InnerList.Add(w);
        }

    }

Setup a dataBase with table:

        Workers
         id:Guid
         name:String
         id_boss:Guid

   With this rows to test:

     id: {61CE1744-75B7-423E-B03D-DC8D840B6820}
     id_boss: {61CE1744-75B7-423E-B03D-DC8D840B6820}
     Name: Autonomo

     id: {813CD836-9EB1-4AD5-8201-DA922105B9A9}
     id_boss: NULL
     Name: Jefe

     id: {3BD9A6CC-28FB-4248-9C55-9E2A132CAAA2}
     id_boss: {813CD836-9EB1-4AD5-8201-DA922105B9A9}
     Name: Slave A

     id: {CB408098-B170-4B27-A41E-610358EB41B0}
     id_boss: {813CD836-9EB1-4AD5-8201-DA922105B9A9}
     Name: Slave B

And have map dao Worker.xml similar to:

<?xml version="1.0" encoding="UTF-8" ?>
<sqlMap namespace="" xmlns="http://ibatis.apache.org/mapping";>
 
 <cacheModels>
        <cacheModel id="none" implementation="MEMORY">
                <flushInterval minutes="10"></flushInterval>
                <flushOnExecute
statement="InsertAllFields"></flushOnExecute>
                <flushOnExecute
statement="UpdateAllFields"></flushOnExecute>
                <flushOnExecute
statement="DeleteAllFields"></flushOnExecute>
                <property name="Type" value="Weak"></property>
        </cacheModel>
</cacheModels>
        
<parameterMaps>
        <parameterMap id="pmAllFields" class="Worker">
                <parameter property="Boss.Id" dbType="Guid"
column="id_boss" />
                <parameter property="Name" dbType="VarWChar" />
                <parameter property="Id" dbType="Guid" />
        </parameterMap>
</parameterMaps>
        
<resultMaps>
        <resultMap id="rmAllFields" class="Trabajador" >
                <result property="Id" dbType="Guid" column="id"  />
                <result property="Boss" dbType="Guid" column="id_boss" 
                          select="LoadAllFieldsById" lazyLoad="true"/>
                <result property="Slaves" column="id"                   
                          select="LoadAllFieldsByBoss" lazyLoad="true"
/>                      <result property="Name" dbType="VarWChar"
column="name" />
        </resultMap>
</resultMaps>
        
<statements>
                
        <select id="LoadAllFieldsById" parameterClass="Guid" 
                  resultMap="rmAllFields" resultClass="Worker" 
                  listClass="WorkerCollection" cacheModel="none">
                        SELECT id, id_boss, name
                        FROM Workers
                        <dynamic prepend="WHERE">
                                <isParameterPresent>
                                        Id = #value#
                                </isParameterPresent>
                        </dynamic>
        </select>
                
        <select id="LoadAllFieldsByBoss" parameterClass="Guid" 
                  resultMap="rmAllFields" resultClass="Worker" 
                listClass="WorkerCollection" cacheModel="none">
                        SELECT id, id_boss, name
                        FROM Trabajadores
                        <dynamic prepend="WHERE">
                                <isParameterPresent>
                                        id_boss = #value#
                                </isParameterPresent>
                                <isNotParameterPresent>
                                        id_boss IS NULL
                                </isNotParameterPresent>
                        </dynamic>
                </select>
                
                
                <insert id="InsertAllFields" parameterMap="pmAllFields"
>
                        INSERT INTO Workers (id_boss, name, id)
                        VALUES (?,?,?)
                </insert>
                
                <update id="UpdateAllFields" parameterMap="pmAllFields"
>
                        UPDATE Workers 
                        SET   id_boss = ?,
                                name = ?
                        WHERE Id = ?
                </update>
                
                <delete id="DeleteAllFields" parameterClass="Guid">
                        DELETE FROM Trabajadores WHERE id = #value#
                </delete>
                
                
        </statements>     
        
</sqlMap>

Here are the changes i made:

1) Add a method to IBatisNet.Common.Utilities.Proxy.CachedProxyGenerator

public override object CreateClassProxy(Type baseClass,IInterceptor
interceptor, params object[] argumentsForConstructor)
{
  //TODO cache generated proxy see CachedProxyGenerator.CreateProxy(..) 
  return base.CreateClassProxy (baseClass, 
                                interceptor,argumentsForConstructor);
}

2) Created new namespace and folder IBatisNet.DataMapper.LazyLoad and
add two class one for lazy load list and the other for lazy load Item
(0..1 relationship). This class is heavy based on
IBatisNet.DataMapper.LazyLoadList.

namespace IBatisNet.DataMapper.LazyLoad
{
        /// <summary>
        /// 
        /// </summary>
        [Serializable]
        internal class ItemInterceptor : IInterceptor   
        {
                #region Fields
                private object _param = null;
                private object _target = null;
                private string _propertyName= string.Empty;
                private DataSource _dataSource;
                private IMappedStatement _mappedSatement;
                private bool _loaded = false;
                private object _innerItem = null;
                private object _loadLock = new object();
                private static ArrayList _passthroughMethods = new
ArrayList(2);

                private static CachedProxyGenerator _proxyGenerator =
new CachedProxyGenerator();
                private static readonly ILog _logger =
LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType );
                #endregion

                #region  Constructor (s) / Destructor

                /// <summary>
                /// Constructor for a lazy list loader
                /// </summary>
                static ItemInterceptor()
                {
                        _passthroughMethods.Add("GetType");
                        //_passthroughMethods.Add("ToString"); why not
cause the load of item ?!
                }

                /// <summary>
                /// Constructor for a lazy Item loader
                /// </summary>
                /// <param name="dataSource">The dataSource used to do
the query</param>
                /// <param name="mappedSatement">The mapped statement
used to build the Item</param>
                /// <param name="param">The parameter object used to
build the Item</param>
                /// <param name="propertyName">The property's name which
been proxified.</param>
                /// <param name="target">The target object which
contains the property proxydied.</param>
                internal ItemInterceptor(DataSource dataSource,
IMappedStatement mappedSatement, object param, object target,string
propertyName)
                {
                        _param = param;
                        _mappedSatement = mappedSatement;
                        _dataSource = dataSource;
                        _target = target; 
                        _propertyName = propertyName;
                }               
                #endregion

                #region Methods
                /// <summary>
                /// Static constructor
                /// </summary>
                /// <param name="dataSource">The dataSource used the
query</param>
                /// <param name="mappedSatement">The statement used to
build the Item</param>
                /// <param name="param">The parameter object used to
build the Item</param>
                /// <param name="target">The target object which
contains the property proxydied.</param>
                /// <param name="propertyName">The property's name which
been proxified.</param>
                /// <returns>A proxy</returns>
                internal static object NewInstance(DataSource
dataSource, IMappedStatement mappedSatement, object param, object
target,string propertyName)
                {
                        object proxy = null;
                        IInterceptor handler = new
ItemInterceptor(dataSource, mappedSatement, param, target,
propertyName);

                        if (mappedSatement.Statement.ResultClass !=
null)
                        {
                                object[] argumentsForConstructor = null;

                                if (param is object[])
                                {
                                        //Multiple primary key
                                        argumentsForConstructor =
(object[])param;
                                }
                                else
                                {
                                        argumentsForConstructor = new
object[]{param};
                                }

                                if
(mappedSatement.Statement.ResultClass.IsInterface)
                                {
                                        proxy =
_proxyGenerator.CreateProxy(mappedSatement.Statement.ResultClass,
handler,
System.Activator.CreateInstance(mappedSatement.Statement.ResultClass,arg
umentsForConstructor) );
                                }
                                else if
(mappedSatement.Statement.ResultClass.IsClass)
                                {
                                        proxy =
_proxyGenerator.CreateClassProxy(mappedSatement.Statement.ResultClass,ha
ndler,argumentsForConstructor);
                                }
                                else
                                {
                                        throw new
DataMapperException("Cant proxify " + propertyName + " because
ResultClass " + mappedSatement.Statement.ResultClass + " is no a class
or interface");
                                }
                        }
                        else
                        {
                                throw new DataMapperException("Cant
proxify " + propertyName + " because ResultClass is not defined.
ResultClass " + mappedSatement.Statement.ResultClass);
                                //proxList =
_proxyGenerator.CreateClassProxy(typeof(Object), handler, param);
                        }

                        return proxy;
                }
                
                #region IInterceptor members

                /// <summary>
                /// 
                /// </summary>
                /// <param name="invocation"></param>
                /// <param name="arguments"></param>
                /// <returns></returns>
                public object Intercept(IInvocation invocation, params
object[] arguments)
                {
                        if (_logger.IsDebugEnabled) 
                        {
                                _logger.Debug("Proxyfying call to " +
invocation.Method.Name);
                        }

                        lock(_loadLock)
                        {
                                if ((_loaded == false) &&
(!_passthroughMethods.Contains(invocation.Method.Name)))
                                {
                                        IDalSession session = new
SqlMapSession(_dataSource);

                                        if (_logger.IsDebugEnabled) 
                                        {
        
_logger.Debug("Proxyfying call, query statement " +
_mappedSatement.Name);
                                        }

                                        session.OpenConnection();
                                        _innerItem =
_mappedSatement.ExecuteQueryForObject(session, _param); 
                                        session.CloseConnection();

                                        _loaded = true;
                                         
                                }
                        }

                        object returnValue = invocation.Method.Invoke(
_innerItem, arguments);
                        
                        ObjectProbe.SetPropertyValue( _target,
_propertyName, _innerItem);

                        if (_logger.IsDebugEnabled) 
                        {
                                _logger.Debug("End of proxyfied call to
" + invocation.Method.Name);
                        }

                        return returnValue;
                }

                #endregion

                #endregion

        }
}

namespace IBatisNet.DataMapper.LazyLoad
{
        /// <summary>
        /// 
        /// </summary>
        [Serializable]
        internal class ListInterceptor : IInterceptor   
        {
                #region Fields
                private object _param = null;
                private object _target = null;
                private string _propertyName= string.Empty;
                private DataSource _dataSource;
                private IMappedStatement _mappedSatement;
                private bool _loaded = false;
                private IList _innerList = null;
                private object _loadLock = new object();
                private static ArrayList _passthroughMethods = new
ArrayList(2);

                private static CachedProxyGenerator _proxyGenerator =
new CachedProxyGenerator();
                private static readonly ILog _logger =
LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType );
                #endregion

                #region  Constructor (s) / Destructor

                /// <summary>
                /// Constructor for a lazy list loader
                /// </summary>
                static ListInterceptor()
                {
                        _passthroughMethods.Add("GetType");
                        //_passthroughMethods.Add("ToString");// why not
cause the load of collection ?!
                }

                /// <summary>
                /// Constructor for a lazy list loader
                /// </summary>
                /// <param name="dataSource">The dataSource used to do
the query</param>
                /// <param name="mappedSatement">The mapped statement
used to build the list</param>
                /// <param name="param">The parameter object used to
build the list</param>
                /// <param name="propertyName">The property's name which
been proxified.</param>
                /// <param name="target">The target object which
contains the property proxydied.</param>
                internal ListInterceptor(DataSource dataSource,
IMappedStatement mappedSatement, object param, object target,string
propertyName)
                {
                        _param = param;
                        _mappedSatement = mappedSatement;
                        _dataSource = dataSource;
                        _target = target; 
                        _propertyName = propertyName;
                }               
                #endregion

                #region Methods
                /// <summary>
                /// Static constructor
                /// </summary>
                /// <param name="dataSource">The dataSource used the
query</param>
                /// <param name="mappedSatement">The statement used to
build the list</param>
                /// <param name="param">The parameter object used to
build the list</param>
                /// <param name="target">The target object which
contains the property proxydied.</param>
                /// <param name="propertyName">The property's name which
been proxified.</param>
                /// <returns>A proxy</returns>
                internal static ICollection NewInstance(DataSource
dataSource, IMappedStatement mappedSatement, object param, object
target,string propertyName)
                {
                        object proxList = null;
                        IInterceptor handler = new
ListInterceptor(dataSource, mappedSatement, param, target,
propertyName);

                        if (mappedSatement.Statement.ListClass != null)
                        {
                                if
(mappedSatement.Statement.ListClass.GetInterface("ICollection") == null)
                                {
                                                throw new
DataMapperException("Cant proxify " + propertyName + " because listClass
" + mappedSatement.Statement.ListClass + " not implement ICollection");
                                }

                                if
(mappedSatement.Statement.ListClass.IsInterface)
                                {
                                        proxList =
_proxyGenerator.CreateProxy(new
Type[]{mappedSatement.Statement.ListClass, typeof(ICollection)},
handler, mappedSatement.Statement.CreateInstanceOfListClass());
                                }
                                else if
(mappedSatement.Statement.ListClass.IsClass)
                                {
                                        proxList =
_proxyGenerator.CreateClassProxy(mappedSatement.Statement.ListClass,hand
ler,new object[]{});
                                }
                                else
                                {
                                        throw new
DataMapperException("Cant proxify " + propertyName + " because listClass
" + mappedSatement.Statement.ListClass + " is no a class or interface");
                                }
                        }
                        else
                        {
                                proxList =
_proxyGenerator.CreateProxy(typeof(ICollection), handler, new
ArrayList());
                        }

                        return (ICollection) proxList;
                }
                
                #region IInterceptor members

                /// <summary>
                /// 
                /// </summary>
                /// <param name="invocation"></param>
                /// <param name="arguments"></param>
                /// <returns></returns>
                public object Intercept(IInvocation invocation, params
object[] arguments)
                {
                        if (_logger.IsDebugEnabled) 
                        {
                                _logger.Debug("Proxyfying call to " +
invocation.Method.Name);
                        }

                        lock(_loadLock)
                        {
                                if ((_loaded == false) &&
(!_passthroughMethods.Contains(invocation.Method.Name)))
                                {
                                        IDalSession session = new
SqlMapSession(_dataSource);

                                        if (_logger.IsDebugEnabled) 
                                        {
        
_logger.Debug("Proxyfying call, query statement " +
_mappedSatement.Name);
                                        }

                                        session.OpenConnection();
                                        _innerList =
_mappedSatement.ExecuteQueryForList(session, _param); 
                                        session.CloseConnection();

                                        _loaded = true;
                                         
                                }
                        }

                        object returnValue = invocation.Method.Invoke(
_innerList, arguments);
                        
                        ObjectProbe.SetPropertyValue( _target,
_propertyName, _innerList);

                        if (_logger.IsDebugEnabled) 
                        {
                                _logger.Debug("End of proxyfied call to
" + invocation.Method.Name);
                        }

                        return returnValue;
                }

                #endregion

                #endregion

        }
}

3) Modify
IBatisNet.DataMapper.MappedStatements.MappedStatement.SetObjectProperty(
..) in this way:

private void SetObjectProperty(RequestScope request, ResultMap
resultMap, 
                ResultProperty mapping, ref object target, IDataReader
reader)
{
        string selectStatement = mapping.Select;

        if (selectStatement.Length == 0 && mapping.NestedResultMap ==
null)
        {
                // If the property is not a 'select' ResultProperty 
                //                     or a 'resultMap' ResultProperty
                // We have a 'normal' ResultMap

                #region Not a select statement
                if (mapping.TypeHandler == null || mapping.TypeHandler
is 
                UnknownTypeHandler) // Find the TypeHandler
                {
                        lock(mapping) 
                        {
                                if (mapping.TypeHandler == null || 
                                    mapping.TypeHandler is
UnknownTypeHandler)
                                {
                                        int columnIndex = 0;
                                        if (mapping.ColumnIndex == 
        
ResultProperty.UNKNOWN_COLUMN_INDEX) 
                                        {
                                columnIndex =
reader.GetOrdinal(mapping.ColumnName);
                                        } 
                                        else 
                                        {
                                            columnIndex =
mapping.ColumnIndex;
                                        }
                Type systemType
=((IDataRecord)reader).GetFieldType(columnIndex);
mapping.TypeHandler =
_sqlMap.TypeHandlerFactory.GetTypeHandler(systemType);
                                }
                        }                                       
             }

                object dataBaseValue = mapping.GetDataBaseValue( reader
);
request.IsRowDataFound = request.IsRowDataFound || (dataBaseValue !=
null);

        if (resultMap != null) 
        {
            resultMap.SetValueOfProperty( ref target, mapping,
dataBaseValue );
        }
        else
        {
MappedStatement.SetValueOfProperty( ref target, mapping, dataBaseValue
);
        }
        #endregion
        }
     else if (mapping.NestedResultMap != null) // 'resultMap'
ResultProperty
     {
                                object obj = null;
         obj = mapping.NestedResultMap.CreateInstanceOfResult();
         if (FillObjectWithReaderAndResultMap(request, reader, 
           mapping.NestedResultMap, obj) == false)
         {
                        obj = null;
         }

          MappedStatement.SetValueOfProperty( ref target, mapping, obj
);
        }
        else //'select' ResultProperty 
        {
          // Get the select statement
          IMappedStatement queryStatement = 
                         _sqlMap.GetMappedStatement(selectStatement);
          string paramString = mapping.ColumnName;
          object keys = null;
          bool wasNull = false;

        #region Find Key(s)
          //No changed, omit int this code for cleaning
        #endregion

        if (wasNull) 
        {
                // set the value of an object property to null
                ObjectProbe.SetPropertyValue(target,
mapping.PropertyName, null);
        } 
        else // Collection object or .Net object
        {
                PostBindind postSelect = new PostBindind();
                postSelect.Statement = queryStatement;
                postSelect.Keys = keys;
                postSelect.Target = target;
                postSelect.ResultProperty = mapping;

                #region Collection object or .NET object
                // Check if the object to Map implement 'IList' or is
IList type
              // If yes the ResultProperty is map to a IList object or
typed 
                // collection
                // TODO QUESTION: Why no use ICollection instead of
IList ?¿
        if ( (mapping.PropertyInfo.PropertyType.GetInterface("IList") !=
null) 
           || (mapping.PropertyInfo.PropertyType == typeof(IList)))
        {
               if (mapping.IsLazyLoad)
                 {
                object proxyList = 
 
IBatisNet.DataMapper.LazyLoad.ListInterceptor.NewInstance(
_sqlMap.DataSource,queryStatement,keys,target,mapping.PropertyName);
               //proxyList =
LazyLoadList.NewInstance(_sqlMap.DataSource, 
               //queryStatement, keys, target, mapping.PropertyName);
                                                         
        ObjectProbe.SetPropertyValue( target, mapping.PropertyName,
proxyList);
           }
           else
                {
                  if (mapping.PropertyInfo.PropertyType ==
typeof(IList))
                  {
                        postSelect.Method =
ExecuteMethod.ExecuteQueryForIList;
                  }
                  else
                  {
        postSelect.Method =
ExecuteMethod.ExecuteQueryForStrongTypedIList;
                  }
                }
        } 
        else if (mapping.PropertyInfo.PropertyType.IsArray)
        {
          //TODO test/think about if this method is called, System.Array
//implement IList and never pass the first if() 
         if (mapping.IsLazyLoad)
         {
                throw new NotImplementedException("Lazy load no
supported yet! 
                   for System.Array property:" +
mapping.PropertyInfo.Name);
         }
         postSelect.Method = ExecuteMethod.ExecuteQueryForArrayList;
        }
        else // The ResultProperty is map to a .Net object
        {
                if (mapping.IsLazyLoad)
                {
                        object proxyItem =  
 
IBatisNet.DataMapper.LazyLoad.ItemInterceptor.NewInstance(
                             _sqlMap.DataSource,queryStatement,keys,
                             target,mapping.PropertyName);
        
ObjectProbe.SetPropertyValue(target, mapping.PropertyName, proxyItem);
                }
                else
                {
                        postSelect.Method =
ExecuteMethod.ExecuteQueryForObject;
                }
        }
    #endregion

    if (!mapping.IsLazyLoad)
    {
                request.QueueSelect.Enqueue(postSelect);
    }
  }
 }
}

Future Improvements:

 - When lazy load worker.Boss and call worker.Boss.Id, actually the
proxy 
  load the item, this is not necesary because of worker.Boss is created 
  with a the constructor Worker(id_boss) and already have the correct
_id 
  field assigned.

 - For my its usefull if I could specify in SqlMapDao a custom
interceptor 
  class to use in LazyLoad, for example in order to acomplish the abobe 
  improvement.

   Something similar to:

    <sqlMapConfig  xmlns="http://ibatis.apache.org/dataMapper";>

        <settings>
                <setting
LazyLoadList="MyLazyLoadListInterceptor,MyAssembly"/>           <setting
LazyLoadItem="MyLazyLoadItemInterceptor,MyAssembly"/>
        </settings>

    </sqlMapConfig>

- Maybe only need one interceptor class, instead of one for collection
and one for "Items" and test in NewInstace(..) if the property to
intercept is a 
collection or not and create the correct proxy.


I aprreciate your feedback :-)

And sorry for the awfull tabular code.





Reply via email to