Linq GROUP BY or manually grouping in a loop? - vb.net

Yesterday I asked this question on stackoverflow. Today I realize that if I do a GROUP BY I also need to create a new type of object.
Let's say I have some data that looks like this:
var1 var2 var3 qty
1 a 1a 50
1 a 1a 25
2 b 2b 10
2 b 2b 15
2 b 2b 10
3 a 3a 25
Here is my working LinQ query
From j In MyTable
Where j.var1 = "xxx"
Group j By Key = New With {Key .var1 = j.var1, Key .var2= j.var2, Key .var3 = j.var3} Into Group
Select New With {.var1 = Key.var1, .var2 = Key.var2, .var3 = Key.var3, .qty = Group.Sum(Function(x) x.qty)}
Actually I use Entity Framework and the code look more like this
Dim foo = (From j In dbContext.MyTable
Where j.var1 = anotherVariable
Group j By Key = New With {Key .var1 = j.var1, Key .var2= j.var2, Key .var3 = j.var3} Into Group
Select New With {.var1 = Key.var1, .var2 = Key.var2, .var3 = Key.var3, .quantity = Group.Sum(Function(x) x.Qty)}).ToArray()
foo is a new type that doesn't exist in my generated Entities. But I have an entity generated by my entity framework that can contains these. It's MyTable itself. I use a GROUP BY only to sum a column of MyTable. I query a MyTable entities and I can put the result in a MyTable entity too.
My question are
1) Can I write something like this
Dim foo = (From j In dbContext.MyTable
Where j.var1 = anotherVariable
Group j By Key = New With {Key .var1 = j.var1, Key .var2= j.var2, Key .var3 = j.var3} Into Group
Select New MyTable With {.var1 = Key.var1, .var2 = Key.var2, .var3 = Key.var3, .qty = Group.Sum(Function(x) x.qty)}).ToArray()
In this case do I need to explicitely write all the mappings ?
2) Should I change my mind. Do a simpler query without GROUP BY and try to group and sum in a VB.NET loop (For Each). Or two queries ? On to get all MyTable with a WHERE clause and another to group ?
Dim foo = dbContext.MyTable.Where(Function(p As MyTable) p.var1 = anotherVariable).ToArray()
For Each bar In foo
'Code to group and sum or another query
Next

You won't be able to instantiate MyTable in an LINQ to Entities query but you can simply enumerate the results of the projection with ToArray and then construct the entities with another Select call.

Related

Select several levels of childs

I'm using entity Framework 6.
On my database I have these tables :
MasterTable ( Id , name)
Child1 ( ID , name , vl1 , Master_ID)
Child2 ( ID , name , vl2 , MasterID )
Child3 (ID , name , vl3 , Master_ID )
Child3Itm ( ID , name , Child3_ID)
For a given MasterTable item, I want to load with a single Query from database:
All Child1 where vl1 > 5
All Child2 where vl2 > 6
All Child3 where vl3 > 7
And in each Child3 to load all of the Child3Itm content.
I'm using this query:
Dim lst = (From t In context.MasterTable.Where(Function(t1) t1.id = 7)
Select New With {
t,
.chld1 = t.child1s.Where(Function(t21) t21.vl1 >5),
.chld2 = t.child2s.Where(Function(t31) t31.vl2>6 ),
.chld3 = t.child3s.Where(Function(t41) t41.vl3>7).Select(Function(t411) t411.Child3Itms)
}).ToList
The problem is that no Child3 are selected. All others are OK. What can i do? Thanks in advance!
Without seeing your data, it will be tough to diagnose. But since you have access to a debugger, you can do it yourself. With the code below, step over each line and inspect the variables. It should be easy to see where your code is failing
Dim masters = From t In context.MasterTable Where t = 7
Dim child1s = From m In masters Where m.child1s.vl1 > 5
Dim child2s = From m In masters Where m.child2s.vl2 > 6
Dim child3s = From m In masters Where m.child3s.vl3 > 7
Dim child3i = child3s.Child3Itms
Might be a typo in your question. Make sure the condition for the child3s is correct:
t41.vl>7
Should it be this?
t41.vl3 > 7
Dim lst = (From t In context.MasterTable.Where(Function(t1) t1.id = 7)
Select New With {
t,
.chld1 = t.child1s.Where(Function(t21) t21.vl1 >5),
.chld2 = t.child2s.Where(Function(t31) t31.vl2>6 ),
.chld3 = t.child3s.Where(Function(t41) t41.vl3>7),
.chld3itms = t.child3s.Where(Function(t51) t51.vl3>7).Select(Function(t511) t511.Child3Itms)
}).ToList
Dim lst = (From t In context.MasterTable.Where(Function(t1) t1.id = 7)
Select New With {
t,
.chld1 = t.child1s.Where(Function(t21) t21.vl1 >5),
.chld2 = t.child2s.Where(Function(t31) t31.vl2>6 ),
.chld3 = (From t2 in t.child3s.Where(Function(t41) t41.vl3>7)
Select New With {
t2,
.chld3it=t2.Child3Itms
})
}).ToList
This is working too.
If someone can tell me if this answer is better or no compared with the other solution posted by antoni ???

datatable sum column and concatenate rows using LINQ and group by on multiple columns

