How to order inner keysource in linq vb group join - vb.net

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:

Related

How to transform an SQL statement with Group by and Order By into a LINQ statement in vb.net

I have the following code working correctly, but was requested to combine it into one LINQ statement:
Dim AddlOrders = From ords In ctxi.V_TKT_HIST_BVs.AsEnumerable() _
Select ords Where (ords.CUST_NO = cstno) And (ords.ORIG_STA_ID <> "SWWEB") _
Order By ords.ORIG_TKT_NO Descending, ords.TKT_DT Descending
Dim AddlOrds As New Collection(Of V_TKT_HIST_BV)
Dim o As New V_TKT_HIST_BV
If (cstno Is Nothing) OrElse (AddlOrders Is Nothing) OrElse (AddlOrders.Count = 0) Then
AddlOrdersLabel.Text = "You have 0 additional orders."
AddlOrdersGrid.Visible = False
Else
For Each ord In AddlOrders
If prevord = String.Empty Then
prevord = ord.ORIG_TKT_NO
totord = ord.TOT
o = ord
ElseIf prevord = ord.ORIG_TKT_NO Then
totord += ord.TOT
Else
o.TOT = totord
AddlOrds.Add(o)
prevord = ord.ORIG_TKT_NO
totord = ord.TOT
o = ord
End If
Next
If o IsNot Nothing Then
AddlOrds.Add(o)
End If
Dim Addord = From ords In AddlOrds Order By ords.TKT_DT Descending
AddlOrdersGrid.DataSource = Addord
I have tried the following statement, but Visual Studio changes "Into os" to "Into os()" and gives a message that Definition of method os is not accessible in this context:
Dim orders = From o1 In ctxi.V_TKT_HIST_BVs
Where o1.CUST_NO = cstno
Group o1 By o1.TKT_DT, o1.ORIG_TKT_NO, o1.TOT
Into os() Select ORIG_ORD_NO, total = os.Sum(TOT),
tdate = os.Last(Function(v) v.TKT_DAT)
An example of the SQL would be like:
SELECT TOP (200) CUST_NO, EMAIL_ADRS_1, SUM(TOT) AS Expr1, ORIG_TKT_NO,
MIN(DISTINCT TKT_DT) AS Expr2
FROM V_TKT_HIST_BV
GROUP BY CUST_NO, EMAIL_ADRS_1, ORIG_TKT_NO
HAVING (EMAIL_ADRS_1 LIKE 'name%')
ORDER BY Expr2
Does anyone have an idea why it would change os into a method?
This would be it in C#:
AddlOrderGrid.DataSource=ctxi.V_TKT_HIST_BVs
.Where(t=>t.CUST_NO==cstno)
.Where(t=>t.ORIG_STA_ID!="SWWEB")
.GroupBy(t=>t.ORIG_TKT_NO)
.Select(t=> new {
CUST_NO=cstno,
EMAIL_ADRS_1=t.FirstOrDefault().EMAIL_ADRS_1,
TOT=t.SUM(u=>u.TOT),
ORIG_TKT_NO=t.Key,
TKT_DT=t.Min(u=>u.TKT_DT)
})
.OrderByDescending(t=>t.TKT_DT);
Converted to VB.NET:
Dim Addord = ctxi.V_TKT_HIST_BVs _
.Where(Function(t) t.CUST_NO = cstno) _
.Where(Function(t) t.ORIG_STA_ID <> "SWWEB") _
.GroupBy(Function(t) t.ORIG_TKT_NO) _
.Select(Function(t) New With { _
Key .CUST_NO = cstno, _
Key .EMAIL_ADRS_1 = t.FirstOrDefault().EMAIL_ADRS_1, _
Key .TOT = t.SUM(Function(u) u.TOT), _
Key .ORIG_TKT_NO = t.Key, _
Key .TKT_DT = t.Min(Function(u) u.TKT_DT) _
}).OrderByDescending(Function(t) t.TKT_DT)
If (Addord.Any()) Then
AddlOrderGrid.DataSource=Addord
Else
AddlOrdersLabel.Text = "You have 0 additional orders."
AddlOrdersGrid.Visible = False
Endif

Entity framework : Using summary functions inside a Projection

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.

LINQ-To-DataSet Anonymous types - Double converted to integer - why?

