Entity framework : Using summary functions inside a Projection - vb.net

I'm trying to run the following query :
Dim lst = (From t In context.MyObj1 where t1.id>6 Select New With { _
.Parent = t, _
.sash = t.child1.AsQueryable.Where(Function(t2) t2.tp=2).Sum(Function(t3) t3.quantity), _
.vlh = t.child1.AsQueryable.Where(Function(t3) t3.tp=2).Sum(Function(t3) t3.value) _
}).ToList
( in this query .quantity and .value have Decimal type.)
but I'm getting this error on runtime :
An unhandled exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll
Additional information: The cast to value type 'System.Decimal' failed because the materialized value is null.
Either the result type's genericparameter or the query must use a nullable type.
It's sure that the collection child1 has items that have .tp=2.
What's wrong ?
Thank you !
Updated :
these are the tables on database :
MyObj1:
Id name
2 name1
7 name7
8 name8
Child1:
ID ParentID TP Quantity Value
1 2 2 7 9
2 7 2 20 10
3 7 2 8 11
( ParentID is the forign key for child1 related to ID field on MyObj )
Also , I try the query like this :
Dim lst = (From t In context.MyObj1 where t1.id>6 Select New With { _
.Parent = t, _
.sash = t.child1.AsQueryable.Where(Function(t2) t2.tp=2).Count(Function(t3) t3.quantity), _
.vlh = t.child1.AsQueryable.Where(Function(t3) t3.tp=2).Count(Function(t3) t3.value) _
}).ToList
and has no problem. so I think maybe the problem is the SUM function.
Update :
This is working without errors :
Dim lst = (From t In context.MyObj1 where t1.id>6 Select New With { _
.Parent = t, _
.sash = t.child1.AsQueryable.Where(Function(t2) t2.tp=2).Sum(Function(t3) Ctype(t3.quantity,System.Nullable(of Decimal)), _
.vlh = t.child1.AsQueryable.Where(Function(t3) t3.tp=2).Sum(Function(t3) Ctype(t3.value,System.Nullable(of Decimal)) _
}).ToList
But I have problems because this method doesn't return any value on the Sums for those parent's items that doesn't have any child in Child1 collection , for example For the Item on Myobj1 with id=8 there's no child1's item , but in this case I want to return a 0 as a sum.
What can I do ?
Thank you !

Try this:
Dim lst = (From t In context.MyObj1
Where t.id > 6
Where Not (t.child1 Is Nothing)
Select New With {}).ToList
Hard to tell with just the code you've posted, but it appears something before you get into the LINQ statements is already null (i.e., Nothing).
EDIT
Sorry, just couldn't hack it in VB anymore ... switching to C# - hoping this is what you're looking for (because it's EF, I don't have an actual DB, and don't have time to set up an in-memory data store, it's not tested with your actual data):
(from t in context.MyObj1s
where t.Id > 6
from c in context.Child1s
where c.ParentId == t.Id
where c.Tp == 2
group new { Quantity = c.Quantity, Value = c.Value } by t into g
select new
{
Parent = g.Key,
Sash = g.Sum(x => x.Quantity),
Vlh = g.Sum(x => x.Value),
}).ToList();
This avoids passing the child1 navigation property on MyObj1 into a context where it's trying to convert IQueryables into SQL, which child1 is not (directly).

The cast to nullable decimals is necessary because of the null values.
If you want zeros in stead of null values you have to add DefaultIfEmpty:
Dim lst = (From t In context.MyObj1 _
where t1.id>6 Select New With { _
.Parent = t, _
.sash = t.child1.Where(Function(t2) t2.tp=2) _
.Select(Function(t3) t3.quantity), _
.DefaultIfEmpty().Sum(), _
.vlh = t.child1.Where(Function(t3) t3.tp=2) _
.Select(Function(t3) t3.value) _
.DefaultIfEmpty().Sum() _
}).ToList
This return an IEnumerable with a 0 value when there are no results in the subqueries.

Related

LİNQ SQL Creating a New List Using Join VB.NET

I'd like to get item_order and item_id from ITEM_DEF and from ITEM_SYT p4 colums. Also item_ids will be the same. And then sort it in a list order by Ascending.
So the new List will include 3 colums as item_id, item_order and p4.
EDIT: I just need to see how I should declare the List.
Here I have an example which performs well:
Dim ItemSytList As List(Of VLibrary.LINQ.ITEM_SYT) = New List(Of VLibrary.LINQ.ITEM_SYT)
ItemSytList = (From itemSyt As VLibrary.LINQ.ITEM_SYT In Context.ITEM_SYTs _
Join itemDef As VLibrary.LINQ.ITEM_DEF In Context.ITEM_DEFs On itemSyt.item_id Equals itemDef.item_id _
Where itemSyt.p1.ToUpper = "BDDK".ToUpper And itemSyt.p2.ToUpper = TableName.ToUpper _
And itemDef.template_id = TemplateId And If(itemDef.item_close_date.HasValue, itemDef.item_close_date, StartDate) >= StartDate _
And If(itemDef.item_open_date.HasValue, itemDef.item_open_date, EndDate) <= EndDate Select itemSyt).ToList
This code provides to keep ITEM_SYT's colums as it's children.
What you need to do is use an anonymous type. (Use implicit typing with LINQ whenever possible)
Dim ItemSytList =
From itemSyt As VLibrary.LINQ.ITEM_SYT In context.ITEM_SYTs
Join itemDef As VLibrary.LINQ.ITEM_DEF In context.ITEM_DEFs On itemSyt.item_id Equals itemDef.item_id
Where itemSyt.p1.ToUpper = "BDDK".ToUpper And itemSyt.p2.ToUpper = TableName.ToUpper _
And itemDef.template_id = TemplateId And If(itemDef.item_close_date.HasValue, itemDef.item_close_date, StartDate) >= StartDate _
And If(itemDef.item_open_date.HasValue, itemDef.item_open_date, EndDate) <= EndDate
Select New With {
.item_order = itemDef.item_order,
.item_id = itemDef.item_id,
.p4 = itemSyt.p4}
You can access the results like this
Dim firstP4 = ItemSytList.OrderBy(Function(i) i.item_order).First().p4

How to order inner keysource in linq vb group join

I'm trying to join a table to itself using a group join and VB.NET. The code below works and sorts the outer (parent) rows but I'd like to guarantee the sequence of the inner (child) rows:
Dim queryEthnicities = From aParentEthnicity In edata.Ethnicity _
Group Join aChildEthnicity In edata.Ethnicity On aChildEthnicity.ParentID Equals aParentEthnicity.EthnicityID Into EthnicityList = Group _
Where aParentEthnicity.RoundID.Equals(aRoundID) Order By aParentEthnicity.Sort
I found something similar for C# at Ordering inner keysource in simple/unnamed C# LINQ group join
but haven't found a way to do this in VB - grateful for any ideas.
Here's what you need to provide when asking a question:
Public Class edata
Public Ethnicity As Ethnicity() = _
{ _
New Ethnicity() With { .EthnicityID = 4, .ParentId = 1, .RoundID = 3, .Sort = 8 }, _
New Ethnicity() With { .EthnicityID = 3, .ParentId = 1, .RoundID = 4, .Sort = 5 }, _
New Ethnicity() With { .EthnicityID = 1, .RoundID = 2 } _
}
End Class
Public Class Ethnicity
Public ParentID As Integer
Public EthnicityID As Integer
Public RoundID As Integer
Public Sort As Integer
End Class
Sub Main
Dim edata = New edata()
Dim aRoundID = 2
Dim queryEthnicities = _
From aParentEthnicity In edata.Ethnicity _
Group Join aChildEthnicity In edata.Ethnicity On aChildEthnicity.ParentID Equals aParentEthnicity.EthnicityID Into EthnicityList = Group _
Where aParentEthnicity.RoundID.Equals(aRoundID) _
Order By aParentEthnicity.Sort
End Sub
When this code runs it produces:
To ensure the order of the inner data you need to change the query like so:
Dim queryEthnicities = _
From aParentEthnicity In edata.Ethnicity _
Group Join aChildEthnicity In edata.Ethnicity.OrderBy(Function (x) x.Sort) On aChildEthnicity.ParentID Equals aParentEthnicity.EthnicityID Into EthnicityList = Group _
Where aParentEthnicity.RoundID.Equals(aRoundID) _
Order By aParentEthnicity.Sort
Now it produces:

How can I preserve Null (DBNull) in Sum in LINQ queries

I am trying to perform this query on two DataTables in a DataSet
SELECT Totals.accCategory, Totals.ID, Totals.Account, Sum(Totals.Jan) AS Jan FROM (SELECT * FROM Allocated UNION SELECT * FROM Spent) AS Totals GROUP BY Totals.accCategory, Totals.ID, Totals.Account
As they are generated in code (in memory) into the DataSet I need to use LINQ thus:
Dim t = (From totals In (allocated.AsEnumerable.Union(spent.AsEnumerable)) _
Group totals By accCategory = totals.Item("accCategory"), ID = totals.Item("ID"), Account = totals.Item("Account") _
Into g = Group _
Select New With {Key .accCategory = accCategory, Key .ID = ID, Key .Account = Account, Key .Jan = g.Sum(Function(totals) Totals.Item("Jan"))}).ToList
Which fails as there are some instances where there are no records to sum. The Access query returns an empty cell - which is what I want. I can make the LINQ statement work by using If(IsDbNull(totals.Item("Jan")),0,totals.Item("Jan")) but then I get 0.00 if the total is zero (which is correct) but also if there are no items to sum (which I don't want)
I have tried Select New With {Key .accCategory = accCategory, Key .ID = ID, Key .Account = Account, Key .Jan = g.Sum(Function(totals) DirectCast(totals.Item("Jan"), Nullable(Of Decimal)))}).ToList which doesn't work either.
How can I make .Jan a Nullable(Of Decimal) and accept DBNull as a value??
Thanks
Andy
Got it!
Dim t = (From totals In (allocated.AsEnumerable.Union(spent.AsEnumerable)) _
Group totals By accCategory = totals.Item("accCategory"), ID = totals.Item("ID"), Account = totals.Item("Account") _
Into g = Group _
Select New With {Key .accCategory = accCategory, Key .ID = ID, Key .Account = Account, Key .Jan = If(g.AsQueryable.Any(Function(totals) totals.Field(Of Nullable(Of Decimal))("Jan").HasValue), g.AsQueryable.Sum(Function(totals) totals.Field(Of Nullable(Of Decimal))("Jan")), Nothing)}).ToList

Nested Linq Queries Saga

So I am trying to write something like this:
SELECT s.CompanyID,
s.ShareDate,
s.OutstandingShares,
s.ControlBlock
FROM (
SELECT MAX(ShareDate) AS Sharedate,
CompanyID
FROM ShareInfo
WHERE (ShareDate <= #filter_date)
GROUP BY CompanyID
) AS si
INNER JOIN
tblShareInfo AS s ON s.ShareDate = si.Sharedate AND s.CompanyID = si.CompanyID
Essentially this is trying to return the most recent Share Information, we keep a running history. Now I am trying to write something similar to this in LINQ.
Here was my closest attempt:
From a _
In db_context.ShareInfos _
Where a.ShareDate <= filter_date _
Group a By a.CompanyID Into Group _
Select CompanyID, MostRecentShareDate = Group.Max(Function(a) a.ShareDate) _
Join b In db_context.ShareInfos On b.CompanyID Equals a.CompanyID _
Select b.CompanyID, b.ShareDate, b.OS, b.CB()
Unfortunately this does not compile. Obviously I'm not understanding the LINQ syntax somehow. Can anyone steer me in the right direction?
Thanks.
with your last select statement you should use
select new {
CompanyID = b.CompanyID,
ShareDate = b.ShareDate,
OS = b.OS,
CB = b.CB
};
that's a start...
Okay so looks like this needs to be done using two statements:
Dim MostRecentShareDates = _
From s2 In query_collection.DBContext.ShareInfos _
Where s2.ShareDate <= filter_date _
Group s2 By s2.CompanyID Into Group _
Select New With { _
.CompanyID = CompanyID, _
.MostRecentShareDate = Group.Max(Function(s3) s3.ShareDate) _
}
Return From s In query_collection.DBContext.ShareInfos _
Join s1 In MostRecentShareDates On s.CompanyID Equals s1.CompanyID And s.ShareDate Equals s1.MostRecentShareDate _
Select New With { _
.CompanyID = s.CompanyID, _
.ShareDate = s.ShareDate, _
.OS = s.OS, _
.CB = s.CB _
}
I tried using the 'Let' keyword to embed the first statement into the second, but that would not compile either. Now the nice thing about this is the Linq has delayed execution, so until you traverse the collection returned by the second statement, no SQL gets generated. Linq is then smart enough to combine the two code fragments into one SQL statement, essentially exactly the same statement as I wrote in my original SQL above.

Linq syntax in VB.NET

What I really want is to select these two tables in to an anon type like in Scott Gu's blog: here However, I would settle for this created type "ActiveLots" I am joining two tables together and want to be able to reference columns from each in my result set.
I don't seem to be getting the syntax correctly.
Dim pi = From p In dc.Inventories Join i In dc.InventoryItems On p.InventoryItemID _
Equals i.InventoryItemID Where p.LotNumber <> "" _
Select New ActiveLots LotNumber = p.LotNumber, Quantity = p.Quantity, Item = i.Item, Uom = i.UnitofMeasure, Description = i.Description
Have a look at Daniel Moth's blog entry. I suspect you want:
Dim pi = From p In dc.Inventories _
Join i In dc.InventoryItems
On p.InventoryItemID Equals i.InventoryItemID _
Where p.LotNumber <> "" _
Select New With { .LotNumber = p.LotNumber, .Quantity = p.Quantity, _
.Item = i.Item, .Uom = i.UnitofMeasure, _
.Description = i.Description }
That's using an anonymous type - to use a concrete type, you'd use New ActiveLots With { ... (where ActiveLots has to have a parameterless constructor).