Complex query with multiple joins and LINQ, how ?

Posts   
1  /  2
 
    
DrDelete
User
Posts: 39
Joined: 23-Apr-2009
# Posted on: 27-Apr-2009 14:41:44   

Developing in VS2008 SP1 and SqlServer 2008 SP1 and LLBLGenPro demo 2.6

I have this query in SQL:

SELECT [Contact].LastName, SUM(OrderRow.Amount * Product.Price)
FROM Contact
INNER JOIN [Order] ON [Contact].ContactId=[Order].ContactId 
INNER JOIN [OrderRow] ON [Order].OrderId=[OrderRow].OrderId
INNER JOIN [Product]ON OrderRow.ProductId=Product.ProductId 
GROUP BY [OrderRow].OrderId, [Contact].LastName
HAVING SUM(OrderRow.Amount * Product.Price) > 100

It should return all contacts where the total order amount is 100 or more.

I am struggling with the LINQ code to get this done.

Can you give me a suggestion ?

(see attached db schema for reference)

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 27-Apr-2009 16:02:05   
var q = from c in metaData.Contact
                     join o in metaData.Order on ......
                     join or in metaData.OrderRow on ......
                     join p in metaData.Product on ......
                     group new { c, o, or, p } by new { OrderId = or.OrderId, LastName = c.LastName } into g
                     Where g.Sum(x => x.or.Amount * x => x.p.Price) >100
                     select new
                        {
                            LastName = g.Key.LastName,
                            Total = g.Sum(x => x.or.Amount * x => x.p.Price)
                        };
DrDelete
User
Posts: 39
Joined: 23-Apr-2009
# Posted on: 27-Apr-2009 16:20:25   

Walaa wrote:

var q = from c in metaData.Contact
                     join o in metaData.Order on ......
                     join or in metaData.OrderRow on ......
                     join p in metaData.Product on ......
                     group new { c, o, or, p } by new { OrderId = or.OrderId, LastName = c.LastName } into g
                     Where g.Sum(x => x.or.Amount * x => x.p.Price) >100
                     select new
                        {
                            LastName = g.Key.LastName,
                            Total = g.Sum(x => x.or.Amount * x => x.p.Price)
                        };

I used your suggested query in code:

LinqMetaData lm = new LinqMetaData();

            var q = (from c in lm.Contact
                     join o in lm.Order on c.ContactId equals o.ContactId
                     join or in lm.OrderRow on o.ContactId equals or.OrderId
                     join p in lm.Product on or.ProductId equals p.ProductId
                     group new { c, o, or, p } by new { OrderId = or.OrderId, LastName = c.LastName } into g
                     where g.Sum(x => x.or.Amount * x.p.Price) >100
                     select new
                        {
                            LastName = g.Key.LastName,
                            Total = g.Sum(x => x.or.Amount * x.p.Price)
                        }).ToList();


            Assert.IsTrue(q.Count == 4, "4 contacts were expected");

But...notice that I altered the row:

Where g.Sum(x => x.or.Amount * x => x.p.Price) >100

into

where g.Sum(x => x.or.Amount * x.p.Price) >100

to get the build done.

Within NUnit I got an exception:

Logica.LLBLGenPro.Tests.LLBLGenProTest.SelectContactsWithOrderTotalGT100: SD.LLBLGen.Pro.ORMSupportClasses.ORMQueryExecutionException : An exception was caught during the execution of a retrieval query: The multi-part identifier "LPLA_7.Amount" could not be bound. The multi-part identifier "LPLA_7.Price" could not be bound. Invalid column name 'Amount'. Invalid column name 'Price'.. Check InnerException, QueryExecuted and Parameters of this exception to examine the cause of this exception. ----> System.Data.SqlClient.SqlException : The multi-part identifier "LPLA_7.Amount" could not be bound. The multi-part identifier "LPLA_7.Price" could not be bound. Invalid column name 'Amount'. Invalid column name 'Price'.

This is the query after verbose leveling the app.config SqlServerDQE trace switch (to 4):

Query: SELECT [LPA_L1].[LastName], [LPA_L1].[LPAV_1] AS [Total] FROM (SELECT DISTINCT [LPA_L3].[OrderId], [LPA_L3].[LastName], SUM([LPA_L3].[LPAV]) AS [LPAV], SUM([LPA_L3].[LPAV_1]) AS [LPAV_1] FROM (SELECT DISTINCT [LPA_L4].[OrderId], [LPA_L4].[LastName], [LPA_L4].[LPAV_], (CONVERT(DECIMAL, [LPA_L4].[Amount])) * [LPA_L4].[Price] AS [LPAV_1] FROM (SELECT DISTINCT [LPA_L7].[OrderId], [LPA_L5].[LastName], (CONVERT(DECIMAL, [LPLA_7].[Amount])) * [LPLA_7].[Price] AS [LPAV] FROM ((( [ORM].[dbo].[Contact] [LPA_L5] INNER JOIN [ORM].[dbo].[Order] [LPA_L6] ON [LPA_L5].[ContactId] = [LPA_L6].[ContactId]) INNER JOIN [ORM].[dbo].[OrderRow] [LPA_L7] ON [LPA_L6].[ContactId] = [LPA_L7].[OrderId]) INNER JOIN [ORM].[dbo].[Product] [LPA_L8] ON [LPA_L7].[ProductId] = [LPA_L8].[ProductId])) [LPA_L4]) [LPA_L3] GROUP BY [LPA_L3].[OrderId], [LPA_L3].[LastName]) [LPA_L1] WHERE ( ( ( ( [LPA_L1].[LPAV] > @LPAV_1)))) Parameter: @LPAV_1 : Decimal. Length: 0. Precision: 0. Scale: 0. Direction: Input. Value: 100.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 27-Apr-2009 17:55:44   

