- Home
- LLBLGen Pro
- Bugs & Issues
Type Converter Invoked With Wrong ParameterType
Joined: 10-Dec-2009
Hi,
I am using .NET 3.5. I am using postgresql database. The NPGSQL version is 2.0.6.0. The LLBLGen version is 2.6.9.903 (that's the file version of the SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll I am using).
I have written my own type converter for converting between C# .NET System.DateTime and NpgsqlTypes.NpgsqlTime. The full code for this converter is further below.
When I load an entity that maps to a DB table that has a column "Occurence Time" of NPG type "Time without timezone", the ConvertFrom function of the converter is invoked. I am expecting the value parameter of the function to be of type NpgsqlTypes.NpgsqlTime, but the type actually is System.DateTime. This violates the definition of my converter for which the CanConvertFrom function returns true for the NpgsqlTypes.NpgsqlTime type only, not for System.DateTime. I have however put a breakpoing in the CanConvertFrom function, and I have verified that it is never invoked by LLBLGen. I guess this is not a big problem though.
What's mind boggling is that it looks like LLBLGen performs a pre-conversion from Npg "Time without time zone" into a .NET System.DateTime, instead of converting into NpgsqlTypes.NpgsqlTime before invoking my customized converter. Could you indicate whether this is the desired LLBLgen behavior? I am actually fine with my converter being invoked with a System.DateTime and not a NpgsqlTime. I just want to check with you that it's not an LLBLGen bug, or a mis-usage of LLBLGen type converter on my side.
Attached is a screenshot of my debugging in VS, with the parameter of type System.DateTime instead of NpgsqlTypes.NpgsqlTime.
Here is the line in the persistence info that indicates the converter to be used:
base.AddElementFieldMapping( "PeriodicityEntity", "OccurenceTime", "OccurenceTime", true, (int)NpgsqlDbType.Time, 0, 0, 0, false, "", new SD.LLBLGen.Pro.TypeConverters.NpgsqlTimeConverter(), typeof(System.DateTime), 1 );
Here is the full code details of my type converter (inspired by the Enum converter given in http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=7355).
using System;
using System.ComponentModel;
using NpgsqlTypes;
namespace SD.LLBLGen.Pro.TypeConverters
{
/// <remarks>This implementation is targeted to be used as a converter which is instantiated through code, not through TypeDescriptor.GetConverter.
/// This means that this converter can be used in an attribute on a type, but the implementation can fail (but doesn't have to) in that
/// situation as it's not tested in that scenario. The core difference is that 'context' is ignored in the converter code.</remarks>
[Description("Converter with as core type System.Time, for mapping a field with a .NET type System.DateTime onto a Time without time zone database field")]
public class NpgsqlTimeConverter: TypeConverter
{
public NpgsqlTimeConverter()
{
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (sourceType == typeof(NpgsqlTime));
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (destinationType == typeof(NpgsqlTime));
}
public override object ConvertFrom(
ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value == null)
{
return null;
}
if (value.GetType() != typeof(NpgsqlTime))
{
throw new NotSupportedException(
"Conversion from a value of type '" + value.GetType().ToString() + "' to System.DateTime is not supported");
}
NpgsqlTime npgTime = (NpgsqlTime)value;
DateTime utcNow = DateTime.UtcNow;
return
new DateTime(
utcNow.Year, utcNow.Month, utcNow.Day,
npgTime.Hours, npgTime.Minutes, npgTime.Seconds,
npgTime.Milliseconds, DateTimeKind.Utc);
}
public override object ConvertTo(
ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (value == null)
{
return null;
}
if (!(value is DateTime))
{
throw new ArgumentException("Value isn't of type System.DateTime", "value");
}
if (destinationType != typeof(NpgsqlTime))
{
throw new NotSupportedException("Conversion from System.DateTime to a value of type '" + destinationType.ToString() + "' isn't supported");
}
return new NpgsqlTime(((DateTime)value).TimeOfDay);
}
public override object CreateInstance(ITypeDescriptorContext context, System.Collections.IDictionary propertyValues)
{
return new DateTime();
}
}
}
Here is the exception that is raised by my converter because the type of the parameter is wrong:
A fatal problem has occured.
Message: Conversion from a value of type 'System.DateTime' to System.DateTime is not supported
Type: System.NotSupportedException
in: NpgsqlIntervalConverter
Stack:
at SD.LLBLGen.Pro.TypeConverters.NpgsqlTimeConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value) in C:\Documents and Settings\ybastiand\My Documents\dev\BI_1.1\Infra\LLBLGen\TypeConverters\NpgsqlIntervalTimeSpanTypeConverter\NpgsqlIntervalTimeSpanTypeConverter\NpgsqlTimeConverter.cs:line 37
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.ReadRowIntoFields(Object[] values, IEntityFields2 rowDestination, List`1 fieldIndexToOrdinal, IFieldPersistenceInfo[] fieldsPersistenceInfo)
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.CreateEntityInstanceFromReaderRow(IEntityFactory2 entityFactory, IFieldPersistenceInfo[] fieldsPersistenceInfo, Int32 numberOfFieldsInQuery, InheritanceHierarchyType typeOfHierarchy, Dictionary`2 hierarchyFieldAliasesToOrdinals, List`1 fieldIndexToOrdinal, Dictionary`2 entityFieldStartIndexesPerEntity, Boolean hasExcludedFields, IFieldPersistenceInfo[] persistenceInfosForRowReader, Object[] valuesOfRow, IEntityFactory2& entityFactoryToUse)
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.ExecuteMultiRowRetrievalQuery(IRetrievalQuery queryToExecute, IEntityFactory2 entityFactory, IEntityCollection2 collectionToFill, IFieldPersistenceInfo[] fieldsPersistenceInfo, Boolean allowDuplicates, IEntityFields2 fieldsUsedForQuery)
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.FetchEntityCollectionInternal(IEntityCollection2 collectionToFill, IRelationPredicateBucket& filterBucket, Int32 maxNumberOfItemsToReturn, ISortExpression sortClauses, ExcludeIncludeFieldsList excludedIncludedFields, Int32 pageNumber, Int32 pageSize)
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.FetchPrefetchPath(IEntityCollection2 rootEntities, IRelationPredicateBucket filterBucket, Int64 maxNumberOfItemsToReturn, ISortExpression sortClauses, IPrefetchPath2 prefetchPath, Boolean forceParameterizedPPath)
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.FetchEntityCollection(IEntityCollection2 collectionToFill, IRelationPredicateBucket filterBucket, Int32 maxNumberOfItemsToReturn, ISortExpression sortClauses, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList excludedIncludedFields, Int32 pageNumber, Int32 pageSize)
at BI.Biopacs.Core.Data35.DatabaseSpecific.DataAccessAdapter.FetchEntityCollection(IEntityCollection2 collectionToFill, IRelationPredicateBucket filterBucket, Int32 maxNumberOfItemsToReturn, ISortExpression sortClauses, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList excludedIncludedFields, Int32 pageNumber, Int32 pageSize) in C:\Documents and Settings\ybastiand\My Documents\dev\BI_1.1\Biopacs\Core\Data35\DatabaseSpecific\DataAccessAdapterCustom.cs:line 1077
at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProvider2.ExecuteEntityProjection(QueryExpression toExecute)
at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.ExecuteExpression(Expression handledExpression)
at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.Execute(Expression expression)
at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.System.Linq.IQueryProvider.Execute(Expression expression)
at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProQuery`1.Execute()
at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProQuery`1.SD.LLBLGen.Pro.LinqSupportClasses.ILLBLGenProQuery.Execute[TResult]()
at BI.Biopacs.Core.Database.Repositories.DBNotificationRuleRepository.Find() in C:\Documents and Settings\ybastiand\My Documents\dev\BI_1.1\Biopacs\Core\Database\Repositories\DBNotificationRuleRepository.cs:line 74
at BI.Biopacs.Core.BusinessServices.NotificationRuleService.GetRules() in C:\Documents and Settings\ybastiand\My Documents\dev\BI_1.1\Biopacs\Core\BusinessServices\NotificationRuleService.cs:line 44
at BI.Biopacs.Core.Notification.Administration.RuleListPresenter.GetRules() in C:\Documents and Settings\ybastiand\My Documents\dev\BI_1.1\Biopacs\Core\Notification\Administration\RuleListPresenter.cs:line 212
at BI.Biopacs.Core.Notification.Administration.RuleListPresenter.ViewLoad(Object sender, EventArgs e) in C:\Documents and Settings\ybastiand\My Documents\dev\BI_1.1\Biopacs\Core\Notification\Administration\RuleListPresenter.cs:line 318
at BI.Biopacs.Core.Notification.Administration.Forms.ControlRuleListView.FormLoad(Object sender, EventArgs e) in C:\Documents and Settings\ybastiand\My Documents\dev\BI_1.1\Biopacs\Core\Notification\Administration\Forms\ControlRuleListView.cs:line 121
at System.Windows.Forms.UserControl.OnLoad(EventArgs e)
at DevExpress.XtraEditors.XtraUserControl.OnLoad(EventArgs e)
at System.Windows.Forms.UserControl.OnCreateControl()
at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
at System.Windows.Forms.Control.CreateControl()
at System.Windows.Forms.Control.ControlCollection.Add(Control value)
at BI.Biopacs.Core.Notification.Administration.Forms.NotificationMainView.SetPanel(Object View) in C:\Documents and Settings\ybastiand\My Documents\dev\BI_1.1\Biopacs\Core\Notification\Administration\Forms\NotificationMainView.cs:line 204
at BI.Biopacs.Core.Notification.Administration.NotificationMainPresenter._MainView_PresenterRulesButtonClicked(Object sender, EventArgs e) in C:\Documents and Settings\ybastiand\My Documents\dev\BI_1.1\Biopacs\Core\Notification\Administration\NotificationMainPresenter.cs:line 153
at BI.Biopacs.Core.Notification.Administration.Forms.NotificationMainView.RulesButton_ItemClick(Object sender, ItemClickEventArgs e) in C:\Documents and Settings\ybastiand\My Documents\dev\BI_1.1\Biopacs\Core\Notification\Administration\Forms\NotificationMainView.cs:line 235
at DevExpress.XtraBars.BarItem.OnClick(BarItemLink link)
at DevExpress.XtraBars.BarBaseButtonItem.OnClick(BarItemLink link)
at DevExpress.XtraBars.BarItemLink.OnLinkClick()
at DevExpress.XtraBars.BarItemLink.OnLinkAction(BarLinkAction action, Object actionArgs)
at DevExpress.XtraBars.BarButtonItemLink.OnLinkAction(BarLinkAction action, Object actionArgs)
at DevExpress.XtraBars.BarItemLink.OnLinkActionCore(BarLinkAction action, Object actionArgs)
at DevExpress.XtraBars.ViewInfo.BarSelectionInfo.ClickLink(BarItemLink link)
at DevExpress.XtraBars.ViewInfo.BarSelectionInfo.UnPressLink(BarItemLink link)
at DevExpress.XtraBars.Ribbon.Handler.BaseRibbonHandler.OnUnPressItem(DXMouseEventArgs e, RibbonHitInfo hitInfo)
at DevExpress.XtraBars.Ribbon.Handler.BaseRibbonHandler.OnUnPress(DXMouseEventArgs e, RibbonHitInfo hitInfo)
at DevExpress.XtraBars.Ribbon.Handler.BaseRibbonHandler.OnMouseUp(DXMouseEventArgs e)
at DevExpress.XtraBars.Ribbon.Handler.RibbonHandler.OnMouseUp(DXMouseEventArgs e)
at DevExpress.XtraBars.Ribbon.RibbonControl.OnMouseUp(MouseEventArgs e)
at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.Control.WndProc(Message& m)
at DevExpress.Utils.Controls.ControlBase.WndProc(Message& m)
at DevExpress.XtraBars.Ribbon.RibbonControl.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
The TypeConverter should convert from mapped .NET Type to another desired .NET Type. So you should expect a System.DateTime.
For more info, please check the source code of the BooleanNumeric TypeConverter available in the SDK package.
Joined: 10-Dec-2009
I actually have made my converter work for .Net to .Net so I am fine if the conversion always is .Net to .Net.
I am still puzzled though because we use another customized converter, between .NET TimeSpan and NpgsqlInterval (see interval DB type at http://www.postgresql.org/docs/8.2/static/datatype-datetime.html).
I have put a breakpoint in this converter, and the ConvertFrom method does get invoked with a value parameter of type NpgsqlInterval (as per attached screenshot). If I understand your post correctly, it should be TimeSpan always?
Here is the persistence info for the interval converter usage (one among others):
base.AddElementFieldMapping( "QueueItemEntity", "ElapsedTime", "ElapsedTime", true, (int)NpgsqlDbType.Interval, 0, 0, 0, false, "", new SD.LLBLGen.Pro.TypeConverters.NpgsqlIntervalConverter(), typeof(System.TimeSpan), 10 );
Here is the interval converter. This was implemented by another developer at my company, and the code looks very close to the BooleanNumeric converter.
using System;
using System.ComponentModel;
using NpgsqlTypes;
namespace SD.LLBLGen.Pro.TypeConverters
{
/// <summary>
/// </summary>
/// <remarks>This implementation is targeted to be used as a converter which is instantiated through code, not through TypeDescriptor.GetConverter.
/// This means that this converter can be used in an attribute on a type, but the implementation can fail (but doesn't have to) in that
/// situation as it's not tested in that scenario. The core difference is that 'context' is ignored in the converter code.</remarks>
[Description("Converter with as core type System.TimeSpan, for mapping a field with a .NET type System.TimeSpan onto a Interval database field")]
public class NpgsqlIntervalConverter : TypeConverter
{
/// <summary>
/// Initializes a new instance of the <see cref="NpgsqlIntervalTimeSpanTypeConverter"/> class.
/// </summary>
public NpgsqlIntervalConverter()
{
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return ((sourceType.FullName == "NpgsqlTypes.NpgsqlInterval") || (sourceType.FullName == "System.TimeSpan"));
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return ((destinationType == typeof(NpgsqlInterval)) || (destinationType == typeof(TimeSpan)));
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value == null)
{
return null;
}
if (value.GetType() == typeof(NpgsqlInterval))
{
NpgsqlInterval npginterval = (NpgsqlInterval)value;
System.TimeSpan timespan = new System.TimeSpan(npginterval.Days, npginterval.Hours, npginterval.Minutes, npginterval.Seconds);
return timespan;
}
return value;
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (value == null)
{
//throw new ArgumentNullException("value", "Value can't be null");
return null;
}
if (!(value is TimeSpan))
{
throw new ArgumentException("Value isn't of type System.TimeSPan", "value");
}
if (destinationType == typeof(NpgsqlInterval))
{
return new NpgsqlInterval((TimeSpan)value);
}
if (destinationType == typeof(TimeSpan))
{
return value;
}
throw new NotSupportedException("Conversion from System.TimeSpan to '" + destinationType.ToString() + "' isn't supported");
}
public override object CreateInstance(ITypeDescriptorContext context, System.Collections.IDictionary propertyValues)
{
return TimeSpan.Zero;
}
}
}
Please check this thread: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=10551 It's discussing the same issue, and there is a TypeConverter source code posted on the second page.
Please post back here, if you either work it out or not.
Joined: 10-Dec-2009
Hi,
Well that's funny because the Haaz user (Manu) who posted the NpgsqlInterval converter in the thread you are referring to is the developer from my company who I was referring to earlier. So the NpgsqlInterval converter that I have posted in this thread is the same one as in the other thread (with a few more modifications that we have made since the other thread's post).
At any case I now have the NpgsqlTime converter working for performing the desired conversion (includes day light saving time). The only remaining question is why for this converter, LLBLGen invokes ConvertFrom with a value of type System.DateTime instead of type NpgsqlTypes.NpgsqlTime. This doesn't seem consistent with our other converter (the NpgsqlInterval one) that gets invoked by LLBLgen with a value of type Npgsql.NpgsqlInterval (and not a System.TimeSpan).
Thanks in advance for your help,
Yves.
ybastian wrote:
Hi,
Well that's funny because the Haaz user (Manu) who posted the NpgsqlInterval converter in the thread you are referring to is the developer from my company who I was referring to earlier. So the NpgsqlInterval converter that I have posted in this thread is the same one as in the other thread (with a few more modifications that we have made since the other thread's post).
At any case I now have the NpgsqlTime converter working for performing the desired conversion (includes day light saving time). The only remaining question is why for this converter, LLBLGen invokes ConvertFrom with a value of type System.DateTime instead of type NpgsqlTypes.NpgsqlTime. This doesn't seem consistent with our other converter (the NpgsqlInterval one) that gets invoked by LLBLgen with a value of type Npgsql.NpgsqlInterval (and not a System.TimeSpan).
Thanks in advance for your help,
Yves.
The ADO.NET provider used (Npgsql) returns the values from the DB in .NET types through the datareader. LLBLGen Pro then checks whether the field a value has to be placed in has a type converter. If so, it calls ConvertFrom on the type converter of the field, by passing in the value read from the datareader. No conversion done whatsoever. See DataAccessAdapterBase.ReadRowIntoFields sourcecode for details
Npgsql has a connection string property, Use Extended Types, which is false by default. If you set it to true, it will return the timetz value in NpgsqlTime objects, otherwise in DateTime. See the npgsql user docs http://npgsql.projects.postgresql.org/docs/manual/UserManual.html