linqMetaData throws exception, possibly due to subsequent outer joins

Posts   
 
    
Isz
User
Posts: 108
Joined: 26-Jan-2006
# Posted on: 30-Oct-2008 20:51:05   

Hi...

I am using the LinqMetaData object to execute LINQ queries in LLBLGen and have encountered a reproducible bug using the latest 2.6 Oct 6th release.

I believe the problem occurs when the linq expression calls for an outer join, followed by a subsequent outer join.

A few months ago, I encountered a similar problem like this which is documented in this thread:

http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=13521

While that issue was since resolved, it appears a similar issue is causing the exception I am now experiencing.

To produce this, I have created a table called asset, with a nullable FK relation to a table called work_package on asset_id. To filter records correctly, we need an additional relation to a table called deficiency on work_package_id (this relation is not nullable, but both joins must be outer).

The linq expression desired looks like this:


        [Test()]
        public void TestLLBLGenBugWithNewVersionOct6_Test2()
        {
            IDataAccessAdapter adapter = DataAccessAdapterFactory.CreateStandardAdapter();
            LinqMetaData linqMetaData = new LinqMetaData(adapter);

            var query = from a in linqMetaData.Asset
                        join wp in linqMetaData.WorkPackage on a.AssetId equals wp.AssetId into workPackages
                        from workPackage in workPackages.DefaultIfEmpty()
                        join d in linqMetaData.Deficiency on workPackage.WorkPackageId equals d.WorkPackageId into deficiencies
                        from deficiency in deficiencies.DefaultIfEmpty()

                        select new WorkPackage
                        {
                            AssetId = a.AssetId,
                            AssetName = a.Name,
                            AssetNumber = a.AssetNumber.ToString(),
                            AssetLetter = a.AssetLetter,
                        };

            this._workPackageList = new List<WorkPackage>(query.ToArray());
        }
    }



The exception raised is this:



 Value cannot be null.
Parameter name: key
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.ArgumentNullException: Value cannot be null.
Parameter name: key

Source Error:

Line 2077:      {
Line 2078:          IEntityFactory2 toReturn = null;
Line 2079:          _factoryPerType.TryGetValue(typeOfEntity, out toReturn);
Line 2080:          return toReturn;
Line 2081:      }


Source File: C:\Projects\GT_Clients\TEC\TecAms\Code\Tec.TecAms\Tec.TecAms.DataEntities.Generic\FactoryClasses\EntityFactories.cs    Line: 2079

Stack Trace:

[ArgumentNullException: Value cannot be null.
Parameter name: key]
   System.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument) +41
   System.Collections.Generic.Dictionary`2.FindEntry(TKey key) +2667593
   System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value) +14
   Tec.TecAms.DataEntities.Generic.FactoryClasses.EntityFactoryFactory.GetFactory(Type typeOfEntity) in C:\Projects\GT_Clients\TEC\TecAms\Code\Tec.TecAms\Tec.TecAms.DataEntities.Generic\FactoryClasses\EntityFactories.cs:2079
   Tec.TecAms.DataEntities.Generic.FactoryClasses.ElementCreator.GetFactoryImpl(Type typeOfEntity) in C:\Projects\GT_Clients\TEC\TecAms\Code\Tec.TecAms\Tec.TecAms.DataEntities.Generic\FactoryClasses\EntityFactories.cs:2184
   Tec.TecAms.DataEntities.Generic.FactoryClasses.ElementCreator.GetFactory(Type typeOfEntity) in C:\Projects\GT_Clients\TEC\TecAms\Code\Tec.TecAms\Tec.TecAms.DataEntities.Generic\FactoryClasses\EntityFactories.cs:2109
   SD.LLBLGen.Pro.LinqSupportClasses.LinqUtils.CreateEntityInstanceFromEntityType(Type entityType, IElementCreatorCore generatedCodeElementCreator) +108
   SD.LLBLGen.Pro.LinqSupportClasses.LinqUtils.GetEntityName(Object value, IElementCreatorCore generatedCodeElementCreator) +58
   SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.CreateDynamicRelation(Object left, Object right, JoinHint joinType, String aliasLeft, String aliasRight, IPredicate onClause) +67
   SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleJoinExpression(JoinExpression expressionToHandle) +4047
   SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle) +837
   SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle) +187
   SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleGroupJoinExpression(GroupJoinExpression expressionToHandle) +42
   SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleGroupJoinExpression(GroupJoinExpression expressionToHandle) +35
   SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle) +702
   SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle) +187
   SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleAndProcessJoinExpressionSide(SetExpression side, Expression sideSelector, Expression& handledSide, Expression& handledSideSelector, String& aliasSide) +26
   SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleJoinExpression(JoinExpression expressionToHandle) +836
   SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle) +837
   SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle) +187
   SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.HandleExpressionTree(Expression expression) +872
   SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.Execute(Expression expression) +10
   SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.System.Linq.IQueryProvider.Execute(Expression expression) +14
   SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator() +36
   System.Linq.Buffer`1..ctor(IEnumerable`1 source) +239
   System.Linq.Enumerable.ToArray(IEnumerable`1 source) +79
   Tec.TecAms.ReportServer.WorkPackageReport.FillReportData() in C:\Projects\GT_Clients\TEC\TecAms\Code\Tec.TecAms\Tec.TecAms.ReportServer\WorkPackageReport.cs:62
   Tec.TecAms.UI.Web.Secure.Reports.WorkPackageReport.FillReportData() in C:\Projects\GT_Clients\TEC\TecAms\Code\Tec.TecAms\Tec.TecAms.UI.Web\Secure\Reports\WorkPackageReport.aspx.cs:76
   Tec.TecAms.UI.Web.Secure.Reports.WorkPackageReport.Setup() in C:\Projects\GT_Clients\TEC\TecAms\Code\Tec.TecAms\Tec.TecAms.UI.Web\Secure\Reports\WorkPackageReport.aspx.cs:62
   Tec.TecAms.UI.Web.Secure.Reports.WorkPackageReport.Page_Load(Object sender, EventArgs e) in C:\Projects\GT_Clients\TEC\TecAms\Code\Tec.TecAms\Tec.TecAms.UI.Web\Secure\Reports\WorkPackageReport.aspx.cs:86
   System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +15
   System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +33
   System.Web.UI.Control.OnLoad(EventArgs e) +99
   GrowingTechnologies.Web.ControlLibrary.Custom.BasePage.OnLoad(EventArgs e) +44
   Tec.TecAms.UI.Web.BasePage.OnLoad(EventArgs e) in C:\Projects\GT_Clients\TEC\TecAms\Code\Tec.TecAms\Tec.TecAms.UI.Web\BasePages\BasePage.cs:131
   Tec.TecAms.UI.Web.BasePages.BaseSecurePage.OnLoad(EventArgs e) in C:\Projects\GT_Clients\TEC\TecAms\Code\Tec.TecAms\Tec.TecAms.UI.Web\BasePages\BaseSecurePage.cs:144
   System.Web.UI.Control.LoadRecursive() +47
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1436