Below is my simplified class.
I have 2 methods that are getting some data from dataset through LINQ.
Then I have GetTotalSales() method that unions and sums all data.
Before final union statement I am checking data in
For Each r In O1
Dim t = r.TYWeekSales ' so far so good - t is decimal
Next
and r.TYWeekSales is correct - it is decimal.
after union statement:
Dim union = O1.Union(O2)
Dim TotalSales2 = From u In union
Group u By _
Name = u.Name
Into Group _
Select TYWeekNetSale = Group.Sum(Function(u) u.TYWeekNetSale), _
LYWeekNetSale = Group.Sum(Function(u) u.LYWeekNetSale)
For Each r2 In TotalSales2
Dim t2 = r2.TYWeekNetSale ' no good - t2 is an integer
Next
t2 is converted to integer. Why? How can this be fixed?
Here is my whole class:
Public Class Class1
Private _Orders1 As IEnumerable
Private _Orders2 As IEnumerable
Private _DSNetSales As DataSet
Public Property Orders1() As IEnumerable
Get
Return _Orders1
End Get
Set(value As IEnumerable)
_Orders1 = value
End Set
End Property
Public Property Orders2() As IEnumerable
Get
Return _Orders2
End Get
Set(value As IEnumerable)
_Orders2 = value
End Set
End Property
Public Property DSNetSales() As DataSet
Get
Return _DSNetSales
End Get
Set(value As DataSet)
_DSNetSales = value
End Set
End Property
Private Sub GetSomething()
Dim TYWeekSales = DSNetSales.Tables("T1").AsEnumerable()
Dim TYWeekSalesData = From b In TYWeekSales
Group b By _
Name = b.Field(Of String)("loc")
Into Group _
Select Name,
TYWeekNetSale = Group.Sum(Function(b) b.Field(Of Decimal)("net")), _
LYWeekNetSale = CType(0.0, Decimal)
Dim LYWeekSales = DSNetSales.Tables("T2").AsEnumerable()
Dim LYWeekSalesData = From b In LYWeekSales
Group b By _
Name = b.Field(Of String)("loc")
Into Group _
Select Name,
TYWeekNetSale = CType(0.0, Decimal), _
LYWeekNetSale = Group.Sum(Function(b) b.Field(Of Decimal)("net"))
Dim union = TYWeekSalesData.Union(LYWeekSalesData)
Orders1 = From u In union
Group u By _
Name = u.Name
Into Group _
Select Name,
TYWeekNetSale = Group.Sum(Function(u) u.TYWeekNetSale), _
LYWeekNetSale = Group.Sum(Function(u) u.LYWeekNetSale)
End Sub
Private Sub GetSomethingElse()
Dim TYWeekSales = DSNetSales.Tables("T3").AsEnumerable()
Dim TYWeekSalesData = From b In TYWeekSales
Group b By _
Name = b.Field(Of String)("loc")
Into Group _
Select Name,
TYWeekNetSale = Group.Sum(Function(b) b.Field(Of Decimal)("net")), _
LYWeekNetSale = CType(0.0, Decimal)
Dim LYWeekSales = DSNetSales.Tables("T4").AsEnumerable()
Dim LYWeekSalesData = From b In LYWeekSales
Group b By _
Name = b.Field(Of String)("loc")
Into Group _
Select Name,
TYWeekNetSale = CType(0.0, Decimal), _
LYWeekNetSale = Group.Sum(Function(b) b.Field(Of Decimal)("net"))
Dim union = TYWeekSalesData.Union(LYWeekSalesData)
Orders2 = From u In union
Group u By _
Name = u.Name
Into Group _
Select Name,
TYWeekNetSale = Group.Sum(Function(u) u.TYWeekNetSale), _
LYWeekNetSale = Group.Sum(Function(u) u.LYWeekNetSale)
End Sub
Private Sub GetTotalSales()
Dim O1 = From b In Orders1
Dim O2 = From p In Orders2
For Each r In O1
Dim t = r.TYWeekSales ' so far so good - t is decimal
Next
For Each r In O2
Dim t = r.TYWeekSales
Next
Dim union = O1.Union(O2)
Dim TotalSales2 = From u In union
Group u By _
Name = u.Name
Into Group _
Select TYWeekNetSale = Group.Sum(Function(u) u.TYWeekNetSale), _
LYWeekNetSale = Group.Sum(Function(u) u.LYWeekNetSale)
For Each r2 In TotalSales2
Dim t2 = r2.TYWeekNetSale ' hmmm - t2 is an integer now
Next
End Sub
End Class

PredicateBuilder + Join + VB.NET