What's the runtime lib build number of the demo install you're using? (see: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=7717)

We've made some linq related fixes recently, so I can check with the buildnr if you have a build which should work OK. I'll then try to reproduce it.

Frans Bouma | Lead developer LLBLGen Pro
DrDelete
User
Posts: 39
Joined: 23-Apr-2009
# Posted on: 28-Apr-2009 08:05:40   

Otis wrote:

What's the runtime lib build number of the demo install you're using? (see: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=7717)

We've made some linq related fixes recently, so I can check with the buildnr if you have a build which should work OK. I'll then try to reproduce it.

Runtime libraries release date 6-april-2009

SD.LLBLGen.Pro.LinqSupportClasses.Net35.dll : assemblyversion = 2.6.0.0, fileversion = 2.6.9.331

SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll : assemblyversion = 2.6.0.0, fileversion = 2.6.9.327

SD.LLBLGen.Pro.DQE.SqlServer.NET20.dll : assemblyversion = 2.6.0.0, fileversion = 2.6.8.1114

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 28-Apr-2009 10:45:11   

Reproduced.

Looking into it. The query produced isn't as simple as your original SQL, due to the fact that there's no HAVING support in linq, (i.e. where clauses on a groupby expression are not converted to a HAVING clause, even though llblgen pro supports it, but to a normal where clause).


SELECT  [LPA_L1].[CompanyName], [LPA_L1].[LPAV_1] AS [Total] 
FROM 
(
    SELECT  DISTINCT [LPA_L3].[OrderId], [LPA_L3].[CompanyName], SUM([LPA_L3].[LPAV_]) AS [LPAV_], SUM([LPA_L3].[LPAV_1]) AS [LPAV_1]
    FROM
    (
        SELECT  DISTINCT [LPA_L4].[OrderId], [LPA_L4].[CompanyName], [LPA_L4].[LPAV_], 
                (CONVERT(DECIMAL, [LPA_L4].[Quantity])) * [LPA_L4].[UnitPrice] AS [LPAV_1] 
        FROM 
        (
            SELECT  DISTINCT [LPA_L7].[OrderID] AS [OrderId], [LPA_L5].[CompanyName], 
                    (CONVERT(DECIMAL, [LPLA_7].[Quantity])) * [LPLA_7].[UnitPrice] AS [LPAV_] 
            FROM ((( [Northwind].[dbo].[Customers] [LPA_L5]  INNER JOIN [Northwind].[dbo].[Orders] [LPA_L6]  
                    ON  [LPA_L5].[CustomerID] = [LPA_L6].[CustomerID]) 
                INNER JOIN [Northwind].[dbo].[Order Details] [LPA_L7]  
                    ON  [LPA_L6].[OrderID] = [LPA_L7].[OrderID]) 
                INNER JOIN [Northwind].[dbo].[Products] [LPA_L8]  
                    ON  [LPA_L7].[ProductID] = [LPA_L8].[ProductID])
        ) [LPA_L4]
    ) [LPA_L3] 
    GROUP BY [LPA_L3].[OrderId], [LPA_L3].[CompanyName]
) [LPA_L1] 
WHERE ( ( ( ( ( [LPA_L1].[LPAV_] > @LPAV_1)))))

(I ran it on northwind with customer, order, orderdetails, product and quantity * unitprice causes the CONVERT expression to be emitted. The alias error is inside this CONVERT clause, which is a dbfunction call. This should be converted properly however the engine doesn't pick it up.

(edit) the alias is not present in the join (LPLA* are converted to real aliases LPA* and it can't do that as it refers to a target which isn't there. Likely a bug in the alias rewriter.

It will do the calculation twice, even though it in theory doesn't have to. Using Let also doesn't cut it, as that gives a problem with a nested query not being convertable to an entity based query.

Frans Bouma | Lead developer LLBLGen Pro
DrDelete
User
Posts: 39
Joined: 23-Apr-2009
# Posted on: 28-Apr-2009 10:51:55   

Otis wrote:

Reproduced.

....

hi Frans,

Is there a workaround, not using LINQ, because I am performing a POC on several ORM tools and want to make progress.

I prefer to use LINQ where possible, if LINQ is not possible or when a bug arises I want to have a fallback scenario....

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 28-Apr-2009 11:17:21   

DrDelete wrote:

Otis wrote:

Reproduced.

....

hi Frans,

Is there a workaround, not using LINQ, because I am performing a POC on several ORM tools and want to make progress.

I prefer to use LINQ where possible, if LINQ is not possible or when a bug arises I want to have a fallback scenario....

Yes there's a fallback to our native query system. Our linq provider is written on top of that, so you can always fall back to our native system. I've given the linq query I used to reproduce your problem and the fall back code and the query generated by the fallback code.

Fails:


[Test]
public void GroupByOnJoinedEntitiesWithWhereOnAggregate()
{
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        LinqMetaData metaData = new LinqMetaData(adapter);
        var q = from c in metaData.Customer
                join o in metaData.Order on c.CustomerId equals o.CustomerId
                join od in metaData.OrderDetail on o.OrderId equals od.OrderId
                join p in metaData.Product on od.ProductId equals p.ProductId
                group new { c, o, od, p } by new { od.OrderId, c.CompanyName } into g
                where g.Sum(x => x.od.Quantity * x.p.UnitPrice.Value) > 100.0M
                select new { CompanyName = g.Key.CompanyName, Total = g.Sum(x => x.od.Quantity * x.p.UnitPrice.Value) };

        foreach(var v in q)
        {
            
        }
    }
}

Works:


[Test]
public void GroupByOnJoinedEntitiesWithWhereOnAggregateRegularCode()
{
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        ResultsetFields fields = new ResultsetFields(2);
        RelationPredicateBucket filter = new RelationPredicateBucket();
        filter.Relations.Add(CustomerEntity.Relations.OrderEntityUsingCustomerId);
        filter.Relations.Add(OrderEntity.Relations.OrderDetailEntityUsingOrderId);
        filter.Relations.Add(OrderDetailEntity.Relations.ProductEntityUsingProductId);
        fields.DefineField(CustomerFields.CompanyName, 0);
        fields.DefineField(new EntityField2("Total", (OrderDetailFields.Quantity * ProductFields.UnitPrice), AggregateFunction.Sum, typeof(decimal)), 1);
        GroupByCollection groupBy = new GroupByCollection();
        groupBy.Add(OrderDetailFields.OrderId);
        groupBy.Add(fields[0]);
        groupBy.HavingClause = new PredicateExpression(new FieldCompareValuePredicate(fields[1], null, ComparisonOperator.GreaterThan, 100.0M));
        DataTable table = new DataTable();
        adapter.FetchTypedList(fields, table, filter, 0, null, true,groupBy);
    }
}