I Have a datatable with following records
ID NAME VALUE CONTENT
1 AAA 10 SYS, LKE
2 BBB 20 NOM
1 AAA 15 BST
3 CCC 30 DSR
2 BBB 05 EFG
I want to write a VB.NET/LINQ query to have a output like below table: -
ID NAME SUM CONTENT (as CSV)
1 AAA 25 SYS, LKE, BST
2 BBB 25 NOM, EFG
3 CCC 30 DSR
Please provide me LINQ query to get the desired result. Thanks.
I have tried concatenation using below query
Dim grouped = From row In dtTgt.AsEnumerable() _
Group row By New With {row.Field(Of Int16)("ID"), row.Field(Of String)("Name")} _
Into grp() _
Select ID, Name, CONTENT= String.Join(",", From i In grp Select i.Field(Of String)("CONTENT"))
This query will give you the expected output:-
Dim result = From row In dt.AsEnumerable()
Group row By _group = New With {Key .Id = row.Field(Of Integer)("Id"),
Key .Name = row.Field(Of String)("Name")} Into g = Group
Select New With {Key .Id = _group.Id, Key .Name = _group.Name,
Key .Sum = g.Sum(Function(x) x.Field(Of Integer)("Value")),
Key .Content = String.Join(",", g.Select(Function(x) x.Field(Of String)("Content")))}
Thanks for your answers.
However, I have managed to get the desired result using simple code (Without LINQ): -
Dim dt2 As New DataTable
dt2 = dt.Clone()
For Each dRow As DataRow In dt.Rows
Dim iID As Integer = dRow("ID")
Dim sName As String = dRow("Name")
Dim sContt As String = dRow("Content")
Dim iValue As Integer = dRow("Value")
Dim rwTgt() As DataRow = dt2.Select("ID=" & iID)
If rwTgt.Length > 0 Then
rwTgt(0)("Value") += iValue
rwTgt(0)("Content") += ", " & sContt
Else
rw = dt2.NewRow()
rw("ID") = iID
rw("Name") = sName
rw("Value") = iValue
rw("Content") = sContt
dt2.Rows.Add(rw)
End If
Next

Update a table from another table

I have got 2 tables "animal_breeds" and "ztmp.ztmp_509810_anims_out". In "animals breed" every animal has key and breed name and percentage. Few animals might have 2 different breeds with different percentage. Now based on the animals key in "animals_breeds" i want to update "ztmp.ztmp_509810_anims_out"
i am using this code which i know is wrong
update ztmp.ztmp_509810_anims_out
set
alt_id1 = ab.breed
,alt_id2 = pcnt
,alt_id3 = ab.breed
,alt_id4 = pcnt
,alt_id5 = ab.breed
,alt_id6 = pcnt
,alt_id7 = ab.breed
,alt_id8 = pcnt
from animal_breeds ab
where ab.soc_code = ztmp_509810_anims_out.soc_code and ab.animals_key = ztmp_509810_anims_out.animals_key
and ab.soc_code = 'AUNDB';
could i use a for loop inside an update statement?
UPDATE ztmp.ztmp_509810_anims_out AS z
SET soc_code = q.soc_code,
animals_key = q.animals_key,
alt_id1 = breeds[1],
alt_id2 = pcnts[1],
alt_id3 = breeds[2],
alt_id4 = pcnts[2]
FROM (SELECT soc_code, animals_key,
array_agg(breed) breeds, array_agg(pcnt) pcnts
FROM animal_breeds
GROUP BY soc_code, animals_key
) q
WHERE z.soc_code = q.soc_code
AND z.animals_key = q.animals_key;
If there can be more than 2 breeds per animals_key, add breeds[3] and pcnts[3] and so on.

Linq query with GROUP BY on multiple fields, SUM and COUNT

I'm trying to use a LINQ SQL to query my database.
Let's say I have some data that looks like this:
var1 var2 var3 qty
1 a 1a 50
1 a 1a 25
2 b 2b 10
2 b 2b 15
2 b 2b 10
3 a 3a 25
I know how to format my query in SQL. It would be something like this:
SELECT var1, var2, var3, count(*) AS count, sum(qty) As quantity
FROM MyTable
GROUP BY var1, var2, var3
In this case the output would be something like this:
var1 var2 var3 Count Qty
1 a 1a 2 75
2 b 2b 3 35
3 a 3a 1 25
How can I do the same thing with LINQ in vb.net
Dim groups = From j In MyTable
Group By j.var1, j.var2, j.var3 Into g
Select new {var1 = g.var1,
var2 = g.var2,
var3 = g.var3,
quantity = sum(g.qty),
count = count(*)}
That's not quite right, but I think it's close. I don't understand the syntax of the group by in VB.NET.
You want to group by an anonymous type, this is the syntax in VB.NET (note the Key):
Dim groups =
From j In MyTable
Group By x = New With {Key .var1 = j.var1, Key .var2 = j.var2, Key .var3 = j.var3} Into g = Group
Select New With {
.var1 = x.var1,
.var2 = x.var2,
.var3 = x.var3,
.quantity = g.Sum(Function(r) r.qty),
.count = g.Count()
}
You will have to use anonymous type like this:-
Group j By Key = New With { Key .Var1 = j.var1,
Key .Var2 = j.var2,
Key .Var3 = j.var3 } Into Group
Select New With { .var1 = Key.Var1,
.var2 = Key.Var2,
.var3 = Key.Var3,
.quantity = Group.Sum(Function(x) x.qty),
.count = Group.Count()
}

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