I have a db that contains 3 tables
Products Table Suppliers Table Categories Table
Id Id Id
ProductName SupplierName CategoryName
Quantity . .
SupplierId . .
CategoryId
.
.
I am using PredicateBuilder to select Product accoring to the selected product field (Quantity,Productname...)
How Can I use PredicateBuilder or any other method to Select the Product according to Its Suppliername or Categoryname
Please I am using VB.NET I saw many C# examples but I can not understand it nor translate it
I am thinking of using join in predicateBuilder but I do not how !!!!
To be more clear What I want is to combine multiple field in one search ,Like for example:
Give me a Product where It's Name contains "s" and Quantity <10 and SupplierName is Kimo
Give me a product where i's name contains "g" only
give me the products for categoryName "Machines"
.
.
And this search predicate is changable because Each Products table has many fields ,So the search is dynamic according to selected fields
Waiting for your kind help.
I think Arion answer was correct but It needs some revision
AnyWay I came up with this solution ,It is not the most effecient one but It solved my problem.
Dim SupplierAlso As String = ""
Dim CategoryAlso As String = ""
Dim pred = PredicateBuilder.True(Of Products)()
Select Case Entry
Case "Number"
Dim Inum = Entry
pred = pred.And(Function(m As products) m.ID.Equals(CInt(Inum)))
Case "ProductName"
Dim IName = Entry
pred = pred.And(Function(m As Products) m.ProductName.IndexOf(IName, StringComparison.OrdinalIgnoreCase) >= 0)
.
.
.
Case "Supplier"
SupplierAlso = Entry
Case "Category"
CategoryAlso = Ent
Next
Dim f As ProductsDataTable = Products.Product
Dim tmp As IEnumerable(Of Products) = New ProductsDataTable().AsEnumerable()
tmp = f.AsEnumerable.Where(pred.Compile)
Dim qry
If CategoryAlso = "" And SupplierAlso = "" Then
q = (From prods In tmp
Join Cats In Categories
On prods.CategoryId Equals Cats.ID
Join Supps In Suppliers
On Supps.ID Equals prods.SupplierId
Select Supps.SupplierName, Cats.CategoryName, prods.ID _
, prods.ProductName, prods.UnitPrice, prods.CategoryId _
, prods.SupplierId, prods.Location, _
prods.Description, prods.SellPrice, prods.CountInStock _
, prods.ProductionDate, prods.ExpiryDate, _
prods.ProductType, prods.ProductSeason).ToList
ElseIf CategoryAlso <> "" And SupplierAlso <> "" Then
q = (From prods In tmp
Join Cats In Categories
On prods.CategoryId Equals Cats.ID
Join Supps In Suppliers
On Supps.ID Equals prods.SupplierId
Where Cats.CategoryName.IndexOf((CategoryAlso)
, StringComparison.OrdinalIgnoreCase) >= 0 And _ Supps.SupplierName.IndexOf((SupplierAlso), _
StringComparison.OrdinalIgnoreCase) >= 0
Select Supps.SupplierName, Cats.CategoryName, prods.ID, _
prods.ProductName, prods.UnitPrice, prods.CategoryId, _
prods.SupplierId, prods.Location, _
prods.Description, prods.SellPrice, prods.CountInStock, _
prods.ProductionDate, prods.ExpiryDate, _
prods.ProductType, prods.ProductSeason).ToList
ElseIf SupplierAlso <> "" And CategoryAlso = "" Then
q = (From prods In tmp
Join Cats In Categories
On Cats.ID Equals prods.CategoryId
Join Supps In Suppliers
On prods.SupplierId Equals Supps.ID Where _
Supps.SupplierName.IndexOf((SupplierAlso), _
StringComparison.OrdinalIgnoreCase) >= 0
Select Cats.CategoryName, Supps.SupplierName, prods.ID, _
prods.ProductName, prods.UnitPrice, prods.CategoryId, _
prods.SupplierId, prods.Location, _
prods.Description, prods.SellPrice, prods.CountInStock, _
prods.ProductionDate, prods.ExpiryDate, _
prods.ProductType, prods.ProductSeason).ToList
ElseIf CategoryAlso <> "" And SupplierAlso = "" Then
q = (From prods In tmp
Join Cats In Categories
On prods.CategoryId Equals Cats.ID Where Cats.CategoryName.IndexOf _
((CategoryAlso), StringComparison.OrdinalIgnoreCase) >= 0
Join Supps In Suppliers On Supps.ID Equals prods.SupplierId
Select Supps.SupplierName, Cats.CategoryName, prods.ID, _
prods.ProductName, prods.UnitPrice, prods.CategoryId, _
prods.SupplierId, prods.Location, _
prods.Description, prods.SellPrice, prods.CountInStock, _
prods.ProductionDate, prods.ExpiryDate, _
prods.ProductType, prods.ProductSeason).ToList
End If
For Each it In q
With it
DataGridView2.Rows.Add _
({.ID, .ProductName, .UnitPrice, .categoryname, .suppliername, .Location, _
.Description, .SellPrice, .CountInStock, _ .ProductionDate, .ExpiryDate, _
.ProductType, .ProductSeason})
End With
Next
So what do you think ,Is there a better way? Ofcourse yes?Silly question ,But Where?
So i would do something like this then. I have simplify the where statements but I think you will get my point:
dim checkCategoryName as boolean=true
dim checkSupplier as boolean=true
dim checkQuantity as boolean=true
dim query= db.Products.Select (function(p) p)
if checkCategoryName then
query=query _
.Where (function(p) db.Categories.Where (function(c) c.CategoryName="??" ) _
.Select (function(c) c.Id) _
.Contains(p.CategoryId))
end if
if checkSupplier then
query=query _
.Where (function(q) db.Suppliers.Where (function(s) s.SupplierName="??") _
.Select (function(s) s.Id) _
.Contains(q.SupplierId))
end if
if checkQuantity then
query=query.Where (function(q) q.Quantity<10)
end if
dim result=query.ToList()
Where db is the linq data context.

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).