I fetch it into a datatable, as it's an untyped dynamic list. I could project it to a custom class if you want to, using projections (see the manual for further details on fetching projections).

query generated:


SELECT  [Northwind].[dbo].[Customers].[CompanyName], 
        SUM([Northwind].[dbo].[Order Details].[Quantity] * [Northwind].[dbo].[Products].[UnitPrice]) AS [Total] 
FROM ((( [Northwind].[dbo].[Customers]  INNER JOIN [Northwind].[dbo].[Orders]  
            ON  [Northwind].[dbo].[Customers].[CustomerID]=[Northwind].[dbo].[Orders].[CustomerID]) 
        INNER JOIN [Northwind].[dbo].[Order Details]  
            ON  [Northwind].[dbo].[Orders].[OrderID]=[Northwind].[dbo].[Order Details].[OrderID]) 
        INNER JOIN [Northwind].[dbo].[Products]  
            ON  [Northwind].[dbo].[Products].[ProductID]=[Northwind].[dbo].[Order Details].[ProductID]) 
GROUP BY [Northwind].[dbo].[Order Details].[OrderID], [Northwind].[dbo].[Customers].[CompanyName] 
HAVING ( SUM([Northwind].[dbo].[Order Details].[Quantity] * [Northwind].[dbo].[Products].[UnitPrice]) > @Total1)

I do want the linq query to work as well, so I'll try to fix that today for you too.

Frans Bouma | Lead developer LLBLGen Pro
DrDelete
User
Posts: 39
Joined: 23-Apr-2009
# Posted on: 28-Apr-2009 11:22:19   

Otis wrote:

DrDelete wrote:

Otis wrote:

Reproduced.

....

This working sample is with using the Adapter template group, I am using self servicing, could this be posted also ?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 28-Apr-2009 11:39:21   

DrDelete wrote:

Otis wrote:

DrDelete wrote:

Otis wrote:

Reproduced.

....

This working sample is with using the Adapter template group, I am using self servicing, could this be posted also ?


ResultsetFields fields = new ResultsetFields(2);
RelationCollection relations = new RelationCollection();
relations.Add(CustomerEntity.Relations.OrderEntityUsingCustomerId);
relations.Add(OrderEntity.Relations.OrderDetailEntityUsingOrderId);
relations.Add(OrderDetailEntity.Relations.ProductEntityUsingProductId);
fields.DefineField(CustomerFields.CompanyName, 0);
fields.DefineField(new EntityField("Total", (OrderDetailFields.Quantity * ProductFields.UnitPrice), AggregateFunction.Sum, typeof(decimal)), 1);
GroupByCollection groupBy = new GroupByCollection();
groupBy.Add(OrderDetailFields.OrderId);
groupBy.Add(fields[0]);
groupBy.HavingClause = new PredicateExpression(new FieldCompareValuePredicate(fields[1], ComparisonOperator.GreaterThan, 100.0M));
DataTable table = new DataTable();
TypedListDAO dao = new TypedListDAO();
dao.GetMultiAsDataTable(fields, table, 0, null, null, relations, true, groupBy, null, 0, 0);

Frans Bouma | Lead developer LLBLGen Pro
DrDelete
User
Posts: 39
Joined: 23-Apr-2009
# Posted on: 28-Apr-2009 14:14:31   

Otis wrote:

DrDelete wrote:

Otis wrote:

DrDelete wrote:

Otis wrote:

Reproduced.

....

This working sample is with using the Adapter template group, I am using self servicing, could this be posted also ?


ResultsetFields fields = new ResultsetFields(2);
RelationCollection relations = new RelationCollection();
relations.Add(CustomerEntity.Relations.OrderEntityUsingCustomerId);
relations.Add(OrderEntity.Relations.OrderDetailEntityUsingOrderId);
relations.Add(OrderDetailEntity.Relations.ProductEntityUsingProductId);
fields.DefineField(CustomerFields.CompanyName, 0);
fields.DefineField(new EntityField("Total", (OrderDetailFields.Quantity * ProductFields.UnitPrice), AggregateFunction.Sum, typeof(decimal)), 1);
GroupByCollection groupBy = new GroupByCollection();
groupBy.Add(OrderDetailFields.OrderId);
groupBy.Add(fields[0]);
groupBy.HavingClause = new PredicateExpression(new FieldCompareValuePredicate(fields[1], ComparisonOperator.GreaterThan, 100.0M));
DataTable table = new DataTable();
TypedListDAO dao = new TypedListDAO();
dao.GetMultiAsDataTable(fields, table, 0, null, null, relations, true, groupBy, null, 0, 0);

