Linq Group by a field and count distinct by another - vb.net

I have a datatable and I want to use LINQ to get the equivalent of
Select barid, count(distinct bar) as barCount
From myTable
Group By barid
The data is in the form:
barid bar
4 1
4 1
4 1
12 2
12 2
12 2
12 3
13 1
13 2
13 3
The result should be:
barid, barCount
4 1
12 2
13 3
Can you help?

I cannot translate it into vb.net but the c# equivalent would be:
var result = from record in data
group record by record.barid into grp
select new
{
barid = grp.Key,
barCount = grp.Select( item => item.bar ).Distinct( ).Count( )
};

Considering your DataTable columns to be of type Integer, you can use below query:-
Dim result = dt.AsEnumerable().GroupBy(Function(x) x.Field(Of Integer)("barid")) _
.[Select](Function(x) New With { _
Key .barid = x.Key, _
Key .barCount = x.[Select](Function(z) z.Field(Of Integer)("bar")) _
.Distinct().Count()})
Then you can simply use the ForEach like this:-
For Each x In result
Console.WriteLine("barid {0}",x.barid)
Console.WriteLine("barCount {0}", x.barCount)
Console.WriteLine("---------------")
Next
This is giving me the following output:-

Related

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

Grouping identical rows and returning the id of the row with a maximum value

I have a datatable like below:
id desc amt value 1 value 2 value 3 value 4 count Consolidated ID
1 test 23 78 98 30 13 5
2 sample 14 25 45 36 12 24
3 test 23 78 98 30 13 30
4 sample 14 25 45 36 12 20
5 test 23 78 98 30 13 11
I need to
group by the columns desc, amt, value1, value2, value3 and value4, and
return the consolidated ID as the id which has maximum in the count column for each group. Result set should be like below:
id desc amt value 1 value 2 value 3 value 4 count Consolidated ID
1 test 23 78 98 30 13 5 3
3 test 23 78 98 30 13 30 3
5 test 23 78 98 30 13 11 3
2 sample 14 25 45 36 12 24 2
4 sample 14 25 45 36 12 20 2
I know this can be done looping through datatable. But is there a simpler way to do it with LINQ in 1 or 2 statements?
DataTable - dt
Result will be of type IEnumerable of anonymous type in the Select statement, which has same columns as your DataTable
var final = dt.AsEnumerable().GroupBy(x=>
new {
amt = x["amt"],
value1 = x["value1"],
value2 = x["value2"],
value3 = x["value3"],
value4 = x["value4"],
})
.ToDictionary(p=>p.Key,
p=>new {id = p.Select(s=>s["id"]),
cid = p.OrderByDescending(n=>Convert.ToInt32((n["count"]))).Select(s=>s["id"]).First()})
.SelectMany(a=>a.Value.id.Select(h=>new {
h,
a.Key.amt,
a.Key.value1,
a.Key.value2,
a.Key.value3,
a.Key.value4,
ConsolidatedID = a.Value.cid
}));
Also pasting the VB.Net version (using Telerik Code Converter) as that has been the point of contention, though needs verification, as I do not work in VB.Net
Dim final = dt.AsEnumerable().GroupBy(Function(x) New With { _
Key .amt = x("amt"), _
Key .value1 = x("value1"), _
Key .value2 = x("value2"), _
Key .value3 = x("value3"), _
Key .value4 = x("value4") _
}).ToDictionary(Function(p) p.Key, Function(p) New With { _
Key .id = p.[Select](Function(s) s("id")), _
Key .cid = p.OrderByDescending(Function(n) Convert.ToInt32((n("count")))).[Select](Function(s) s("id")).First() _
}).SelectMany(Function(a) a.Value.id.[Select](Function(h) New With { _
h, _
a.Key.amt, _
a.Key.value1, _
a.Key.value2, _
a.Key.value3, _
a.Key.value4, _
Key .ConsolidatedID = a.Value.cid _
}))
If you want to fill the consolidated id column in the datatable, I suggest you first define a local function that returns the "key" of the row -- the values important to grouping:
Dim keyGetter = Function(row As DataRow) New With {
Key .desc = row("desc"),
Key .amt = row("amt"),
Key .value1 = row("value 1"),
Key .value2 = row("value 2"),
Key .value3 = row("value 3"),
Key .value4 = row("value 4")
}
Then, for each group, you can get the id of the row with the maximum count:
Dim results = dt.AsEnumerable.GroupBy(keyGetter, Function(key, grp)
Dim maxCount = grp.Max(Function(row) x("count"))
Return grp.First(Function(row) row("count") = maxCount)
End Function).ToDictionary(Function(x) x.Key, Function(x) x.First)
Then you can iterate over the datatable and fill the column. For each row, generate the key using the keyGetter, and use that to get the consolidated id:
For Each row In dt.AsEnumerable
row("consolidated id") = results(keyGetter(row))
Next

