Unknown discriminator value '<some value>' in row read from db, which doesn't exist in hierarchy of root '<some entity>'.

Posts   
 
    
clint
User
Posts: 150
Joined: 15-Nov-2005
# Posted on: 07-Dec-2023 01:28:47   

LLBLGen Pro version + buildnr: LLBLGen Pro V5.6 RTM(5.6.1)

Runtime library version: SD.LLBLGen.Pro.ORMSupportClasses.dll has version 5.6.0.0

Template:Adapter. Using .NET Core 3.1

Database: PostgreSQL 13

Problem:

I have a class hierarchy using TargetPerEntityHierarchy. The target table is Assessment. The class hierarchy has a parent class called AssessmentEntity and two subtypes called REAssessmentEntity and PPAssessmentEntity.

The Assessment table field I'm using for a discriminator is called IsRE and has database type int2. I use value 2 for AssessmentEntity, value 1 for REAssessmentEntity and value 0 for PPAssessmentEntity.

Assessment.IsRE has database type int2. LLBLGen designer maps this to .NET type short. However, I want it to map to an AssessmentEntity.IsRE field of type byte.

So, I made a type converter to convert between byte and short and applied it to the field mapping for Assessment entity field IsRE in the designer.

When fetching an REAssessmentEntity, I get the error: Unknown discriminator value '1' in row read from db, which doesn't exist in hierarchy of root 'AssessmentEntity'.

I noticed that the fetch will call my type converter to convert byte in code to int2 in the database. Then I see it write out the query to the output window. Then after that, I get the exception. So, I'm guessing it is getting the error when trying to convert the query results to an REAssessmentEntity. I noticed it never called my type converter to convert the int2 data from the database to byte in the code.

Similar problem in this thread:

I found this old thread that was similar except his discriminator field was a GUID in code and a string in the database. https://llblgen.com/tinyforum/Thread/14502/1

In that thread, if I'm interpreting it correctly, it seemed to say you might change LLBLGen runtime to run the type converter before assigning the database data for the discriminator field to the entity.

Stack Trace

at SD.LLBLGen.Pro.ORMSupportClasses.InheritanceInfoProvider.GetEntityFactoryTargetPerEntityHierarchy(String entityName, Object[] values)
at SD.LLBLGen.Pro.ORMSupportClasses.InheritanceInfoProvider.GetEntityFactory(String entityName, Object[] values, Dictionary`2 entityFieldStartIndexesPerEntity)
at SD.LLBLGen.Pro.ORMSupportClasses.ModelInfoProviderBase.GetEntityFactory(String entityName, Object[] values, Dictionary`2 entityFieldStartIndexesPerEntity)
at GCS.Data.FactoryClasses.EntityFactoryBase2`1.GetEntityFactory(Object[] fieldValues, Dictionary`2 entityFieldStartIndexesPerEntity) in D:\repos_wt\SyncLLBLGen\GCSLRMS_DotNet\LLBLGen\GCS.DAL Solution\GeneratedCode\DatabaseGeneric\FactoryClasses\EntityFactories.cs:line 50
at SD.LLBLGen.Pro.ORMSupportClasses.EntityFactoryCore2.SD.LLBLGen.Pro.ORMSupportClasses.IEntityFactoryCore.GetEntityFactory(Object[] fieldValues, Dictionary`2 entityFieldStartIndexesPerEntity)
at SD.LLBLGen.Pro.ORMSupportClasses.PersistenceCore.CreateEntityInstanceFromReaderRow(IEntityFactoryCore entityFactory, InheritanceHierarchyType typeOfHierarchy, Object[] valuesOfRow, Dictionary`2 indicesForEnumConverts, Dictionary`2 indicesForTypeConverters, Dictionary`2 hierarchyFieldValueArrayLengths, Dictionary`2 entityFieldStartIndexesPerEntity, Boolean selfServicing, IEntityFactoryCore& entityFactoryToUse)
at SD.LLBLGen.Pro.ORMSupportClasses.EntityMaterializerBase.HandleRowData(Object[] valuesOfRow, FetchCounters counters)
at SD.LLBLGen.Pro.ORMSupportClasses.EntityMaterializerBase.Materialize(Func`4 valueReadErrorHandler, String& failureErrorText)
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterCore.ExecuteMultiRowRetrievalQuery(IRetrievalQuery queryToExecute, IEntityFactory2 entityFactory, IEntityCollection2 collectionToFill, IFieldPersistenceInfo[] fieldsPersistenceInfo, Boolean allowDuplicates, IEntityFields2 fieldsUsedForQuery)
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterCore.FetchEntityCollectionInternal(QueryParameters parameters)
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterCore.FetchEntityCollection(QueryParameters parameters)
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterCore.FetchEntityCollection(IEntityCollection2 collectionToFill, IRelationPredicateBucket filterBucket)
at GCS.SharedBL.REAssessmentManager.GetREAssessmentForParcel(Int32 propertyId, REAssessmentEntity& reAssessment, Boolean includeTaxItems, Boolean includePayments, Boolean includeValuations) in D:\repos_wt\SyncLLBLGen\GCSLRMS_DotNet\GCS.SharedBL\GCS.SharedBL Solution\GCS.SharedBL\REAssessmentManager.cs:line 241
at GCS.SharedBL.REAssessmentManager.GetREAssessmentForParcel(Int32 propertyId, REAssessmentEntity& reAssessment) in D:\repos_wt\SyncLLBLGen\GCSLRMS_DotNet\GCS.SharedBL\GCS.SharedBL Solution\GCS.SharedBL\REAssessmentManager.cs:line 196
at GCS.TaxCollection.BL.SetLotteryCreditClaimCountHelper.FindData(Int16 taxYear, String userDefinedId, Boolean isRealEstate, Notification& notification, Int32& assessmentId, Int16& lotteryCreditsClaimed, String& lotteryCreditBatchNumber, DateTime& certificationDate, String& legalDescription, String& propertyAddressString, String& taxAddressString) in D:\repos_wt\SyncLLBLGen\GCSLRMS_DotNet\GCS.TaxCollection\GCS.TaxCollection Solution\GCS.TaxCollection.BL\SetLotteryCreditClaimCountHelper.cs:line 63
at LandNav.Areas.Processes.ViewModels.SetLotteryCreditClaimCountViewModel.GetFindData(Int16 taxYear, Boolean isRE, String userDefinedId) in D:\repos_wt\SyncLLBLGen\GCSLRMS_DotNet\LandNav\LandNav Solution\LandNav\Areas\Processes\ViewModels\SetLotteryCreditClaimCountViewModel.cs:line 316
at LandNav.Areas.Processes.Controllers.SetLotteryCreditClaimCountController._Find(Int16 taxYear, Boolean isRE, String userDefinedId) in D:\repos_wt\SyncLLBLGen\GCSLRMS_DotNet\LandNav\LandNav Solution\LandNav\Areas\Processes\Controllers\SetLotteryCreditClaimCountController.cs:line 74
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
clint
User
Posts: 150
Joined: 15-Nov-2005
# Posted on: 07-Dec-2023 01:45:03   