Thanks, that worked. If the bug with LINQ provider is solved, can I be informed so I can rewrite it again with LINQ ?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 28-Apr-2009 15:18:56   

DrDelete wrote:

Otis wrote:

DrDelete wrote:

Otis wrote:

DrDelete wrote:

Otis wrote:

Reproduced.

....

This working sample is with using the Adapter template group, I am using self servicing, could this be posted also ?


ResultsetFields fields = new ResultsetFields(2);
RelationCollection relations = new RelationCollection();
relations.Add(CustomerEntity.Relations.OrderEntityUsingCustomerId);
relations.Add(OrderEntity.Relations.OrderDetailEntityUsingOrderId);
relations.Add(OrderDetailEntity.Relations.ProductEntityUsingProductId);
fields.DefineField(CustomerFields.CompanyName, 0);
fields.DefineField(new EntityField("Total", (OrderDetailFields.Quantity * ProductFields.UnitPrice), AggregateFunction.Sum, typeof(decimal)), 1);
GroupByCollection groupBy = new GroupByCollection();
groupBy.Add(OrderDetailFields.OrderId);
groupBy.Add(fields[0]);
groupBy.HavingClause = new PredicateExpression(new FieldCompareValuePredicate(fields[1], ComparisonOperator.GreaterThan, 100.0M));
DataTable table = new DataTable();
TypedListDAO dao = new TypedListDAO();
dao.GetMultiAsDataTable(fields, table, 0, null, null, relations, true, groupBy, null, 0, 0);

Thanks, that worked. If the bug with LINQ provider is solved, can I be informed so I can rewrite it again with LINQ ?

Yes. I've to run the tests again to see if the fix doesn't break anything, and then I'll add a new build to this post.

Frans Bouma | Lead developer LLBLGen Pro
DrDelete
User
Posts: 39
Joined: 23-Apr-2009
# Posted on: 28-Apr-2009 15:21:05   

Otis wrote:

DrDelete wrote:

Otis wrote:

DrDelete wrote:

Otis wrote:

DrDelete wrote:

Otis wrote:

Reproduced.

....

This working sample is with using the Adapter template group, I am using self servicing, could this be posted also ?


ResultsetFields fields = new ResultsetFields(2);
RelationCollection relations = new RelationCollection();
relations.Add(CustomerEntity.Relations.OrderEntityUsingCustomerId);
relations.Add(OrderEntity.Relations.OrderDetailEntityUsingOrderId);
relations.Add(OrderDetailEntity.Relations.ProductEntityUsingProductId);
fields.DefineField(CustomerFields.CompanyName, 0);
fields.DefineField(new EntityField("Total", (OrderDetailFields.Quantity * ProductFields.UnitPrice), AggregateFunction.Sum, typeof(decimal)), 1);
GroupByCollection groupBy = new GroupByCollection();
groupBy.Add(OrderDetailFields.OrderId);
groupBy.Add(fields[0]);
groupBy.HavingClause = new PredicateExpression(new FieldCompareValuePredicate(fields[1], ComparisonOperator.GreaterThan, 100.0M));
DataTable table = new DataTable();
TypedListDAO dao = new TypedListDAO();
dao.GetMultiAsDataTable(fields, table, 0, null, null, relations, true, groupBy, null, 0, 0);

Thanks, that worked. If the bug with LINQ provider is solved, can I be informed so I can rewrite it again with LINQ ?

Yes. I've to run the tests again to see if the fix doesn't break anything, and then I'll add a new build to this post.

How will this affect my 30 days trail period?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 28-Apr-2009 15:23:03   

DrDelete wrote:

Otis wrote:

Yes. I've to run the tests again to see if the fix doesn't break anything, and then I'll add a new build to this post.

How will this affect my 30 days trail period?

That's for the designer, your testcode will keep on running, the designer will stop functioning after the 30 days are over. Please see the attachment of the previous post of mine (paperclip) where you'll find the latest builds of the ORMSupportclasses and linqprovider which should fix the issue you reported. They're debug builds, but should work fine now.

Frans Bouma | Lead developer LLBLGen Pro
DrDelete
User
Posts: 39
Joined: 23-Apr-2009
# Posted on: 11-May-2009 08:46:44   

Otis wrote:

DrDelete wrote:

Otis wrote:

DrDelete wrote:

Otis wrote:

DrDelete wrote:

Otis wrote:

Reproduced.

....

This working sample is with using the Adapter template group, I am using self servicing, could this be posted also ?


ResultsetFields fields = new ResultsetFields(2);
RelationCollection relations = new RelationCollection();
relations.Add(CustomerEntity.Relations.OrderEntityUsingCustomerId);
relations.Add(OrderEntity.Relations.OrderDetailEntityUsingOrderId);
relations.Add(OrderDetailEntity.Relations.ProductEntityUsingProductId);
fields.DefineField(CustomerFields.CompanyName, 0);
fields.DefineField(new EntityField("Total", (OrderDetailFields.Quantity * ProductFields.UnitPrice), AggregateFunction.Sum, typeof(decimal)), 1);
GroupByCollection groupBy = new GroupByCollection();
groupBy.Add(OrderDetailFields.OrderId);
groupBy.Add(fields[0]);
groupBy.HavingClause = new PredicateExpression(new FieldCompareValuePredicate(fields[1], ComparisonOperator.GreaterThan, 100.0M));
DataTable table = new DataTable();
TypedListDAO dao = new TypedListDAO();
dao.GetMultiAsDataTable(fields, table, 0, null, null, relations, true, groupBy, null, 0, 0);

