I am in the middle of updating DataMapper (in various ways) for 3.5. One set of pretty major changes is around type coercion, especially when dealing with nullable<t> type values.
Can you give specific value examples of what's failing (what is the value from the FormView, and the exact data type of the property)?
using System; using System.Collections.Generic; using System.Reflection; using System.ComponentModel; using Csla; namespace XXXX.BLL.Utils { /// /// Map data from a source into a target object /// by copying public property values. /// /// public static class DataMapper { internal static class Resources { internal static readonly string PropertyCopyFailed = "property map error"; } #region Map from IDictionary /// /// Copies values from the source into the /// properties of the target. /// /// A name/value dictionary containing the source values. /// An object with properties to be set from the dictionary. /// /// The key names in the dictionary must match the property names on the target /// object. Target properties may not be readonly or indexed. /// public static void Map(System.Collections.IDictionary source, object target) { Map(source, target, false); } /// /// Copies values from the source into the /// properties of the target. /// /// A name/value dictionary containing the source values. /// An object with properties to be set from the dictionary. /// A list of property names to ignore. /// These properties will not be set on the target object. /// /// The key names in the dictionary must match the property names on the target /// object. Target properties may not be readonly or indexed. /// public static void Map(System.Collections.IDictionary source, object target, params string[] ignoreList) { Map(source, target, false, ignoreList); } /// /// Copies values from the source into the /// properties of the target. /// /// A name/value dictionary containing the source values. /// An object with properties to be set from the dictionary. /// A list of property names to ignore. /// These properties will not be set on the target object. /// If , any exceptions will be supressed. /// /// The key names in the dictionary must match the property names on the target /// object. Target properties may not be readonly or indexed. /// public static void Map( System.Collections.IDictionary source, object target, bool suppressExceptions, params string[] ignoreList) { List ignore = new List(ignoreList); foreach (string propertyName in source.Keys) { if (!ignore.Contains(propertyName)) { try { SetPropertyValue(target, propertyName, source[propertyName]); } catch (Exception ex) { if (!suppressExceptions) throw new ArgumentException( String.Format("{0} ({1})", Resources.PropertyCopyFailed, propertyName), ex); } } } } #endregion #region Map from Object /// /// Copies values from the source into the /// properties of the target. /// /// An object containing the source values. /// An object with properties to be set from the dictionary. /// /// The property names and types of the source object must match the property names and types /// on the target object. Source properties may not be indexed. /// Target properties may not be readonly or indexed. /// public static void Map(object source, object target) { Map(source, target, false); } /// /// Copies values from the source into the /// properties of the target. /// /// An object containing the source values. /// An object with properties to be set from the dictionary. /// A list of property names to ignore. /// These properties will not be set on the target object. /// /// The property names and types of the source object must match the property names and types /// on the target object. Source properties may not be indexed. /// Target properties may not be readonly or indexed. /// public static void Map(object source, object target, params string[] ignoreList) { Map(source, target, false, ignoreList); } /// /// Copies values from the source into the /// properties of the target. /// /// An object containing the source values. /// An object with properties to be set from the dictionary. /// A list of property names to ignore. /// These properties will not be set on the target object. /// If , any exceptions will be supressed. /// /// /// The property names and types of the source object must match the property names and types /// on the target object. Source properties may not be indexed. /// Target properties may not be readonly or indexed. /// /// Properties to copy are determined based on the source object. Any properties /// on the source object marked with the equal /// to false are ignored. /// /// public static void Map( object source, object target, bool suppressExceptions, params string[] ignoreList) { List ignore = new List(ignoreList); PropertyInfo[] sourceProperties = GetSourceProperties(source.GetType()); foreach (PropertyInfo sourceProperty in sourceProperties) { string propertyName = sourceProperty.Name; if (!ignore.Contains(propertyName)) { try { SetPropertyValue( target, propertyName, sourceProperty.GetValue(source, null)); } catch (Exception ex) { if (!suppressExceptions) throw new ArgumentException( String.Format("{0} ({1})", Resources.PropertyCopyFailed, propertyName), ex); } } } } private static PropertyInfo[] GetSourceProperties(Type sourceType) { List result = new List(); PropertyDescriptorCollection props = TypeDescriptor.GetProperties(sourceType); foreach (PropertyDescriptor item in props) if (item.IsBrowsable) result.Add(sourceType.GetProperty(item.Name)); return result.ToArray(); } #endregion /// /// Sets an object's property with the specified value, /// coercing that value to the appropriate type if possible. /// /// Object containing the property to set. /// Name of the property to set. /// Value to set into the property. public static void SetPropertyValue(object target, string propertyName, object value) { PropertyInfo propertyInfo = target.GetType().GetProperty(propertyName); if (value == null) { propertyInfo.SetValue(target, value, null); } else { Type pType = Utilities.GetPropertyType(propertyInfo.PropertyType); Type vType = Utilities.GetPropertyType(value.GetType()); if (pType.Equals(vType)) { // types match, just copy value propertyInfo.SetValue(target, value, null); } else { // types don't match, try to coerce types if (pType.Equals(typeof(Guid))) { propertyInfo.SetValue(target, new Guid(value.ToString()), null); } else if (pType.IsEnum && vType.Equals(typeof(string))) { propertyInfo.SetValue(target, System.Enum.Parse(pType, value.ToString()), null); } // '------Modified Part Start Here ----- else if (pType.FullName == "System.Int32" & vType.FullName == "System.String") { if (value.Equals(string.Empty)) { value = "0"; propertyInfo.SetValue(target, Convert.ChangeType(value, pType), null); } else { propertyInfo.SetValue(target, Convert.ChangeType(value, pType), null); } } else if (pType.FullName == "System.Decimal" & vType.FullName == "System.String") { if (value.Equals(string.Empty)) { value = "0"; propertyInfo.SetValue(target, Convert.ChangeType(value, pType), null); } else { propertyInfo.SetValue(target, Convert.ChangeType(value, pType), null); } } //-------------------------------------End of Modification--------------------------------------------------------- else { propertyInfo.SetValue(target, Convert.ChangeType(value, pType), null); } } } } } }
not sure if this post editor handles code correctly...
I do think the CoerceValue() code can easily back-port to 3.0.3.
So far I haven’t done anything in DataMapper that is .NET 3.5 or even
.NET 3.0 specific.
Can you provide specific source/target examples that were
failing so I can ensure they work in the new model in 3.5?
Rocky
From: skaue
[mailto:cslanet@lhotka.net]
Sent: Friday, December 07, 2007 7:09 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] DataMapper and the pain
I did a svn checkout on the trunk and noticed a lot of
improvements on datamapper, but I don't suppose these improvements can be
easily applied to 3.0.2, or?
I ended up with making my own datamapper using the code from this post:
http://forums.lhotka.net/forums/thread/4221.aspx
This solved saving decimal and some other convertion issues. I have yet to try
a datetime field... looking forward to the agony of that.
[…]
// '------Modified Part Start Here -----
else if (pType.FullName == "System.Int32" & vType.FullName == "System.String")
{
if (value.Equals(string.Empty))
{
value = "0";
propertyInfo.SetValue(target, Convert.ChangeType(value, pType), null);
}
else
{
propertyInfo.SetValue(target, Convert.ChangeType(value, pType), null);
}
}
else if (pType.FullName == "System.Decimal" & vType.FullName == "System.String")
{
if (value.Equals(string.Empty))
{
value = "0";
propertyInfo.SetValue(target, Convert.ChangeType(value, pType), null);
}
else
{
propertyInfo.SetValue(target, Convert.ChangeType(value, pType), null);
}
}
//-------------------------------------End of Modification---------------------------------------------------------
Would it be possible to add your CoerceValue() to the 3.0.x branch, or is that branch "locked" ;-)
OK, thanks for that. Numeric (primitive) types need to go to a 0
value from string.Empty or null.
My 3.5 code now does that (it didn’t).
The 3.0.x branch is not locked, but it is for bug fixes only. I’ll
consider this a bug fix (though I think it is a stretch). As a result,
3.0.x is now 3.0.4 and includes a back-port of the 3.5 code for type coercion.
Rocky
Well 3.0.4 is not a release – it is test mode stuff. And
most likely 3.0.4 won’t “release” until 3.5 does – it’ll
be the final vehicle for 3.0 bug fixes up to that point.
The problem I have is that 3.5 really is 3.5. I’m not sure
I can pull off the compiler directive trick to make 3.5 work for .NET 2.0/3.0. Best
case is maybe using compiler directives to get back to 2.0a and 3.0a, and even
then I’m not sure.
So the 3.0.x branch will probably be alive for bug fixes for a
long time :(
Rocky
From: ajj3085
[mailto:cslanet@lhotka.net]
Sent: Friday, December 07, 2007 12:01 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: RE: DataMapper and the pain
Doh! And I just finished upgrading to 3.0.3... ah
well. I'll wait until I it something that affects me for next upgrade :-)
Hehe.
3.5 is a FAR bigger release than 3.0 in terms of impacting
existing functionality. This is partially because it requires 2.0 SP1 (aka
2.0a), which introduces a bunch of new features to 2.0 itself (and thus
3.0/3.5). The same is true for 3.0 SP1 (aka 3.0a)… And there are new
compilers.
And new ASP.NET features/controls – including (I’m
told) a major change to ASP.NET data binding.
3.0a has a whole bunch of enhancements to WPF/WCF/WF.
The net result is that skipping to 3.5 is wise (imo). It really
represents a higher level of maturity of the 3.0 stuff, plus the new compilers.
Rocky
From: ajj3085
[mailto:cslanet@lhotka.net]
Sent: Friday, December 07, 2007 12:18 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: RE: RE: DataMapper and the pain
Ahh, ok. I just like keeping up with the office
releases.
It seems like even though the clr hasn't changed for 3.5, 3.5 is more than just
additive like 3.0 was, because the compiler has changed. Hopefully MS
will slow down a bit, as it is I'm going to skip 3.0 entirely.
Hi,
I am working with .NET 2.0 and hence the same CSLA.NET framework. Should I jump directly to .NET 3.5 and CSLA.NET or is it worth working with .NET 3.0 before 3.5?
Our next project will start in Febuary so I was thinking to directly use 3.5.
Tahir
http://www.lhotka.net/weblog/MicrosoftNETAndCSLANETVersionConfusion.aspx
From: tna55
[mailto:cslanet@lhotka.net]
Sent: Friday, December 07, 2007 2:03 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: RE: RE: RE: DataMapper and the pain
Hi,
I am working with .NET 2.0 and hence the same CSLA.NET framework. Should I
jump directly to .NET 3.5 and CSLA.NET or is it worth working with .NET 3.0
before 3.5?
Our next project will start in Febuary so I was thinking to directly use
3.5.
Tahir
skaue:Ok. I will do an update on the svn checkout then to grab the changes. :-)
I see this thread went off topic along the way. I guess me and my colleague will "upgrade" to 3.5 and vs2008 some time in january, and then this nightmare will be all over.... :P
Please let me know if you encounter any issues with the 3.0.4 code. Since that is also the intended 3.5 code, if there are issues I'd rather fix them now than later!
private static object CoerceValue(Type propertyType, Type valueType, object value)Any suggestions for a fix?
{
if (propertyType.Equals(valueType))
{
// types match, just return value
return value;
}
else
{
if (propertyType.IsGenericType)
{
if (propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
if (value == null)
return null;
else if (valueType.Equals(typeof(string)) && (string)value == string.Empty)
return null;
}
propertyType = Utilities.GetPropertyType(propertyType);
}
if (propertyType.IsEnum && valueType.Equals(typeof(string)))
return Enum.Parse(propertyType, value.ToString());
if (propertyType.IsPrimitive && valueType.Equals(typeof(string)) && string.IsNullOrEmpty((string)value))
value = 0;
try
{
return Convert.ChangeType(value, Utilities.GetPropertyType(propertyType));
}
catch
{
TypeConverter cnv = TypeDescriptor.GetConverter(Utilities.GetPropertyType(propertyType));
if (cnv != null && cnv.CanConvertFrom(value.GetType()))
return cnv.ConvertFrom(value);
else
throw;
}
}
}
propertyType.IsPrimitiveto
propertyType.IsPrimitive || System.Decimal
That fix is too evil :)
In the wish list I had the idea of calling a delegate converter - so you could provide conversions that would effectively compliment this default implementation. I think I'll do that after all (I though I had it solved with these changes, but perhaps not).
I may still handle the Decimal case as you suggest, but the delegate would allow resolving any other failed type conversions without requiring modifications to DataMapper each time.
What is your specific issue?
From: jrosenthal
[mailto:cslanet@lhotka.net]
Sent: Friday, May 01, 2009 9:05 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: DataMapper and the pain
These posts are about 18 mo old - Has anything been done
with this is 3.6.2 because I believe I am having similar issues with in class
containing an int.
In 3.6 you have two choices. Either use the ignore list, or
create a DataMap to explicitly list the fields/properties that are being mapped
to and from.
Rocky
Copyright (c) Marimer LLC