You may be wondering why I'm trying to use type byte in the code for the discriminator field when the database is using type int2.

It's because we have two versions of our software that I want to make as similar as possible.

One is using a SQL Server database and the other is using PostgreSQL. The PostgreSQL version will eventually replace the SQL Server version.

The PostgreSQL version of the code and database is pretty much identical to the SQL Server version except it has some extra tables and some extra fields in some tables.

Each version is using their own LLBLGen project.

The SQL Server version uses database type tinyint for Assessment.IsRE which LLBLGen maps to type byte in the code.

The PostgreSQL version uses database type int2 for Assessment.IsRE which LLBLGen maps to type short in the code.

In order to make both projects as similar as possible, I wanted the PostgreSQL version of our project to also use byte. So that is why I added the TypeConverter to the PostgreSQL version of the project. That way, most of the code and the LLBLGen project itself remains identical.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39760
Joined: 17-Aug-2003
# Posted on: 07-Dec-2023 10:28:36   

We did add a workitem for the issue reported in the thread you linked to back in 2010. It was closed in 2011 'as default conversions should take care of it', but that was premature, as default conversions are ... type converters.

So in short, it's still not supported and as the workitem was closed 12 years ago, we never looked at it again. We did refactor the whole pipeline but didn't take into account this edge case.

In v5.11 (and presumably in 5.6 too, but not sure) the factory is obtained first, then the values are converted (null values, enums, type converters) and then the values are stored in the entity, this is done in ReadRowIntoFields. The factory is obtained by a direct value retrieval from a lookup and there it goes wrong, a short doesn't match the byte values in the key set of the dictionary as the type mismatches.

The one workaround I could think of is: The generated factory class for AssessmentEntity is called AssessmentEntityFactory. Create a partial class of that class and override GetEntityFactory. This method is called when the factory is to be determined based on the values read from the table. These values are passed in. At that moment, you can change them. So in the override convert fieldValues[(int)AssessmentFieldIndex.IsRE] to byte using Convert.ToByte(), it then should work.

We'll reopen the workitem

Frans Bouma | Lead developer LLBLGen Pro
clint
User
Posts: 150
Joined: 15-Nov-2005
# Posted on: 07-Dec-2023 19:14:44   

Otis wrote:

The one workaround I could think of is: The generated factory class for AssessmentEntity is called AssessmentEntityFactory. Create a partial class of that class and override GetEntityFactory. This method is called when the factory is to be determined based on the values read from the table. These values are passed in. At that moment, you can change them. So in the override convert fieldValues[(int)AssessmentFieldIndex.IsRE] to byte using Convert.ToByte(), it then should work.

That worked for me. I had to do that for REAssessmentFactory and PPAssessmentFactory too. Thanks!

Had to change my ByteShortConverter type converter code too.

Just like jlsanmartin did in this thread https://llblgen.com/tinyforum/Thread/11150/1 I had to change my ByteShortConverter.ConvertFrom() method to now allow converting from byte to byte since the IsRE field is now a byte before the ByteShortConverter is called.

Didn't need my ByteShortConverter

I saw that you mentioned "default" conversions. I wasn't aware of any default converters.
Then I saw this topic in the documentation: https://www.llblgen.com/documentation/5.0/designer/Functionality%20Reference/SystemTypeConverters.htm.
So, in the LLBLGen designer I removed the ByteShortConverter from the field mapping for AssessmentEntity.IsRE and the code still worked.

I later found that in PersistenceInfoProvider.InitAssessmentEntityMappings() that it added new SD.LLBLGen.Pro.ORMSupportClasses.ChangeTypeConverter<System.Byte>() as the type converter for the field mapping for AssessmentEntity.IsRE.

I wish there was some way the LLBLGen Designer could show you that it is going to do a type conversion. But I don't know if that is possible since the documentation talks about how some target frameworks don't support type conversion and the designer doesn't know what framework you are using until you generate the source code.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39760
Joined: 17-Aug-2003
# Posted on: 08-Dec-2023 07:52:44   

Glad you found a way to work around it. Yeah it's a pretty big list to cover when we'd want to list all possible conversions simple_smile So we decided to let the designer show a warning/error if something doesn't convert at runtime.

Frans Bouma | Lead developer LLBLGen Pro