Thanks, that worked. If the bug with LINQ provider is solved, can I be informed so I can rewrite it again with LINQ ?

Yes. I've to run the tests again to see if the fix doesn't break anything, and then I'll add a new build to this post.

Sorry for the late response here, but I just replaced the assemblies and re-introduced the LINQ join query like this:

LinqMetaData lm = new LinqMetaData();

            var q = (from c in lm.Contact
                     join o in lm.Order on c.ContactId equals o.ContactId
                     join or in lm.OrderRow on o.ContactId equals or.OrderId
                     join p in lm.Product on or.ProductId equals p.ProductId
                     group new { c, o, or, p } by new { OrderId = or.OrderId, LastName = c.LastName } into g
                     where g.Sum(x => x.or.Amount * x.p.Price) > 100
                     select new
                        {
                            LastName = g.Key.LastName,
                            Total = g.Sum(x => x.or.Amount * x.p.Price)
                        }).ToList();

and I got this exception:

Logica.LLBLGenPro.Tests.LLBLGenProTest.SelectCustomersWithOrderTotalGT100Test: SD.LLBLGen.Pro.ORMSupportClasses.ORMQueryExecutionException : An exception was caught during the execution of a retrieval query: Invalid column name 'Amount'. Invalid column name 'Price'.. Check InnerException, QueryExecuted and Parameters of this exception to examine the cause of this exception. ----> System.Data.SqlClient.SqlException : Invalid column name 'Amount'. Invalid column name 'Price'.

The SQL log:

Generated Sql query: 
    Query: SELECT [LPA_L1].[LastName], [LPA_L1].[LPAV_1] AS [Total] FROM (SELECT DISTINCT [LPA_L3].[OrderId], [LPA_L3].[LastName], SUM([LPA_L3].[LPAV_]) AS [LPAV_], SUM([LPA_L3].[LPAV_1]) AS [LPAV_1] FROM (SELECT DISTINCT [LPA_L4].[OrderId], [LPA_L4].[LastName], [LPA_L4].[LPAV_], (CONVERT(DECIMAL, [LPA_L4].[Amount])) * [LPA_L4].[Price] AS [LPAV_1] FROM (SELECT DISTINCT [LPA_L7].[OrderId], [LPA_L5].[LastName], (CONVERT(DECIMAL, [LPA_L7].[Amount])) * [LPA_L8].[Price] AS [LPAV_] FROM ((( [ORM].[dbo].[Contact] [LPA_L5]  INNER JOIN [ORM].[dbo].[Order] [LPA_L6]  ON  [LPA_L5].[ContactId] = [LPA_L6].[ContactId]) INNER JOIN [ORM].[dbo].[OrderRow] [LPA_L7]  ON  [LPA_L6].[ContactId] = [LPA_L7].[OrderId]) INNER JOIN [ORM].[dbo].[Product] [LPA_L8]  ON  [LPA_L7].[ProductId] = [LPA_L8].[ProductId])) [LPA_L4]) [LPA_L3] GROUP BY [LPA_L3].[OrderId], [LPA_L3].[LastName]) [LPA_L1] WHERE ( ( ( ( [LPA_L1].[LPAV_] > @LPAV_1))))
    Parameter: @LPAV_1 : Decimal. Length: 0. Precision: 0. Scale: 0. Direction: Input. Value: 100.
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 11-May-2009 09:36:28   

Though it's the exact same query you posted earlier which I used to reproduce it and also to test to see if the fix worked:


var q = from c in metaData.Customer
        join o in metaData.Order on c.CustomerId equals o.CustomerId
        join od in metaData.OrderDetail on o.OrderId equals od.OrderId
        join p in metaData.Product on od.ProductId equals p.ProductId
        group new { c, o, od, p } by new { od.OrderId, c.CompanyName } into g
        where g.Sum(x => x.od.Quantity * x.p.UnitPrice.Value) > 100.0M
        select new { CompanyName = g.Key.CompanyName, Total = g.Sum(x => x.od.Quantity * x.p.UnitPrice.Value) };

The only thing which might be different here is that I compared it to a decimal value, not an integer, and perhaps I don't get conversions to decimal where you get them. I'll check where the SQL queries differ.