Linq/vb.net: Calculate DateEnd being DateInit of next register

I am having this class as entity:
public class Something
Property Status as integer
Property DateInit as DateTime
Property DateEnd as DateTime
End Class
I have a generic list with n items of that class. The Structure is:
State DateInit DateEnd
3 10/2/2015 12/2/2015
10 12/4/2015 13/5/2015
22 13/5/2015 2/11/2015
...
I need calculate a formula using DateInit and DateEnd. I have 10 states which forms the row in a grid. In the cell I set the date which state was changed (it is DateInit). DateEnd is the next DateInit date in the row of the cell changed.
Some sample of the grid:
STATUS => 1 3 6 10 15 16 21 22
DATEINIT => null null null 12/4/2015 null null 13/5/2015 null ...
It is the grid.
If I change status I need to set the next DateInit column with value
I report a new Sample, If I have this list:
Dim mylist As New List (Of Something) ()
Dim item1
item1.status = 3
item1.DAteInit = Datetime.parse("10/2/2015")
item1.DAteEnd = Datetime.parse("12/4/2015")
mylist.add(item1)
Dim item2
item2.status = 10
item2.DAteInit = Datetime.parse("10/2/2015")
item2.DAteEnd = Datetime.parse(" 13/5/2015")
mylist.add(item2)
Dim item3
item3.status = 11
item3.DAteInit = Datetime.parse(nothing)
item3.DAteEnd = Datetime.parse(nothing)
mylist.add(item3)
Dim item4
item4.status = 12
item4.DAteInit = Datetime.parse(" 13/5/2015")
item4.DAteEnd = Datetime.parse("10/5/2015")
mylist.add(item4)
Dim item5
item5.status = 15
item5.DAteInit = Datetime.parse("10/5/2015")
item5.DAteEnd = Datetime.parse(" 13/5/2015")
mylist.add(item5)
One case:
If I choice the item 4, I need to get item 5 DataInit to set to DataEnd of item 4.
Other case
If I choice item 4, I need to change previous item with dateend (item 2) and set DateEnd of item2 to DateInit of item 4
If in a few words, If I select one I need previous and next Register with DateEnd different of Nothing
thx
hi what i wanted from some status to calculate, next, current and previous is:
Dim myList as new List(of Something)
Dim calcPrev As BLL.Something = mylist.Linea.TakeWhile(Function (x) x.Status < SomeStatusInserted And x.DateInit isnot nothing).LastOrDefault()
Dim calcCurr As BLL.Something = mylist.Linea.Where(Function (x) x.Status = SomeStatusInserted).FirstOrDefault()
Dim calcNext As BLL.Something = mylist.Linea.SkipWhile(Function (x) x.Status <= SomeStatusInserted And x.DateEnd isnot nothing).FirstOrDefault()
calcPrev.DateEnd = calcCurr.DateInit
calcCurr.DateEnd = calcNext.DateEnd

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 Group by and Sum syntax

i have the following vb.net LINQ Query
Dim q = From row In dx.AsEnumerable
Group row By G = New With {.Cat = row.Field(Of Integer)("catalogid")}.Cat,
New With {.Backorder = row.Field(Of Integer)("backorder")}.Backorder Into pg = Group
Select New With
{
.Cat = pg.Cat,
.Backorder = pg.Sum(Function(x) x.Backorder)
}
i have this datatable called dx
catalogid backorder
1 5
1 10
2 1
2 5
i want to Sum backorder column where catalogid is the same so the result is the following
catalogid backorder
1 15
2 6
in the Select new with part, what is wrong?
Try following...
var result = dx.AsEnumerable()
.GroupBy(row => row.Field<int>("catalogid"))
.Select(group => group.Sum(item => item.Field<int> ("backorder")).CopyToDataTable();
or
var result= from s in dx.AsEnumerable()
group s by s.Field<int>("catalogid") into grp
select new {
CatalogId= grp.Key,
Back Order = grp.Sum(r => r.Field<int>("backorder"))
};
DataTable datatbl = result.CopyToDataTable();