Is it possible to use one numeric TypeConverters for both nullable and non nullable fields?

Posts   
 
    
TomDog
User
Posts: 623
Joined: 25-Oct-2005
# Posted on: 15-Dec-2010 03:40:38   

I ended up having to have two for each numeric type, one for nullable and one for non nullable fields.

The only difference being that the nullable version returns null when converting from null while the non nullable one returns 0.

Example below

    /// <summary>
    /// Converts a number into Int32 in Oracle
    /// </summary>
    public class IntegerNonNullNumericConverter : BaseNumericConverter<Int32>
    {
    }

    /// <summary>
    /// Converts a nullable number into nullable Int32 in Oracle
    /// </summary>
    public class IntegerNumericConverter : IntegerNonNullNumericConverter
    {
        #region Overrides of BaseNumericConverter<decimal>

        /// <summary>
        /// Converts null to a default value of the type, override to convert null to null.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <returns></returns>
        protected override object ConvertFromNull(ITypeDescriptorContext context)
        {
            return null;
        }

        #endregion
    }

I originally had just one TypeConverter that returned null for null but when I had non nullable fields such as SeverityRating returning null for queries like below it would blow up on any null rows.

from rls in this.RiskLikelihoodSeverity
select rls.InitialSeverity.SeverityRating

Jeremy Thomas
daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 15-Dec-2010 05:26:21   

It seems that if you want to split that behavior up, then two TypeConverters is the way to go.

David Elizondo | LLBLGen Support Team
TomDog
User
Posts: 623
Joined: 25-Oct-2005
# Posted on: 15-Dec-2010 11:02:08   

daelmo wrote:

It seems that if you want to split that behavior up, then two TypeConverters is the way to go.

I don't really WANT to split that behavior it's more a case that it seems i HAVE to. My scenario is that I'm using typeconverters so I get the same results in Oracle as for SQL server.

Jeremy Thomas
Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 15-Dec-2010 11:50:40   

Me too, I think you will have to use 2 TypeConverters.

Unless you want to modify your model to have all fields to be nullable if they allow null in the database. This way you will only need one type converter, because you will not need the null to Zero conversion case.

TomDog
User
Posts: 623
Joined: 25-Oct-2005
# Posted on: 15-Dec-2010 18:35:02   

Walaa wrote:

Unless you want to modify your model to have all fields to be nullable if they allow null in the database.

They currenty are.

Walaa wrote:

This way you will only need one type converter, because you will not need the null to Zero conversion case.

I still do need the null to Zero conversion case for right joins that return a null row as in my query example, else I get an exception in the LLBL RTL (SeverityRating is non nullable in db too).

Jeremy Thomas
Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 16-Dec-2010 08:59:59   

I still do need the null to Zero conversion case for right joins that return a null row as in my query example, else I get an exception in the LLBL RTL (SeverityRating is non nullable in db too).

Would you please post a code snippet that returns the exception with right join, and please post the exception details too.

TomDog
User
Posts: 623
Joined: 25-Oct-2005
# Posted on: 16-Dec-2010 09:33:28   

Walaa wrote:

I still do need the null to Zero conversion case for right joins that return a null row as in my query example, else I get an exception in the LLBL RTL (SeverityRating is non nullable in db too).

Would you please post a code snippet that returns the exception with right join, and please post the exception details too.

from rls in this.RiskLikelihoodSeverity
select rls.InitialSeverity.SeverityRating
SELECT "LPA_L1"."SEVERITY_RATING" AS "SeverityRating"
FROM
 "AQD"."RM_SEVERITY" "LPA_L1" 
RIGHT JOIN
"AQD"."RM_RISK_LIKELIHOOD_SEVERITY" "LPA_L2" ON "LPA_L1"."SEVERITY_ID"="LPA_L2"."INITIAL_SEVERITY_ID"

Results from SQl Developer

4.4
4.4
4.4
4.4
4.4
1.1
3.3
(null)
2.2
3.3
(null)
(null)
2.2
1.1
4.4
(null)