(edit) uhoh flushed . I simplified the unittest to get to the smallest query possible to reproduce the problem (as it's otherwise a major headache to debug this as the expression tree is incredibly big and complex otherwise) and this simplification did work, but your query indeed doesn't. My fault, sorry for that. I'll look into fixing this. I've an idea where this issue is located, so I hope to have a fix soon. A linq provider is unfortunately so massively complex that these kind of issues are hard to avoid, as it's hard to predict if some code is really OK based on analysis. (unittests won't help here, there's always a situation which might make things fail and which is different from the situations tested).

Frans Bouma | Lead developer LLBLGen Pro
DrDelete
User
Posts: 39
Joined: 23-Apr-2009
# Posted on: 11-May-2009 10:06:36   

Otis wrote:

Though it's the exact same query you posted earlier which I used to reproduce it and also to test to see if the fix worked:


var q = from c in metaData.Customer
        join o in metaData.Order on c.CustomerId equals o.CustomerId
        join od in metaData.OrderDetail on o.OrderId equals od.OrderId
        join p in metaData.Product on od.ProductId equals p.ProductId
        group new { c, o, od, p } by new { od.OrderId, c.CompanyName } into g
        where g.Sum(x => x.od.Quantity * x.p.UnitPrice.Value) > 100.0M
        select new { CompanyName = g.Key.CompanyName, Total = g.Sum(x => x.od.Quantity * x.p.UnitPrice.Value) };

The only thing which might be different here is that I compared it to a decimal value, not an integer, and perhaps I don't get conversions to decimal where you get them. I'll check where the SQL queries differ.

(edit) uhoh flushed . I simplified the unittest to get to the smallest query possible to reproduce the problem (as it's otherwise a major headache to debug this as the expression tree is incredibly big and complex otherwise) and this simplification did work, but your query indeed doesn't. My fault, sorry for that. I'll look into fixing this. I've an idea where this issue is located, so I hope to have a fix soon. A linq provider is unfortunately so massively complex that these kind of issues are hard to avoid, as it's hard to predict if some code is really OK based on analysis. (unittests won't help here, there's always a situation which might make things fail and which is different from the situations tested).

Don't worry wink , LLBLGenPro is not the only one having difficulties with LINQ regarding complex queries

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 11-May-2009 11:08:30   

DrDelete wrote:

Otis wrote:

Though it's the exact same query you posted earlier which I used to reproduce it and also to test to see if the fix worked:


var q = from c in metaData.Customer
        join o in metaData.Order on c.CustomerId equals o.CustomerId
        join od in metaData.OrderDetail on o.OrderId equals od.OrderId
        join p in metaData.Product on od.ProductId equals p.ProductId
        group new { c, o, od, p } by new { od.OrderId, c.CompanyName } into g
        where g.Sum(x => x.od.Quantity * x.p.UnitPrice.Value) > 100.0M
        select new { CompanyName = g.Key.CompanyName, Total = g.Sum(x => x.od.Quantity * x.p.UnitPrice.Value) };

The only thing which might be different here is that I compared it to a decimal value, not an integer, and perhaps I don't get conversions to decimal where you get them. I'll check where the SQL queries differ.

(edit) uhoh flushed . I simplified the unittest to get to the smallest query possible to reproduce the problem (as it's otherwise a major headache to debug this as the expression tree is incredibly big and complex otherwise) and this simplification did work, but your query indeed doesn't. My fault, sorry for that. I'll look into fixing this. I've an idea where this issue is located, so I hope to have a fix soon. A linq provider is unfortunately so massively complex that these kind of issues are hard to avoid, as it's hard to predict if some code is really OK based on analysis. (unittests won't help here, there's always a situation which might make things fail and which is different from the situations tested).

Don't worry wink , LLBLGenPro is not the only one having difficulties with LINQ regarding complex queries

Well, some competitors have released 'linq' providers which can't do much more than simple queries, and it's no surprise they'll fail in more complex queries. They also didn't spend that much time on them, considering the fact the code base of these providers is smaller. We did spend a lot of time on the linq provider to get a provider which can eat any query. If ours fails too, we apparently have spent time for nothing wink . We know to get the 100% query coverage is almost impossible outside MS, but still we do want to get as close to 100% as possible. As you've seen, we do have workarounds in the form of our native query api, but linq queries should work too.

I'm pretty close to where the problem is. It's related to folding of the groupby source into a derived table when multiple aggregates are used: the fields for the second aggregate's expression aren't placed in the projection of the derived table. For 1 level this works, but for all extra levels this fails (and your complex query is a multi level one). The solution is to add the fields in the expression to aggregate to all projections in the derived tables already folded. (as there may be 3 or more aggregates). It doesn't fail always as we do have multi-aggregate groupby tests which work (also aggregates on aggregates), though these aggregates are on fields only, not on expressions.

Stay tuned.

Ps: which frameworks failed as well? EF? On linq to sql I can run the query without a problem

Frans Bouma | Lead developer LLBLGen Pro
DrDelete
User
Posts: 39
Joined: 23-Apr-2009
# Posted on: 11-May-2009 11:14:23   

Otis wrote:

DrDelete wrote:

Otis wrote:

Though it's the exact same query you posted earlier which I used to reproduce it and also to test to see if the fix worked:


var q = from c in metaData.Customer
        join o in metaData.Order on c.CustomerId equals o.CustomerId
        join od in metaData.OrderDetail on o.OrderId equals od.OrderId
        join p in metaData.Product on od.ProductId equals p.ProductId
        group new { c, o, od, p } by new { od.OrderId, c.CompanyName } into g
        where g.Sum(x => x.od.Quantity * x.p.UnitPrice.Value) > 100.0M
        select new { CompanyName = g.Key.CompanyName, Total = g.Sum(x => x.od.Quantity * x.p.UnitPrice.Value) };

The only thing which might be different here is that I compared it to a decimal value, not an integer, and perhaps I don't get conversions to decimal where you get them. I'll check where the SQL queries differ.

(edit) uhoh flushed . I simplified the unittest to get to the smallest query possible to reproduce the problem (as it's otherwise a major headache to debug this as the expression tree is incredibly big and complex otherwise) and this simplification did work, but your query indeed doesn't. My fault, sorry for that. I'll look into fixing this. I've an idea where this issue is located, so I hope to have a fix soon. A linq provider is unfortunately so massively complex that these kind of issues are hard to avoid, as it's hard to predict if some code is really OK based on analysis. (unittests won't help here, there's always a situation which might make things fail and which is different from the situations tested).

Don't worry wink , LLBLGenPro is not the only one having difficulties with LINQ regarding complex queries

Well, some competitors have released 'linq' providers which can't do much more than simple queries, and it's no surprise they'll fail in more complex queries. They also didn't spend that much time on them, considering the fact the code base of these providers is smaller. We did spend a lot of time on the linq provider to get a provider which can eat any query. If ours fails too, we apparently have spent time for nothing wink . We know to get the 100% query coverage is almost impossible outside MS, but still we do want to get as close to 100% as possible. As you've seen, we do have workarounds in the form of our native query api, but linq queries should work too.

I'm pretty close to where the problem is. It's related to folding of the groupby source into a derived table when multiple aggregates are used: the fields for the second aggregate's expression aren't placed in the projection of the derived table. For 1 level this works, but for all extra levels this fails (and your complex query is a multi level one). The solution is to add the fields in the expression to aggregate to all projections in the derived tables already folded. (as there may be 3 or more aggregates). It doesn't fail always as we do have multi-aggregate groupby tests which work (also aggregates on aggregates), though these aggregates are on fields only, not on expressions.

Stay tuned.

Ps: which frameworks failed as well? EF? On linq to sql I can run the query without a problem

NHibernate failed also and I had to use HQL for this to get it work, tommorow ADO.EF will be tested.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 11-May-2009 11:52:41   

DrDelete wrote:

Otis wrote:

DrDelete wrote:

Otis wrote:

Though it's the exact same query you posted earlier which I used to reproduce it and also to test to see if the fix worked:


var q = from c in metaData.Customer
        join o in metaData.Order on c.CustomerId equals o.CustomerId
        join od in metaData.OrderDetail on o.OrderId equals od.OrderId
        join p in metaData.Product on od.ProductId equals p.ProductId
        group new { c, o, od, p } by new { od.OrderId, c.CompanyName } into g
        where g.Sum(x => x.od.Quantity * x.p.UnitPrice.Value) > 100.0M
        select new { CompanyName = g.Key.CompanyName, Total = g.Sum(x => x.od.Quantity * x.p.UnitPrice.Value) };

The only thing which might be different here is that I compared it to a decimal value, not an integer, and perhaps I don't get conversions to decimal where you get them. I'll check where the SQL queries differ.

(edit) uhoh flushed . I simplified the unittest to get to the smallest query possible to reproduce the problem (as it's otherwise a major headache to debug this as the expression tree is incredibly big and complex otherwise) and this simplification did work, but your query indeed doesn't. My fault, sorry for that. I'll look into fixing this. I've an idea where this issue is located, so I hope to have a fix soon. A linq provider is unfortunately so massively complex that these kind of issues are hard to avoid, as it's hard to predict if some code is really OK based on analysis. (unittests won't help here, there's always a situation which might make things fail and which is different from the situations tested).

Don't worry wink , LLBLGenPro is not the only one having difficulties with LINQ regarding complex queries

Well, some competitors have released 'linq' providers which can't do much more than simple queries, and it's no surprise they'll fail in more complex queries. They also didn't spend that much time on them, considering the fact the code base of these providers is smaller. We did spend a lot of time on the linq provider to get a provider which can eat any query. If ours fails too, we apparently have spent time for nothing wink . We know to get the 100% query coverage is almost impossible outside MS, but still we do want to get as close to 100% as possible. As you've seen, we do have workarounds in the form of our native query api, but linq queries should work too.

I'm pretty close to where the problem is. It's related to folding of the groupby source into a derived table when multiple aggregates are used: the fields for the second aggregate's expression aren't placed in the projection of the derived table. For 1 level this works, but for all extra levels this fails (and your complex query is a multi level one). The solution is to add the fields in the expression to aggregate to all projections in the derived tables already folded. (as there may be 3 or more aggregates). It doesn't fail always as we do have multi-aggregate groupby tests which work (also aggregates on aggregates), though these aggregates are on fields only, not on expressions.

Stay tuned.

Ps: which frameworks failed as well? EF? On linq to sql I can run the query without a problem

NHibernate failed also and I had to use HQL for this to get it work, tommorow ADO.EF will be tested.

NHibernate doesn't have a proper linq provider which can do even joins (especially join into (groupjoin)) so no surprise there wink .

I've fixed it for your query setup (2-layer folding). Multiple layer folding has some small error (multiple times the same fields added to the derived tables) but that should be minor. Almost there! simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 11-May-2009 12:48:17   

Fixed it (also for multiple aggregates in the where for example). See attached dlls. I had to make a small adjustment to the ormsupportclasses as well (inside a class) as that was better than having the code outside the class. So be sure you use both dlls.

Frans Bouma | Lead developer LLBLGen Pro
DrDelete
User
Posts: 39
Joined: 23-Apr-2009
# Posted on: 11-May-2009 12:59:58   

Otis wrote:

Fixed it (also for multiple aggregates in the where for example). See attached dlls. I had to make a small adjustment to the ormsupportclasses as well (inside a class) as that was better than having the code outside the class. So be sure you use both dlls.

I tested it again and the exception is gone, I get a result now. But.... the result is not correct. The LINQ query results 2 rows where my SQL query returns 4.

I think duplicate customer entries are removed, which shouldn't be... (see attachment picture)

Attachments
Filename File size Added on Approval
query_results.bmp 22,698 11-May-2009 13:01.16 Approved
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 11-May-2009 14:12:11   

DrDelete wrote:

Otis wrote:

Fixed it (also for multiple aggregates in the where for example). See attached dlls. I had to make a small adjustment to the ormsupportclasses as well (inside a class) as that was better than having the code outside the class. So be sure you use both dlls.

I tested it again and the exception is gone, I get a result now. But.... the result is not correct. The LINQ query results 2 rows where my SQL query returns 4.

I think duplicate customer entries are removed, which shouldn't be... (see attachment picture)

Hmm... with 'sql query' you mean the hand-written sql query you used to build the linq query from?

I'll see if I can find where this difference is coming from. (could be the way things are constructed in Linq. Not everything pans out to be ok. For example, the query ran in linq to sql gives the exact same SQL query as LLBLGen Pro does (give or take some tiny differences in aliases and the like) and the results are also the same. I also have multiple rows in my query for the same customer:


SELECT [LPA_L1].[CompanyName], [LPA_L1].[LPAV_1] AS [Total] 
FROM (
    SELECT [LPA_L3].[OrderId], [LPA_L3].[CompanyName], SUM([LPA_L3].[LPAV_]) AS [LPAV_], 
            SUM([LPA_L3].[LPAV_1]) AS [LPAV_1] 
    FROM (
        SELECT [LPA_L4].[OrderId], [LPA_L4].[CompanyName], [LPA_L4].[LPAV_], [LPA_L4].[UnitPrice], 
            [LPA_L4].[Quantity], [LPA_L4].[UnitPrice] * (CONVERT(DECIMAL, [LPA_L4].[Quantity])) AS [LPAV_1] 
        FROM (
            SELECT [LPA_L7].[OrderID] AS [OrderId], [LPA_L5].[CompanyName], 
                (CONVERT(DECIMAL, [LPA_L7].[Quantity])) * [LPA_L8].[UnitPrice] AS [LPAV_], [LPA_L7].[UnitPrice], 
                [LPA_L7].[Quantity] 
            FROM ((( [Northwind].[dbo].[Customers] [LPA_L5]  INNER JOIN [Northwind].[dbo].[Orders] [LPA_L6]  
                    ON  [LPA_L5].[CustomerID] = [LPA_L6].[CustomerID]) 
                INNER JOIN [Northwind].[dbo].[Order Details] [LPA_L7]  ON  [LPA_L6].[OrderID] = [LPA_L7].[OrderID]) 
                INNER JOIN [Northwind].[dbo].[Products] [LPA_L8]  ON  [LPA_L7].[ProductID] = [LPA_L8].[ProductID])
        ) [LPA_L4]
    ) [LPA_L3] 
    GROUP BY [LPA_L3].[OrderId], [LPA_L3].[CompanyName]
) [LPA_L1] 
WHERE ( ( ( ( ( [LPA_L1].[LPAV_] > 100)))))   -- replaced parameter with constant

Keep in mind that your original query is a different query in SQL, so I'll reconstruct your initial query in SQL to query the same data as the linq query did and see if things are indeed different.

Frans Bouma | Lead developer LLBLGen Pro
DrDelete
User
Posts: 39
Joined: 23-Apr-2009
# Posted on: 11-May-2009 14:15:54   

Otis wrote:

DrDelete wrote:

Otis wrote:

Fixed it (also for multiple aggregates in the where for example). See attached dlls. I had to make a small adjustment to the ormsupportclasses as well (inside a class) as that was better than having the code outside the class. So be sure you use both dlls.

I tested it again and the exception is gone, I get a result now. But.... the result is not correct. The LINQ query results 2 rows where my SQL query returns 4.

I think duplicate customer entries are removed, which shouldn't be... (see attachment picture)

Hmm... with 'sql query' you mean the hand-written sql query you used to build the linq query from?

I'll see if I can find where this difference is coming from.

by the way: in my first post I gave the original SQL query ( SQL Server 2008 ).

I think it aggregates also on the customer lastname, which is not correct. It must list all customers with order total of > 100 where a customer can be in the list more than once.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 11-May-2009 14:25:32   

When I convert your initial query to SQL to be equal to the query that's been generated:


SELECT c.CompanyName, SUM(od.Quantity * p.UnitPrice) AS Total
FROM Customers c INNER JOIN Orders o ON c.CustomerID = o.CustomerID
     INNER JOIN [Order Details] od ON o.OrderID = od.OrderID
     INNER JOIN Products p ON od.ProductID = p.ProductID
GROUP BY od.OrderID, c.CompanyName
HAVING SUM(od.Quantity * p.UnitPrice) > 100

I get the same results as the generated query. (so with duplicated names, different totals). I.o.w., I can't reproduce what you're seeing.

(edit) hmm. I think the difference is that CompanyName is unique, LastName is likely not unique. I'll rewrite the queries.

(edit) WHen I use 'ContactTitle' (which isn't unique) instead of CompanyName, I get the same number of rows. What might be different in your dataset is that there is more than 1 lastname per order? Is that the case in your situation?

(edit) you also seem to have the summation wrong, am I correct? I mean, don't you want to have the Jansen values aggregated again to 1 value and compare THAT to 100?

Equal to this:


[Test]
public void GetAllCustomersWithAnOrderTotalHigherThan1000ForOrdersWithProductsWithAnIdBiggerThan40()
{
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        LinqMetaData metaData = new LinqMetaData(adapter);
        var q = from c in metaData.Customer
                where c.Orders.Sum(o => o.OrderDetails.Where(od => od.ProductId > 40).Sum(od => od.Quantity * od.UnitPrice)) > 1000
                select c;

        int counter = 0;
        foreach(var v in q)
        {
            counter++;
        }
        Assert.AreEqual(68, counter);
    }
}

Remove the where clause on orderdetails and you're almost where you want to be. (sum over the totals per order (again a sum), per customer)

(edit) hmm, you need the unitprice from 'Product' instead. Using od.Product.UnitPrice doesn't include the join ... rage

This is really one of these days where you type in 1 extra element and everything breaks apart ... again. In our own query api we haven't had a bug in ... over a year or so... In linq ... not so much.


var q = from c in lm.Contact
        let total = c.Order.Sum(o=>o.OrderRow.Sum(od=>od.Amount * od.Product.Price))
        where total > 100
        select new {c.LastName, Total = total};

This is the linq query I think. It crashes (here) with the relationship od.Product not being joined. Using od.UnitPrice works here (but I think your model doesn't have the price on orderrow)

Frans Bouma | Lead developer LLBLGen Pro
1  /  2