Different bugs using Complex Properties
---------------------------------------

         Key: IBATISNET-17
         URL: http://issues.apache.org/jira/browse/IBATISNET-17
     Project: iBatis for .NET
        Type: Bug
    Reporter: Henrik Uffe Jensen


I was trying to use some complex properties in resultmaps and parametermaps but 
ran into some problems.

The development guide section 3.4.7 (Example 34) is showing complex properties 
the way I also tried to use them. I explain the problems using the reference to 
this 'product', 'catalog' object example. 

Found out that the problems all related to the fact that the seperat 
Reflectioninfo is created and cached for each object containing only their 
local memberproperties, but different places in the code doesn't handle this. 

So with our example two ReflectionInfo's are created. One for 'product' 
containing the 'id' and 'description' properties, and one for 'catalog' 
containing 'id' and 'description' 


* My first problem was that I couldn't save an object with a complex property 
containing an enum. (Well no enums in the mentioned example but anyway...)

Found out that this was due to the enum handling in 
IBatisNet.DataMapper.Configuration.ParameterMapping.GetValueOfProperty. 

The following line of code will try to get the ReflectionInfo from the source 
object type and get the propertyinfo using propertyname. But with our comples 
property example we acually get the 'Product' instance of the ReflectionInfo 
also when we want the 'Catalog' RefelctionInfo. Further more the propertyName 
is 'category.id' and only the 'id' part is present in the reflectioninfo.

PropertyInfo propertyInfo =  
ReflectionInfo.GetInstance(source.GetType()).GetGetter( propertyName );

I added two new methods to IBatisNet.Common.Utilities.Objects.ObjectProbe, 
which can help find the correct type and propertyname for both normal and 
complex properties

/// <summary>
/// Return the type of the object that the property belongs to. 
/// </summary>
/// <param name="obj">The Object on which to invoke the specified 
property.</param>
/// <param name="propertyName">The name of the property.</param>
/// <returns>An object type of the object the property belongs to.</returns>
public static Type GetObjectType(object obj, string propertyName) 
{
        if (propertyName.IndexOf('.') > -1) 
        {
                StringTokenizer parser = new StringTokenizer(propertyName, ".");
                IEnumerator enumerator = parser.GetEnumerator();
                object value = obj;
                string token = null;
                Type type = null;

                while (enumerator.MoveNext()) 
                {
                        token = (string)enumerator.Current;
                        value = GetProperty(value, token);

                        if (value != null && value.GetType().IsClass)
                        {
                                type = value.GetType();
                        }

                        if (value == null) 
                        {
                                break;
                        }
                }
                return type;
        } 
        else 
        {
                return obj.GetType();
        }
}

                
/// <summary>
/// Return the name of the property used in property maps
/// </summary>
/// <param name="propertyName">The name of the property.</param>
/// <returns>An string representing the name of the property used in property 
maps.</returns>
public static string GetPropertyNameForPropertyMap(string propertyName) 
{
        if (propertyName.IndexOf('.') > -1) 
        {
                string[] arr = propertyName.Split('.');
                return arr[arr.Length - 1];
        } 
        else 
        {
                return propertyName;
        }
}


I then changed the code in 
IBatisNet.DataMapper.Configuration.ParameterMapping.GetValueOfProperty to the 
following. Using the new methods to get the correct type and propertyname. 
After that I could save a complex property containing an enum

        #region Enum case
                                
        // HUJ : Get type and propertyname to use for ReflectionInfo and 
PropertyMap cache
        Type type = ObjectProbe.GetObjectType(source, propertyName);
        string propertyNameInMap = 
ObjectProbe.GetPropertyNameForPropertyMap(propertyName);

        // HUJ : Use above type and propertyname in order to work correct with 
complex properties
        PropertyInfo propertyInfo =  
ReflectionInfo.GetInstance(type).GetGetter( propertyNameInMap );
        // PropertyInfo propertyInfo =  
ReflectionInfo.GetInstance(source.GetType()).GetGetter( propertyName );


* Next problem then was that I could not use Complex properties at all when 
trying to get data from the database. This is actually exactly the example 
mentioned. 

Found out that this was due to the enum handling in 
IBatisNet.DataMapper.Configuration.ResultMapping.SetValueOfProperty and in 
IBatis.DataMapper.Configuration.ResultMapping.GetProperties