Null object cannot be converted to a value type. Message Null object cannot be converted to a value type.

 at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
   at System.Convert.ChangeType(Object value, Type conversionType)
   at lambda_method(Closure , Object[] , Int32[] )
   at SD.LLBLGen.Pro.LinqSupportClasses.DataProjectorToObjectList`1.AddRowToResults(IList projectors, Object[] rawProjectionResult) in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\LinqSupportClasses\DataProjectorToObjectList.cs:line 114
   at SD.LLBLGen.Pro.LinqSupportClasses.DataProjectorToObjectList`1. SD.LLBLGen.Pro.ORMSupportClasses.IGeneralDataProjector.AddProjectionResultToContainer(List`1 valueProjectors, Object[] rawProjectionResult) in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\LinqSupportClasses\DataProjectorToObjectList.cs:line 99
   at SD.LLBLGen.Pro.ORMSupportClasses.ProjectionUtils.FetchProjectionFromReader( List`1 valueProjectors, IGeneralDataProjector projector, IDataReader datasource, Int32 maxNumberOfItemsToReturn, Int32 pageNumber, Int32 pageSize, Boolean clientSideLimitation, Boolean clientSideDistinctFiltering, Boolean clientSidePaging, UniqueList`1 stringCache, Dictionary`2 typeConvertersToRun) in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\ORMSupportClasses\Projection\ProjectionUtils.cs:line 202
   at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.FetchProjection( List`1 valueProjectors, IGeneralDataProjector projector, IRetrievalQuery queryToExecute, Dictionary`2 typeConvertersToRun) in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\ORMSupportClasses\AdapterSpecific\DataAccessAdapterBase.cs:line 1715
   at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.FetchProjection( List`1 valueProjectors, IGeneralDataProjector projector, IEntityFields2 fields, IRelationPredicateBucket filter, Int32 maxNumberOfItemsToReturn, ISortExpression sortClauses, IGroupByCollection groupByClause, Boolean allowDuplicates, Int32 pageNumber, Int32 pageSize) in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\ORMSupportClasses\AdapterSpecific\DataAccessAdapterBase.cs:line 1675
   at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProvider2.ExecuteValueListProjection(QueryExpression toExecute) in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\LinqSupportClasses\LLBLGenProProvider2.cs:line 178
   at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.ExecuteExpression( Expression handledExpression) in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\LinqSupportClasses\LLBLGenProProviderBase.cs:line 264
   at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.Execute( Expression expression) in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\LinqSupportClasses\LLBLGenProProviderBase.cs:line 93
   at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.System.Linq.IQueryProvider.Execute( Expression expression) in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\LinqSupportClasses\LLBLGenProProviderBase.cs:line 696
   at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProQuery`1.Execute() in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\LinqSupportClasses\LLBLGenProQuery.cs:line 87
   at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProQuery`1.System.Collections. IEnumerable.GetEnumerator() in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\LinqSupportClasses\LLBLGenProQuery.cs:line 179 
Jeremy Thomas
Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 16-Dec-2010 10:29:36   

Here is my test case.

SQL Query (Northwind):

Select OrderId from Orders o
Right Join Customers c on o.CustomerID = c.CustomerID
Where c.City = 'Paris'

Which returns the following in SQL:

NULL
10738
10907
10964
11043

In Linq I have the following:

var q = from c in metaData.Customer
        join o in metaData.Order 
        on c.CustomerId equals o.CustomerId into CustOrder
        where c.City == "Paris"
        from order in CustOrder.DefaultIfEmpty()
        select new
        {
            OrderId = order.OrderId,
        };

Which generates the following SQL:

    Query: SELECT [LPA_L2].[OrderID] AS [OrderId] FROM ( [Northwind].[dbo].[Customers] [LPA_L1]  LEFT JOIN [Northwind].[dbo].[Orders] [LPA_L2]  ON  [LPA_L1].[CustomerID] = [LPA_L2].[CustomerID]) WHERE ( ( ( ( [LPA_L1].[City] = @p1))))
    Parameter: @p1 : String. Length: 15. Precision: 0. Scale: 0. Direction: Input. Value: "Paris".

And the following is the resultSet returned:

0
10738
10907
10964
11043

Also if you want to project it to OrderEntity, the following will work, and 0 will be returned too.

var q = from c in metaData.Customer
        join o in metaData.Order 
        on c.CustomerId equals o.CustomerId into CustOrder
        where c.City == "Paris"
        from order in CustOrder.DefaultIfEmpty()
        select new OrderEntity()
        {
            OrderId = order.OrderId,
        };

As a variation, you can do the following check to be on the safe side:

var q = from c in metaData.Customer
        join o in metaData.Order 
        on c.CustomerId equals o.CustomerId into CustOrder
        where c.City == "Paris"
        from order in CustOrder.DefaultIfEmpty()
        select new
        {
            OrderId = order != null ? order.OrderId : 0,
        };

Which results in the following SQL:

    Query: SELECT CASE WHEN CASE WHEN ( ( [LPA_L2].[OrderID] IS NOT NULL)) THEN 1 ELSE 0 END=1 THEN [LPA_L2].[OrderID] ELSE @p2 END AS [OrderId] FROM ( [Northwind].[dbo].[Customers] [LPA_L1]  LEFT JOIN [Northwind].[dbo].[Orders] [LPA_L2]  ON  [LPA_L1].[CustomerID] = [LPA_L2].[CustomerID]) WHERE ( ( ( ( [LPA_L1].[City] = @p3))))
    Parameter: @p2 : Int32. Length: 0. Precision: 0. Scale: 0. Direction: Input. Value: 0.
    Parameter: @p3 : String. Length: 15. Precision: 0. Scale: 0. Direction: Input. Value: "Paris".

And yet the same resultSet is returned.

TomDog
User
Posts: 623
Joined: 25-Oct-2005
# Posted on: 16-Dec-2010 10:51:05   

And you have a TypeConverter on OrderId that returns null for null?

Jeremy Thomas
Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 17-Dec-2010 10:33:34   

I don't have any TypeConverter.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39908
Joined: 17-Aug-2003
# Posted on: 17-Dec-2010 10:38:45   

Additionally, doesn't the type converter get the target type passed in? This should be either Nullable<T> or T, which you can use to determine whether you should return 0 or null, or am I missing something? (the framework passes in the target type to convert to, which IMHO should be Nullable<int> for example, for nullable int fields, and int for non-nullable int fields)

Frans Bouma | Lead developer LLBLGen Pro
TomDog
User
Posts: 623
Joined: 25-Oct-2005
# Posted on: 17-Dec-2010 11:31:20   

Otis wrote:

Additionally, doesn't the type converter get the target type passed in? This should be either Nullable<T> or T, which you can use to determine whether you should return 0 or null, or am I missing something? (the framework passes in the target type to convert to, which IMHO should be Nullable<int> for example, for nullable int fields, and int for non-nullable int fields)

The ConvertTo function gets the target type but not the ConvertFrom which is the one in question, in this scenario all it gets is a null in value.

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            return TypeConverterHelper.IsNull(value) ? ConvertFromNull(context) : Convert.ChangeType(value, typeof (T), culture);
        }

Without a typeconverter the query works fine so I guess there is some code in the LBLL RTL which turns null into 0 but doesn't if there is a typeconverter.

Jeremy Thomas
daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 19-Dec-2010 21:08:08   

I understand your dilemma. Using a typeConverter is a slightly different flow when the value to be set is determined. So, this is something you must resolve in the TypeConverter side. I know you can't discriminate in the ConvertFrom method if the value is null. So you should stick with your derived class solution. After all they are just two classes, if the problem is setting the TypeConverter in the Designer to a lot of entities, you could create a simple plug-in that sets TypeConverterA for all NotNullable fields, and TypeConverterB to the others.

David Elizondo | LLBLGen Support Team
TomDog
User
Posts: 623
Joined: 25-Oct-2005
# Posted on: 20-Dec-2010 10:57:54   

daelmo wrote:

I understand your dilemma. Using a typeConverter is a slightly different flow when the value to be set is determined. So, this is something you must resolve in the TypeConverter side. I know you can't discriminate in the ConvertFrom method if the value is null. So you should stick with your derived class solution. After all they are just two classes, if the problem is setting the TypeConverter in the Designer to a lot of entities, you could create a simple plug-in that sets TypeConverterA for all NotNullable fields, and TypeConverterB to the others.

So, as I expected, the short answer is yes, if you want the same behaviour as without a TypeConverter you need to have separate TypeConverters for nullable and non nullable fields (assuming nullable db fields map to nullable dotnet types and vice versa).

It's an extra class per numeric type, extra unit tests + an opportunity to make a mistake in the designer by selecting the wrong one, but still not that big a deal. I just want some clarity around it.

Jeremy Thomas