Exception for nullable enum columns on PocoWithLinq typed views

Posts   
 
    
Posts: 30
Joined: 19-Dec-2022
# Posted on: 19-Dec-2022 12:50:23   

Hi,

we are using LLBLGen 5.8.7 with SelfServicing template group. Our database is Oracle 11 and our target .net version 4.6.1/netstandard2.0 (happens for both). Also the SD.LLBLGen.Pro.ORMSupportClasses.dll is from the 5.8.7 nuget.

Now on a typed view generated as PocoWithLinqQuery we have a nullable enum colum and if we run a query against it like this:

var meta = new LinqMetaData();
var query = from invoiceDetail in meta.InvoiceDetail
    where invoiceDetail.ContractType == ContractType.CustomerContract
    select invoiceDetail;
var test = query.First();

we get the following exception:

 System.InvalidCastException: Invalid cast from 'Enumerations.ContractType' to 'System.Nullable`1[[Enumerations.ContractType, Enumerations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'.
   at System.Convert.DefaultToType(IConvertible value, Type targetType, IFormatProvider provider)
   at System.Enum.System.IConvertible.ToType(Type type, IFormatProvider provider)
   at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
   at System.Convert.ChangeType(Object value, Type conversionType)
   at SD.LLBLGen.Pro.ORMSupportClasses.DbSpecificCreatorBase.GetRealValue(Object currentValue, TypeConverter typeConverterToUse, Type actualDotNetType, Boolean targetIsEnumtyped)
   at SD.LLBLGen.Pro.ORMSupportClasses.DbSpecificCreatorBase.CreateParameter(IEntityFieldCore field, IFieldPersistenceInfo persistenceInfo, ParameterDirection direction, Object valueToSet)
   at SD.LLBLGen.Pro.ORMSupportClasses.FieldCompareValuePredicate.ToQueryText(Boolean inHavingClause)
   at SD.LLBLGen.Pro.ORMSupportClasses.PredicateExpression.ToQueryText(Boolean inHavingClause)
   at SD.LLBLGen.Pro.ORMSupportClasses.PredicateExpression.ToQueryText(Boolean inHavingClause)
   at SD.LLBLGen.Pro.ORMSupportClasses.PredicateExpression.ToQueryText(Boolean inHavingClause)
   at SD.LLBLGen.Pro.ORMSupportClasses.PredicateExpression.ToQueryText(Boolean inHavingClause)
   at SD.LLBLGen.Pro.ORMSupportClasses.PredicateExpression.ToQueryText()
   at SD.LLBLGen.Pro.ORMSupportClasses.DynamicQueryEngineBase.AppendWhereClause(IPredicate filter, QueryFragments destination, IQuery query)
   at SD.LLBLGen.Pro.DQE.Oracle.DynamicQueryEngine.CreateSelectDQImpl(QueryParameters parameters, DbConnection connectionToUse, Boolean emitQueryHints)
   at SD.LLBLGen.Pro.DQE.Oracle.DynamicQueryEngine.CreatePagingSelectDQ(QueryParameters parameters, DbConnection connectionToUse)
   at SD.LLBLGen.Pro.ORMSupportClasses.DynamicQueryEngineBase.CreateSelectDQ(QueryParameters parameters, DbConnection connectionToUse)
   at SD.LLBLGen.Pro.ORMSupportClasses.DaoBase.CreateQueryFromElements(ITransaction transactionToUse, QueryParameters parameters)
   at SD.LLBLGen.Pro.ORMSupportClasses.DaoBase.GetAsProjection(List`1 valueProjectors, IGeneralDataProjector projector, ITransaction transactionToUse, QueryParameters parameters)
   at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProvider.ExecuteValueListProjection(QueryExpression toExecute)
   at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.ExecuteExpression(Expression handledExpression, Type typeForPostProcessing)
   at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.PerformExecute(Expression expression, Type resultType)
   at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.System.Linq.IQueryProvider.Execute[TResult](Expression expression)
   at System.Linq.Queryable.First[TSource](IQueryable`1 source)

If we do the same query on a standard entity instead of a typed view it works.

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 20-Dec-2022 00:23:58   

What's the database type of that field? Are you using a TypeConverter?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 20-Dec-2022 11:06:32   

I don't think it's related to a type converter, I can reproduce it on SQL Server in adapter too. So there might be a bug somewhere. We'll look into it.

[Test]
public void TypedViewWithLinqQueryAndNullableEnum()
{
    using(var adapter = new DataAccessAdapter())
    {
        var metaData = new LinqMetaData(adapter);
        var q = from p in metaData.ProductPocoLinq
                where p.CategoryId == CategoryType.Beverages
                select p;
        var results = q.ToList();
        Assert.AreEqual(11, results.Count);
    }
}


[Test]
public void EntityWithLinqQueryAndNullableEnum()
{
    using(var adapter = new DataAccessAdapter())
    {
        var metaData = new LinqMetaData(adapter);
        var q = from p in metaData.ProductEnumTest
                where p.CategoryId == CategoryType.Beverages
                select p;
        var results = q.ToList();
        Assert.AreEqual(11, results.Count);
    }
}

Entity one succeeds, typed view one fails, which is odd as both run through the same code.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 30
Joined: 19-Dec-2022
# Posted on: 20-Dec-2022 12:29:33   

Yes, the column has no type converter and and DB type is Number(9).

During debugging I noticed that the property ActualDotNetType of the persistenceInfo of the field is null for the TypedView in contrast to Int32 set for an Entity. This results in passing the wrong parameter to Convert.ChangeType(..) in DbSpecificCreatorBase.GetRealValue(..) which the leads to the exception.

I also noticed that LLBLGen generates for the colum the following line in ModellInfoProvider

this.AddElementFieldInfo("InvoiceDetailTypedView", "ContractState", typeof(Nullable<Enumerations.ContractState>), false, false, true, false, (int)InvoiceDetailFieldIndex.ContractState, 0, 0, 9)

The last boolean parameter (7th parameter) is "isNullable" but this column is optional. Therefore it should be true, shouldn't it?

The same applies to PersistenceInfoProvider:

this.AddElementFieldMapping("InvoiceDetailTypedView", "ContractState", "CONTRACTSTATE", false, "Decimal", 0, 9, 0, false, string.Empty, null, typeof(System.Int32), 25);

There it's the 4th parameter which indicates that the column is nullable and it should be true, but is false here. But I don't know if that's related at all to the main problem.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 20-Dec-2022 12:36:59   

I saw that too and tried that as my first attempt to fix it but that's not the root of the issue. The issue here is that the typed view fetch (also for typedlist/tvf calls) with linq are fetches of a projection and the persistence info for the field isn't obtainable as it's a POCO. That IsNullable isn't used for producing select queries so it's always set to false.

This ends up in the built-in converter code where it tries to do a Convert.ChangeType(enumTypedValue, actualTypeToConvertTo) where actualTypeToConvertTo is a nullable type and ChangeType can't handle that.

Converting it first to the real enum type fixed it but not for when you'd do where invoiceDetail.ContractType.Value == ContractType.CustomerContract which should also work. The fix was trivial however, the Convert.ChangeType was only needed in very specific cases so we limited the function to only handle these specific cases and all situations now work fine.

Fixed in hotfix builds 5.8.8 and 5.9.4 which should now be available.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 30
Joined: 19-Dec-2022
# Posted on: 20-Dec-2022 12:51:08   

Nice! Thanks very much for you fast response and fix.