In 'GetProperties' there are the same problem as mentioned before so I solved 
it again by using the two new methods in ObjectProbe. A little extra thing is 
that in order to get the type I create an instance of the result object. There 
are probably a nicer way to do this but I works.


/// <summary>
/// Get the result properties from the xmNode.
/// </summary>
/// <param name="node">An xmlNode.</param>
private void GetProperties(XmlNode node)
{
        XmlSerializer serializer = null;
        ResultProperty property = null;

        /// HUJ : Create instance of result for use with 
ObjectProbe.GetObjectType
        object value = CreateInstanceOfResult();

        serializer = new XmlSerializer(typeof(ResultProperty));
        foreach ( XmlNode resultNode in node.SelectNodes("result") )
        {
                property = (ResultProperty) serializer.Deserialize(new 
XmlNodeReader(resultNode));
                        
                PropertyInfo propertyInfo = null;

                if ( property.PropertyName != "value" && 
!typeof(IDictionary).IsAssignableFrom(_class) )
                {
                        // HUJ : Get correct type and propertyname for use in 
ReflectionInfo and PropertyMap cache
                        Type type = ObjectProbe.GetObjectType( value, 
property.PropertyName );
                        string propertyNameInMap = 
ObjectProbe.GetPropertyNameForPropertyMap( property.PropertyName );

                         // HUJ : Use aboe type and propertyname
                        propertyInfo = 
ReflectionInfo.GetInstance(type).GetSetter( propertyNameInMap );
                        //propertyInfo = 
ReflectionInfo.GetInstance(_class).GetSetter( property.PropertyName );
                }
                property.Initialize( propertyInfo );

                this.AddResultPropery( property  );
        }
}


In 'SetValueOfProperty' the problem was that the value of a complex property's 
property is trying to be set on the main object. So property 'description' on 
object 'category' would actually be set on the 'product' object. This "works" 
with example 34 but that's only because 'id' and 'description' is present on 
both the 'product' and the 'catagory' object.

Well what I do now is to check if the ReflectedType is the same as the main 
object (_class) and if not then user another new method from ObjectProbe to get 
the reflected object.


// HUJ : When using complex properties we need to get and use the reflected 
object
object reflectedObject = null;
if (property.PropertyInfo.ReflectedType != _class)
{
        reflectedObject = ObjectProbe.GetReflectedObject(target, 
property.PropertyName);
}
else
{
        reflectedObject = target;
}
property.PropertyInfo.SetValue( reflectedObject, dataBaseValue, null );
//property.PropertyInfo.SetValue( target, dataBaseValue, null );


Here is the new method in ObjectProbe


/// <summary>
/// Return the type of the object that the property belongs to. 
/// </summary>
/// <param name="obj">The Object on which to invoke the specified 
property.</param>
/// <param name="propertyName">The name of the property.</param>
/// <returns>An object type of the object the property belongs to.</returns>
public static object GetReflectedObject(object obj, string propertyName) 
{
        if (propertyName.IndexOf('.') > -1) 
        {
                StringTokenizer parser = new StringTokenizer(propertyName, ".");
                IEnumerator enumerator = parser.GetEnumerator();
                object value = obj;
                string token = null;
                object reflectedObject = null;

                while (enumerator.MoveNext()) 
                {
                        token = (string)enumerator.Current;
                        value = GetProperty(value, token);

                        if (value != null && value.GetType().IsClass)
                        {
                                reflectedObject = value;
                        }

                        if (value == null) 
                        {
                                break;
                        }
                }
                return reflectedObject;
        } 
        else 
        {
                return obj;
        }
}


Well all of the above code works for me now, but I haven't tested it thorougly 
with all kinds of configurations and neither have I looked into if things could 
be refactored and done in a nicer way. 

BTW you mentioned something about that I could take a look in SVN last time I 
reported a bug. But where are your SVN located? I can't find it on 
svn.apache.org ?


Best regards

Henrik Uffe Jensen







-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://issues.apache.org/jira/secure/Administrators.jspa
-
If you want more information on JIRA, or have a bug to report see:
   http://www.atlassian.com/software/jira

Reply via email to