NullReferenceException when using PrefetchPath

Posts   
 
    
wtijsma
User
Posts: 252
Joined: 18-Apr-2006
# Posted on: 10-Feb-2011 13:14:31   

Hi,

One of my customers have an issue with a construct that used to work in older assembly versions, but in the most recent builds of the (2.6 and 3.0) assemblies it fails.

Because the type of construct is used in many places but doesn't work anymore it prevents them from a quick update path to the latest version (2.6 or 3.0).

Now the code is actually wrong, but they have similar constructs in many places that all fail with the same message. This code runs on the NorthWind database:


[TestMethod]
      public void SelectRelatedEntityIssue()
      {
         IQueryable<CurrencyRateEntity> q = metaData.CurrencyRate;
         q = q.WithPath(a => a.Prefetch(c => c.Currency));
         q = q.Where(d => d.CurrencyRateId == 1);
        
          IQueryable<CurrencyEntity> currency = from s in q select s.Currency;
          Console.WriteLine(currency.FirstOrDefault().CurrencyCode);
                
        }

The Prefetch Path here isn't necessary (removing it fixes the problem), and the way of selecting a currency through another entity is also a bit strange, but maybe the error message can be a bit clearer if it can't be fixed.

This is the exception:

Test method AdventureWorks.Web.Tests._99_BugsReproduction.SelectRelatedEntity.SelectRelatedEntityIssue threw exception: 
System.NullReferenceException: Object reference not set to an instance of an object.
at SD.LLBLGen.Pro.ORMSupportClasses.PersistenceCore.DetermineDifferentValuesForParameterizedPPath(IEntityCollectionCore rootEntities, IPrefetchPathCore prefetchPath, IPrefetchPathElementCore currentElement, Hashtable values) in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\ORMSupportClasses\Persistence\PersistenceCore.cs: line 695
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.FetchPrefetchPath(IEntityCollection2 rootEntities, IRelationPredicateBucket filterBucket, Int64 maxNumberOfItemsToReturn, ISortExpression sortClauses, IPrefetchPath2 prefetchPath, Boolean forceParameterizedPPath) in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\ORMSupportClasses\AdapterSpecific\DataAccessAdapterBase.cs: line 5171
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.FetchEntityCollection(IEntityCollection2 collectionToFill, IRelationPredicateBucket filterBucket, Int32 maxNumberOfItemsToReturn, ISortExpression sortClauses, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList excludedIncludedFields, Int32 pageNumber, Int32 pageSize) in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\ORMSupportClasses\AdapterSpecific\DataAccessAdapterBase.cs: line 2469
at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProvider2.ExecuteEntityProjection(QueryExpression toExecute) in c:\Myprojects\VS.NET Projects\LLBLGen Pro v3.0\Frameworks\LLBLGen Pro\RuntimeLibraries\LinqSupportClasses\LLBLGenProProvider2.cs: line 140
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 261
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 656
at System.Linq.Queryable.FirstOrDefault(IQueryable`1 source)
at AdventureWorks.Web.Tests._99_BugsReproduction.SelectRelatedEntity.SelectRelatedEntityIssue() in SelectRelatedEntity.cs: line 23

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 10-Feb-2011 13:39:45   

Crash happens here:


IEntityFieldCore valueField;
if(currentElement.Relation.StartEntityIsPkSide)
{
    valueField = currentRootEntity.GetFieldByName(currentElement.Relation.GetPKEntityFieldCore(j).Name);
}
else
{
    valueField = currentRootEntity.GetFieldByName(currentElement.Relation.GetFKEntityFieldCore(j).Name);
}
if(valueField.IsNull || (valueField.CurrentValue == null)) // <<<<<<<<<<<<<<<<<<<< 695

which suggests the field is null. This is impossible with generated relations. We'll see if we can repro it on adventureworks

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 10-Feb-2011 16:05:33   

I can reproduce it, so I'll look into it, but there's something wrong with the query: The prefetch path is inside a wrapped query, which will automatically make it being useless. Also, you fetch a related entity which is the one being prefetched. This too makes the prefetch useless.

To fetch the related entities like they do in the above query, you don't need the prefetch path.

It would also be great if you mentioned which version they do use which works. It might help narrowing down what change might have caused this.

Fetching elements from q instead of the related entity works btw:

IQueryable<CurrencyRateEntity> q = metaData.CurrencyRate;
q = q.Where(d => d.CurrencyRateId == 1);
q = q.WithPath(a => a.Prefetch(c => c.Currency));

foreach(var v in q) { }
Frans Bouma | Lead developer LLBLGen Pro
wtijsma
User
Posts: 252
Joined: 18-Apr-2006
# Posted on: 10-Feb-2011 16:16:08   

Otis wrote:

To fetch the related entities like they do in the above query, you don't need the prefetch path.

I know, that's exactly what I mentioned in the post simple_smile

wtijsma wrote:

The Prefetch Path here isn't necessary (removing it fixes the problem), and the way of selecting a currency through another entity is also a bit strange, but maybe the error message can be a bit clearer if it can't be fixed.

However they have a lot of these types of constructs in their application (about 50%), including way more complex usages of these types of queries.

I've instructed them to rewrite their queries, but it's just a lot (too much) of work (and a lot of risks) for now so doesn't help them with solving other issues that actually require them to update to a new version.

I'm looking up the version number now, BRB...

Update: they sent it in attachments Update 2: damn only 2 attachments allowed so here's the numbers:

  • DQE: 2.6.8.612
  • Linq: 2.6.8.1001
  • ORM: 2.6.8.624
Attachments
Filename File size Added on Approval
001.png 35,111 10-Feb-2011 16:16.27 Approved
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 10-Feb-2011 16:16:26   

I doubt this has ever worked. The problem is this: the prefetch path is nested in the outer query. The prefetch path is for CurrencyRate. However as the prefetch path is nested, it gets moved to the outside. However, the elements fetched there are Currency entities, not CurrencyRate entities.

The cause is the s.Currency in the wrapping query. But as you are fetching that entity, you can simply delete the prefetch path call, as it's not necessary.

It might have 'worked' in an old version which did a bad job moving the prefetch path, not sure...

(edit) the versions are very old, we had many fixes after those builds, so I can't say what caused it, other than assume the original code did a lousy job moving the prefetch path node which actually made the query 'work'. (2.6 RTM-ed on June 6th, so the version they're using is very old.

(edit) also, as they're using an old version, I doubt the queries are that complex (as older linq providers keeled over rather quickly when something looked complicated), but migrating to a later linq build will also let them run in a breaking change we had to make: projections don't append Distinct() by default anymore, which was a bug which was corrected late 2008.

I still think it's wise to fix the queries, as they will eventually have to update to a later version and the queries are wrong. It's ok to remove the prefetch path nodes in these queries, as they don't serve any purpose anyway (i.o.w.: the resultsets won't change)

It's perhaps also wise to rewrite the queries from the other side, i.e. fetch currency and filter on currencyrate, instead of the other way around, which makes things perhaps simpler in some cases.

Frans Bouma | Lead developer LLBLGen Pro