Version Information: Microsoft .NET Framework Version:2.0.50727.1433; ASP.NET Version:2.0.50727.1433 

Removing the subsequent outer join provides a successful test.

Thanks for your attention in this!

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 31-Oct-2008 10:00:28   

I saw a similar issue a couple of days ago, and indeed it looks like an error, though with that previous issue the query was really massive so debugging was really impossible...

With this smaller query, I'll see if I can reproduce it. Navigating nullable FK's (order.Customer.CustomerId for example if order.CustomerID is nullable) do result in left joins btw, which might help writing a workaround query.

Will look into it.

(btw, it has nothing to do with that other issue you mentioned, it's related to the fact that two groupjoins are placed after eachother, and the engine has to relate relation 2 to relation 1 and can't find a proper element. )

The joins in your query are of no use, as no filter is placed on the joined elements and as they're left joins, they won't filter any Assert rows. The second DefaultIfEmpty is of no use as well, because the FK is not nullable, so a LEFT join will result in the same set as an INNER join. That might help rewriting the query to a working one which doesn't fail with the current code.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 31-Oct-2008 11:08:45   

Reproduced on northwind. Looking into it.


[Test]
public void MultiDefaultIfEmptyInLineWithGroupJoins()
{
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        LinqMetaData metaData = new LinqMetaData(adapter);
        var q = from o in metaData.Order
                join e in metaData.Employee on o.EmployeeId equals e.EmployeeId into employees
                from emp in employees.DefaultIfEmpty()
                join et in metaData.EmployeeTerritory on emp.EmployeeId equals et.EmployeeId into empterritories
                from empterritory in empterritories.DefaultIfEmpty()
                select new
                {
                    o.CustomerId,
                    o.OrderId,
                    o.OrderDate
                };

        foreach(var v in q)
        {
        }
    }
}

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 31-Oct-2008 13:15:58   

I managed to fix this issue. I don't know if further, more complex defaultifempty trees will work but they're in almost all cases rewritable to more efficient queries.

(see attachment)

Frans Bouma | Lead developer LLBLGen Pro
Isz
User
Posts: 108
Joined: 26-Jan-2006
# Posted on: 31-Oct-2008 16:17:44   

Thanks, that seemed to have fixed it!!!

As for your comments, I will experiment with removing the subsequent DefaultIfEmpty joins and see if the output query is desirable, if it is, then thanks for the good tip! And thanks for the prompt response.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 31-Oct-2008 18:43:04   

Isz wrote:

Thanks, that seemed to have fixed it!!!

As for your comments, I will experiment with removing the subsequent DefaultIfEmpty joins and see if the output query is desirable, if it is, then thanks for the good tip! And thanks for the prompt response.

In general you should see querying as this process: - I want rows of type A - If I want a subset of all rows of A, I have to filter A by defining the filter in such a way that it represents the aspects (!) of the subset you want and have to specify the filter accordingly, starting from A. - Joins serve filters and selects. This means that if you need to filter on a related entity, you use joins (directly or indirectly), if you need data from a related entity, you join, but in all other cases joins are not needed (with the small addition: an inner join can be used as a filter)

This sounds as old news, but often this is overlooked simple_smile . Your query is a good example (no offence): you want Assets, but there's no real subset defined. This means that you don't need to apply any filter and therefore also no joins at all.

Frans Bouma | Lead developer LLBLGen Pro