Details
-
Bug
-
Status: Closed
-
Major
-
Resolution: Fixed
-
DataMapper 1.2.1
-
None
-
Windows
Description
The fix for IBATISNET-118 does not address the issue (just hides it in one particular instance).
The issue is that you can not use HashCodes to determine object equality. Unique objects do not always have unique HashCodes (no matter how hard you try). There just aren't enough hashcodes to go around
I understand why my previous 'patch' wasn't accepted (didn't expect it to be). Since we are not using an object's own "GetHashCode()" to get it's hashcode, we can not use it's own "Equals" to determine equality. So we really have to do is create a new method on ObjectProbe that compares two objects in much the same way that ObjectProbe generates hashcodes. I was just too lazy to write it yesterday
Here is an updated unit test and a potential fix...
— BEGIN UNIT TEST —
using IBatisNet.DataMapper;
using IBatisNet.DataMapper.TypeHandlers;
using NUnit.Framework;
namespace IBatisNet.DataMapper.Test.NUnit.SqlMapTests
{
[TestFixture]
public class CacheKeyTest
{
[Test]
public void ShouldNotConsider1LAndNegative9223372034707292159LToBeEqual()
[Test]
public void ShouldNotConsider1LAndNegative9223372036524971138LToBeEqual()
private static void DoTestClassEquals(long firstLong, long secondLong)
{
TypeHandlerFactory factory = new TypeHandlerFactory();
// Two cache keys are equal except for the parameter.
CacheKey key = new CacheKey(factory, "STATEMENT", "SQL", new TestClass(firstLong), new string[]
CacheKey aDifferentKey = new CacheKey(factory, "STATEMENT", "SQL", new TestClass(secondLong), new string[] {"AProperty"}
, 0, 0, CacheKeyType.Object);
Assert.IsFalse(aDifferentKey.Equals(key)); // should not be equal.
}
private class TestClass
{
private long _property = long.MinValue;
public TestClass(long aProperty)
{ _property = aProperty; } public long AProperty
{
get
set
{ _property = value; } }
}
}
}
— END UNIT TEST —
FIX (not sure if this will break other parts of the system... but it shouldn't):
— BEGIN ObjectProbe —
#region Apache Notice
/*****************************************************************************
- $Header: $
- $Revision: $
- $Date: $
- iBATIS.NET Data Mapper
- Copyright (C) 2004 - Gilles Bayon
- 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
using System;
using System.Collections;
using System.Reflection;
using IBatisNet.Common.Exceptions;
namespace IBatisNet.Common.Utilities.Objects
{
/// <summary>
/// Description résumée de ObjectProbe.
/// </summary>
public class ObjectProbe
{
private static ArrayList _simpleTypeMap = new ArrayList();
static ObjectProbe()
{ _simpleTypeMap.Add(typeof(string)); _simpleTypeMap.Add(typeof(Byte)); _simpleTypeMap.Add(typeof(Int16)); _simpleTypeMap.Add(typeof(char)); _simpleTypeMap.Add(typeof(Int32)); _simpleTypeMap.Add(typeof(Int64)); _simpleTypeMap.Add(typeof(Single)); _simpleTypeMap.Add(typeof(Double)); _simpleTypeMap.Add(typeof(Boolean)); _simpleTypeMap.Add(typeof(DateTime)); _simpleTypeMap.Add(typeof(Decimal)); // _simpleTypeMap.Add(typeof(Hashtable)); // _simpleTypeMap.Add(typeof(SortedList)); // _simpleTypeMap.Add(typeof(ArrayList)); // _simpleTypeMap.Add(typeof(Array)); // simpleTypeMap.Add(LinkedList.class); // simpleTypeMap.Add(HashSet.class); // simpleTypeMap.Add(TreeSet.class); // simpleTypeMap.Add(Vector.class); // simpleTypeMap.Add(Hashtable.class); _simpleTypeMap.Add(typeof(SByte)); _simpleTypeMap.Add(typeof(UInt16)); _simpleTypeMap.Add(typeof(UInt32)); _simpleTypeMap.Add(typeof(UInt64)); _simpleTypeMap.Add(typeof(IEnumerator)); } /// <summary>
/// Returns an array of the readable properties names exposed by an object
/// </summary>
/// <param name="obj">The object</param>
/// <returns>The properties name</returns>
public static string[] GetReadablePropertyNames(object obj)
/// <summary>
/// Returns an array of the writeable properties name exposed by a object
/// </summary>
/// <param name="obj">The object</param>
/// <returns>The properties name</returns>
public static string[] GetWriteablePropertyNames(object obj)
/// <summary>
/// Returns the type that the set expects to receive as a parameter when
/// setting a property value.
/// </summary>
/// <param name="obj">The object to check</param>
/// <param name="propertyName">The name of the property</param>
/// <returns>The type of the property</returns>
private static Type GetPropertyTypeForSetter(object obj, string propertyName)
{
Type type = obj.GetType();
if (obj is IDictionary)
{
IDictionary map = (IDictionary) obj;
object value = map[propertyName];
if (value == null)
else
{ type = value.GetType(); }
}
else
{
if (propertyName.IndexOf('.') > -1)
{
StringTokenizer parser = new StringTokenizer(propertyName, ".");
IEnumerator enumerator = parser.GetEnumerator();
while (enumerator.MoveNext())
{ propertyName = (string)enumerator.Current; type = ReflectionInfo.GetInstance(type).GetSetterType(propertyName); }
}
else
{ type = ReflectionInfo.GetInstance(type).GetSetterType(propertyName); }
}
return type;
}
/// <summary>
/// Returns the type that the set expects to receive as a parameter when
/// setting a property value.
/// </summary>
/// <param name="type">The type to check</param>
/// <param name="propertyName">The name of the property</param>
/// <returns>The type of the property</returns>
private static Type GetPropertyTypeForSetter(Type type, string propertyName)
{
if (propertyName.IndexOf('.') > -1)
{
StringTokenizer parser = new StringTokenizer(propertyName, ".");
IEnumerator enumerator = parser.GetEnumerator();
while (enumerator.MoveNext())
{ propertyName = (string)enumerator.Current; type = ReflectionInfo.GetInstance(type).GetSetterType(propertyName); }
}
else
{ type = ReflectionInfo.GetInstance(type).GetSetterType(propertyName); }
return type;
}
/// <summary>
/// Returns the type that the get expects to receive as a parameter when
/// setting a property value.
/// </summary>
/// <param name="obj">The object to check</param>
/// <param name="propertyName">The name of the property</param>
/// <returns>The type of the property</returns>
public static Type GetPropertyTypeForGetter(object obj, string propertyName)
{
Type type = obj.GetType();
if (obj is IDictionary)
{
IDictionary map = (IDictionary) obj;
object value = map[propertyName];
if (value == null)
{ type = typeof(object); }
else
{ type = value.GetType(); } }
else
{
if (propertyName.IndexOf('.') > -1)
{
StringTokenizer parser = new StringTokenizer(propertyName, ".");
IEnumerator enumerator = parser.GetEnumerator();
while (enumerator.MoveNext())
{ propertyName = (string)enumerator.Current; type = ReflectionInfo.GetInstance(type).GetGetterType(propertyName); } }
else
}
return type;
}
/// <summary>
/// Returns the type that the get expects to receive as a parameter when
/// setting a property value.
/// </summary>
/// <param name="type">The type to check</param>
/// <param name="propertyName">The name of the property</param>
/// <returns>The type of the property</returns>
public static Type GetPropertyTypeForGetter(Type type, string propertyName)
{
if (propertyName.IndexOf('.') > -1)
{
StringTokenizer parser = new StringTokenizer(propertyName, ".");
IEnumerator enumerator = parser.GetEnumerator();
while (enumerator.MoveNext())
{ propertyName = (string)enumerator.Current; type = ReflectionInfo.GetInstance(type).GetGetterType(propertyName); } }
else
return type;
}
private static object GetArrayProperty(object obj, string indexedName)
{
object value = null;
try
{
int startIndex = indexedName.IndexOf("[");
int length = indexedName.IndexOf("]");
string name = indexedName.Substring(0, startIndex);
string index = indexedName.Substring( startIndex+1, length-(startIndex+1));
int i = System.Convert.ToInt32(index);
if (name.Length > 0)
{ value = GetProperty(obj, name); }else
{ value = obj; }if (value is IList)
{ value = ((IList) value)[i]; }else
{ throw new ProbeException("The '" + name + "' property of the " + obj.GetType().Name + " class is not a List or Array."); }}
catch (ProbeException pe)
{ throw pe; }
catch(Exception e)
{ throw new ProbeException("Error getting ordinal value from .net object. Cause" + e.Message, e); }
return value;
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <param name="propertyName"></param>
/// <returns></returns>
protected static object GetProperty(object obj, string propertyName)
{
ReflectionInfo reflectionCache = ReflectionInfo.GetInstance(obj.GetType());
try
{
object value = null;
if (propertyName.IndexOf("[") > -1)
{ value = GetArrayProperty(obj, propertyName); }
else
{
if (obj is IDictionary)
{ value = ((IDictionary) obj)[propertyName]; }
else
{
PropertyInfo propertyInfo = reflectionCache.GetGetter(propertyName);
if (propertyInfo == null)
{ throw new ProbeException("No Get method for property " + propertyName + " on instance of " + obj.GetType().Name); }
try
{ value = propertyInfo.GetValue(obj, null); }
catch (ArgumentException ae)
{ throw new ProbeException(ae); }
catch (TargetException t)
{ throw new ProbeException(t); }
catch (TargetParameterCountException tp)
{ throw new ProbeException(tp); }
catch (MethodAccessException ma)
{ throw new ProbeException(ma); }
}
}
return value;
}
catch (ProbeException pe)
{ throw pe; }
catch(Exception e)
{ throw new ProbeException("Could not Set property '" + propertyName + "' for " + obj.GetType().Name + ". Cause: " + e.Message, e); }
}
private static void SetArrayProperty(object obj, string indexedName, object value)
{
try
{
int startIndex = indexedName.IndexOf("[");
int length = indexedName.IndexOf("]");
string name = indexedName.Substring(0, startIndex);
string index = indexedName.Substring( startIndex+1, length-(startIndex+1));
int i = System.Convert.ToInt32(index);
object list = null;
if (name.Length > 0)
{ list = GetProperty(obj, name); }
else
{ list = obj; }
if (list is IList)
{ ((IList) list)[i] = value; }
else
{ throw new ProbeException("The '" + name + "' property of the " + obj.GetType().Name + " class is not a List or Array."); }
}
catch (ProbeException pe)
catch (Exception e)
{ throw new ProbeException("Error getting ordinal value from .net object. Cause" + e.Message, e); }
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <param name="propertyName"></param>
/// <param name="propertyValue"></param>
protected static void SetProperty(object obj, string propertyName, object propertyValue)
{
ReflectionInfo reflectionCache = ReflectionInfo.GetInstance(obj.GetType());
try
{
if (propertyName.IndexOf("[") > -1)
{ SetArrayProperty(obj, propertyName, propertyValue); }
else
{
if (obj is IDictionary)
{ ((IDictionary) obj)[propertyName] = propertyValue; }
else
{
PropertyInfo propertyInfo = reflectionCache.GetSetter(propertyName);
if (propertyInfo == null)
{ throw new ProbeException("No Set method for property " + propertyName + " on instance of " + obj.GetType().Name); }
try
{ propertyInfo.SetValue(obj, propertyValue, null); }
catch (ArgumentException ae)
{ throw new ProbeException(ae); }
catch (TargetException t)
{ throw new ProbeException(t); }
catch (TargetParameterCountException tp)
{ throw new ProbeException(tp); }
catch (MethodAccessException ma)
{ throw new ProbeException(ma); }
}
}
}
catch (ProbeException pe)
{ throw pe; }
catch (Exception e)
{ throw new ProbeException("Could not Get property '" + propertyName + "' for " + obj.GetType().Name + ". Cause: " + e.Message, e); }}
/// <summary>
/// Return the specified property on an object.
/// </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 representing the return value of the invoked property.</returns>
public static object GetPropertyValue(object obj, string propertyName)
{
if (propertyName.IndexOf('.') > -1)
{
StringTokenizer parser = new StringTokenizer(propertyName, ".");
IEnumerator enumerator = parser.GetEnumerator();
object value = obj;
string token = null;
while (enumerator.MoveNext())
{
token = (string)enumerator.Current;
value = GetProperty(value, token);
if (value == null)
{ break; } }
return value;
}
else
}
/// <summary>
/// Set the specified property on an object
/// </summary>
/// <param name="obj">The Object on which to invoke the specified property.</param>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="propertyValue">The new value to set.</param>
public static void SetPropertyValue(object obj, string propertyName, object propertyValue)
{
if (propertyName.IndexOf('.') > -1)
{
StringTokenizer parser = new StringTokenizer(propertyName, ".");
IEnumerator enumerator = parser.GetEnumerator();
enumerator.MoveNext();
string currentPropertyName = (string)enumerator.Current;
object child = obj;
while (enumerator.MoveNext())
{
Type type = GetPropertyTypeForSetter(child, currentPropertyName);
object parent = child;
child = GetProperty(parent, currentPropertyName);
if (child == null)
{
try
catch (Exception e)
{ throw new ProbeException("Cannot set value of property '" + propertyName + "' because '" + currentPropertyName + "' is null and cannot be instantiated on instance of " + type.Name + ". Cause:" + e.Message, e); } }
currentPropertyName = (string)enumerator.Current;
}
SetProperty(child, currentPropertyName, propertyValue);
}
else
}
/// <summary>
/// Checks to see if a Object has a writable property/field be a given name
/// </summary>
/// <param name="obj"> The object to check</param>
/// <param name="propertyName">The property to check for</param>
/// <returns>True if the property exists and is writable</returns>
public static bool HasWritableProperty(object obj, string propertyName)
{
bool hasProperty = false;
if (obj is IDictionary)
else
{
if (propertyName.IndexOf('.') > -1)
{
StringTokenizer parser = new StringTokenizer(propertyName, ".");
IEnumerator enumerator = parser.GetEnumerator();
Type type = obj.GetType();
while (enumerator.MoveNext())
{ propertyName = (string)enumerator.Current; type = ReflectionInfo.GetInstance(type).GetGetterType(propertyName); hasProperty = ReflectionInfo.GetInstance(type).HasWritableProperty(propertyName); }
}
else
{ hasProperty = ReflectionInfo.GetInstance(obj.GetType()).HasWritableProperty(propertyName); }
}
return hasProperty;
}
/// <summary>
/// Checks to see if the Object have a property/field be a given name.
/// </summary>
/// <param name="obj">The Object on which to invoke the specified property.</param>
/// <param name="propertyName">The name of the property to check for.</param>
/// <returns>
/// True or false if the property exists and is readable.
/// </returns>
public static bool HasReadableProperty(object obj, string propertyName)
{
bool hasProperty = false;
if (obj is IDictionary)
{ hasProperty = ((IDictionary) obj).Contains(propertyName); }
else
{
if (propertyName.IndexOf('.') > -1)
{
StringTokenizer parser = new StringTokenizer(propertyName, ".");
IEnumerator enumerator = parser.GetEnumerator();
Type type = obj.GetType();
while (enumerator.MoveNext())
{ propertyName = (string)enumerator.Current; type = ReflectionInfo.GetInstance(type).GetGetterType(propertyName); hasProperty = ReflectionInfo.GetInstance(type).HasReadableProperty(propertyName); } }
else
}
return hasProperty;
}
/// <summary>
///
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool IsSimpleType(Type type)
{
if (_simpleTypeMap.Contains(type))
else if (type.IsSubclassOf(typeof(ICollection)))
{ return true; }
else if (type.IsSubclassOf(typeof(IDictionary)))
{ return true; }else if (type.IsSubclassOf(typeof(IList)))
{ return true; }
else if (type.IsSubclassOf(typeof(IEnumerable)))
{ return true; }else
{ return false; }}
/// <summary>
/// Calculates a hash code for all readable properties of a object.
/// </summary>
/// <param name="obj">The object to calculate the hash code for.</param>
/// <returns>The hash code.</returns>
public static int ObjectHashCode(object obj)
public static int ObjectHashCode(object obj, string[] properties)
{
ArrayList values = UnwrapObjectDownToSimpleTypes(obj, properties);
int hashCode = obj.GetType().FullName.GetHashCode();
foreach (object simpleObject in values)
{
if (simpleObject != null)
}
return hashCode;
}
public static bool AreObjectsEqual(object obj1, object obj2)
{ return AreObjectsEqual(obj1, obj2, GetReadablePropertyNames(obj1)); } public static bool AreObjectsEqual(object obj1, object obj2, string[] properties)
{
if (obj1 == null && obj2 != null)
return false;
if (obj1 != null && obj2 == null)
return false;
if (obj1 == null && obj2 == null)
return true;
if (obj1.GetType() != obj2.GetType())
return false;
ArrayList obj1Values = UnwrapObjectDownToSimpleTypes(obj1, properties);
ArrayList obj2Values = UnwrapObjectDownToSimpleTypes(obj2, properties);
if (obj1Values.Count != obj2Values.Count)
return false;
for (int i = 0; i < obj1Values.Count; i++)
{ if (obj1Values[i] != obj2Values[i]) return false; } return true;
}
public static ArrayList UnwrapObjectDownToSimpleTypes(object obj, ArrayList objectValues)
{ return UnwrapObjectDownToSimpleTypes(obj, GetReadablePropertyNames(obj), objectValues); }public static ArrayList UnwrapObjectDownToSimpleTypes(object obj, string[] properties)
{ return UnwrapObjectDownToSimpleTypes(obj, properties, new ArrayList()); } public static ArrayList UnwrapObjectDownToSimpleTypes(object obj, string[] properties, ArrayList objectValues)
{
ArrayList alreadyDigested = new ArrayList();
int hashcode = obj.GetType().FullName.GetHashCode();
for (int i = 0; i < properties.Length; i++)
{
object value = GetProperty(obj, properties[i]);
if (value != null)
{
if (IsSimpleType(value.GetType()))
else
{
// It's a Object
// Check to avoid endless loop (circular dependency)
if (value != obj)
{
if (!alreadyDigested.Contains(value))
}
}
}
else
}
return objectValues;
}
}
}
— END ObjectProbe —
— BEGIN CacheKey.Equals(object) —
public override bool Equals(object obj)
{
//-----------------------------------
if (this == obj) return true;
if (!(obj is CacheKey)) return false;
CacheKey cacheKey = (CacheKey)obj;
if (_maxResults != cacheKey._maxResults) return false;
if (_skipRecords != cacheKey._skipRecords) return false;
if (_type != cacheKey._type) return false;
if (_parameter is Hashtable)
else if (_parameter != null && _typeHandlerFactory.IsSimpleType(_parameter.GetType()))
{ if (_parameter != null ? !_parameter.Equals(cacheKey._parameter) : cacheKey._parameter != null) return false; } else
{
if (!ObjectProbe.AreObjectsEqual(_parameter, cacheKey._parameter, _properties))
}
if (_sql != null ? !_sql.Equals(cacheKey._sql) : cacheKey._sql != null) return false;
return true;
}
— END CacheKey.Equals(object) —