LINQ GroupBy Usage - vb.net

I am using this to get all my records ordered by date:
Dim myData = db.Tbl_Exercises.Where(Function(x) x.Exercise_Employee_ID).OrderBy(Function(x) x.Exercise_Create_Date)
I loop through with this:
For Each i As Tbl_Exercise In myData
data.Add(New Object() {i.Tbl_Exercise_Type.ExType_Desc, i.Exercise_Duration})
Next
How can I group by i.Tbl_Exercise_Type.ExType_Desc so I don't have duplicates?
I tried this:
Dim myData = db.Tbl_Exercises.Where(Function(x) x.Exercise_Employee_ID).GroupBy(Function(x) x.Exercise_Type_ID)
But, I don't know how to access the variables within my loop.
Thank you.
Edit:
<EmployeeAuthorize()>
Function ExercisePieChartData() As JsonResult
' get current employee's id
Dim db1 As EmployeeDbContext = New EmployeeDbContext
Dim user1 = db1.Tbl_Employees.Where(Function(e) e.Employee_EmailAddress = User.Identity.Name).Single()
Dim empId = user1.Employee_ID
Dim activityGroups = db.Tbl_Exercises.Where(Function(x) x.Exercise_Employee_ID = empId).GroupBy(Function(x) x.Exercise_Type_ID)
Dim data = New List(Of Object)
' columns (headers)
data.Add(New Object() {"Type", "Duration"})
' Iterate through each collection of activities, grouped by activity types
For Each group In activityGroups
' Iterate through each Tbl_Exercise in the group
For Each activity In group
' Do something with an individual element
data.Add(New Object() {activity.Tbl_Exercise_Type.ExType_Desc, activity.Exercise_Duration})
Next
Next
Return Json(data, JsonRequestBehavior.AllowGet)
End Function
Edit:
' Iterate through each collection of activities, grouped by activity types
For Each group In activityGroups
Dim type = ""
Dim duration = 0
' Iterate through each Tbl_Exercise in the group
For Each activity In group
' Do something with an individual element
type = activity.Tbl_Exercise_Type.ExType_Desc
duration += activity.Exercise_Duration
Next
data.Add(New Object() {type, duration})
Next

Note: Your example isn't entirely clear, so I'm going to make a few assumptions about what you are trying to do with my solution. I'll modify it as needed as more details are made available.
From what it looks like, for each employee you are trying to get a collection of types of exercise that they have performed, and with each type of exercise you would also like a collection of the lengths of time that they spent performing these activities.
I'm going to assume that a row in Tbl_Exercises looks like this:
Public Class Tbl_Exercise
Public Property Exercise_Employee_ID As Integer
Public Property Exercise_Type_ID As Integer
Public Property Exercise_Type_Desc As String
Public Property Exercise_Duration As Integer
End Class
The attempt you make at the end is pretty close, you just need to actually use the grouping once you've created it.
What the GroupBy method has returned you here is actually an IGrouping(Of Integer, Tbl_Exercise) where the key is the Exercise_Type_ID and the value is a collection of rows that are grouped by the key. To access them its pretty simple, you just have to think of the groupings as a sort of nested collection:
Dim activityGroups = db.Tbl_Exercises.Where(Function(x) x.Exercise_Employee_ID = employeeId).GroupBy(Function(x) x.Exercise_Type_ID)
' Iterate through each collection of activities, grouped by activity types
For Each group In activityGroups
' Iterate through each Tbl_Exercise in the group
For Each activity In group
' Do something with an individual element
Console.WriteLine(activity.Exercise_Duration)
Next
Next
Obviously, you may want to do something different than print out individual collections, you may want to data bind each grouping to their own DataGrid so that you can display them separately. You may want to sort each grouping by date, or you may only want the first element from each group. Once you understand how the groupings are structured, it should be pretty easy to accomplish exactly what you're looking for.
Edit:
If you're just looking to aggregate some field in the grouping, this is very simple to accomplish without needing to resort to looping over the results. Simply adding a call to Select on top of your GroupBy will let you project the results into your desired aggregate form:
Dim data = db.Tbl_Exercises _
.Where(Function(x) x.Exercise_Employee_ID = empId) _
.GroupBy(Function(x) x.Exercise_Type_Desc) _
.Select(Function(x)
Return New With {
' x.Key will give you access to the value that the grouping is grouped by
.Exercise_Type_Desc = x.Key,
.TotalDuration = x.Sum(Function(y) y.Exercise_Duration)
}
End Function)

Related

How to display multiple rows with the same ID

Was hoping for some help on this matter. Title pretty much explains what I'm trying to do.
I'm using MySql Database to read the data off the UserID for the purchases they have made, however I've hit a wall because I'm stuck on how to read multiple rows with the same ID.
For exmaple
1, TestProduct
1, TestProduct2
^^^ As there are more rows populated with the same ID how can I read multiple rows?
This is what I'm currently doing and I'm aware this is not working as it's only taking/finding the first ID result it finds and using that one however, I haven't needed to populate multiple rows. So I'm at a loss
SearchUser_COMMAND.Parameters.Add("#userid", MySqlDbType.VarChar).Value = Lbl_UserID.Text
Dim reader2 As MySqlDataReader
reader2 = SearchUser_COMMAND.ExecuteReader()
If reader2.Read() Then
Lbl_Active.Text = reader2(3)
Lbl_ProductName.Text = reader2(2)
Lbl_ProductExpire.Text = reader2(6)
End If
Any help on this matter would be much appreciated.
Thank very much in advance
You could make a class to hold your data, populate a List of those objects, then use some LINQ to iterate over them.
Private Class Data
Public Property Active As Boolean
Public Property Name As String
Public Property Expire As DateTime
End Class
Dim items As New List(Of Data)
If reader2.HasRows Then
While reader2.Read()
items.Add(New Data() With {.Name = CStr(reader2(2)), .Active = CBool(reader2(3)), .Expire = CDate(reader2(6))})
End While
Lbl_Active.Text = String.Join(Environment.NewLine, items.Select(Function(i) i.Active))
Lbl_ProductName.Text = String.Join(Environment.NewLine, items.Select(Function(i) i.Name))
Lbl_ProductExpire.Text = String.Join(Environment.NewLine, items.Select(Function(i) i.Expire))
End If
' maybe clear labels otherwise
For a reader with three items, this should result in something like this
Lbl_Active:
True
True
True
Lbl_ProductName:
name1
name2
name3
Lbl_ProductExpire:
date1
date2
date3
I took the liberty to assume the data types based on the names. You may have all strings in the database (you shouldn't) but then you should use strings.

Combining two list, only records with 1 specific unique property

I'm combining two lists in visual basic. These lists are of a custom object. The only record I want to combine, are the once with a property doesn't match with any other object in the list so far. I've got it running. However, the first list is just 1.247 records. The second list however, is just short of 27.000.000 records. The last time I successfully merged the two list with this restriction, it took over 5 hours.
Usually I code in C#. I've had a similar problem there once, and solved it with the any function. It worked perfectly and really fast. So as you can see in the code, I tried that here too. However it takes way too long.
Private Function combineLists(list As List(Of Record), childrenlist As List(Of Record)) As List(Of Record) 'list is about 1.250 entries, childrenlist about 27.000.000
For Each r As Record In childrenlist
Dim dublicate As Boolean = list.Any(Function(record) record.materiaalnummerInfo = r.materiaalnummerInfo)
If Not dublicate Then
list.Add(r)
End If
Next
Return list
End Function
The object Record looks like this ( I wasn't sure how to make a custom object in VB, and this looks bad, but it worked):
Public Class Record
Dim materiaalnummer As String
Dim type As String 'Config or prefered
Dim materiaalstatus As String
Dim children As New List(Of String)
Public Property materiaalnummerInfo()
Get
Return materiaalnummer
End Get
Set(value)
materiaalnummer = value
End Set
End Property
Public Property typeInfo()
Get
Return type
End Get
Set(value)
type = value
End Set
End Property
Public Property materiaalstatusInfo()
Get
Return materiaalstatus
End Get
Set(value)
materiaalstatus = value
End Set
End Property
Public Property childrenInfo()
Get
Return children
End Get
Set(value)
children = value
End Set
End Property
End Class
I was hoping that someone could point me in the right direction to shorten the time needed. Thank you in advance.
I'm not 100% sure what you want the output to be such as all differences or just ones from the larger list etc but I would definitely try do it with LINQ! Basically sql for vb.net data so would something similar to this:
Dim differenceQuery = list.Except(childrenlist)
Console.WriteLine("The following lines are in list but not childrenlist")
' Execute the query.
For Each name As String In differenceQuery
Console.WriteLine(name)
Next
Also side-note i would suggest not calling one of the lists "list" as it is bad practice and is a in use name on the vb.net system
EDIT
Please try this then let me know what results come back.
Private Function combineLists(list As List(Of Record), childrenlist As List(Of Record)) As List(Of Record) 'list is about 1.250 entries, childrenlist about 27.000.000
list.AddRange(childrenlist) 'combines both lists
Dim result = From v In list Select v.materiaalnummerInfo Distinct.ToList
'result hopefully may be a list with all distinct values.
End Function
Or Don't combine them if you dont want to.

Visual Basic: loaded parallel list boxes with text file substrings, but now items other than lstBox(0) "out of bounds"

The text file contains lines with the year followed by population like:
2016, 322690000
2015, 320220000
etc.
I separated the lines substrings to get all the years in a list box, and all the population amounts in a separate listbox, using the following code:
Dim strYearPop As String
Dim intYear As Integer
Dim intPop As Integer
strYearPop = popFile.ReadLine()
intYear = CInt(strYearPop.Substring(0, 4))
intPop = CInt(strYearPop.Substring(5))
lstYear.Items.Add(intYear)
lstPop.Items.Add(intPop)
Now I want to add the population amounts together, using the .Items to act as an array.
Dim intPop1 As Integer
intPop1 = lstPop.Items(0) + lstPop.Items(1)
But I get an error on lstPop.Items(1) and any item other than lstPop.Items(0), due to out of range. I understand the concept of out of range, but I thought that I create an index of several items (about 117 lines in the file, so the items indices should go up to 116) when I populated the list box.
How do i populate the list box in a way that creates an index of list box items (similar to an array)?
[I will treat this as an XY problem - please consider reading that after reading this answer.]
What you are missing is the separation of the data from the presentation of the data.
It is not a good idea to use controls to store data: they are meant to show the underlying data.
You could use two arrays for the data, one for the year and one for the population count, or you could use a Class which has properties of the year and the count. The latter is more sensible, as it ties the year and count together in one entity. You can then have a List of that Class to make a collection of the data, like this:
Option Infer On
Option Strict On
Imports System.IO
Public Class Form1
Public Class PopulationDatum
Property Year As Integer
Property Count As Integer
End Class
Function GetData(srcFile As String) As List(Of PopulationDatum)
Dim data As New List(Of PopulationDatum)
Using sr As New StreamReader(srcFile)
While Not sr.EndOfStream
Dim thisLine = sr.ReadLine
Dim parts = thisLine.Split(","c)
If parts.Count = 2 Then
data.Add(New PopulationDatum With {.Year = CInt(parts(0).Trim()), .Count = CInt(parts(1).Trim)})
End If
End While
End Using
Return data
End Function
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim srcFile = "C:\temp\PopulationData.txt"
Dim popData = GetData(srcFile)
Dim popTotal = 0
For Each p In popData
lstYear.Items.Add(p.Year)
lstPop.Items.Add(p.Count)
popTotal = popTotal + p.Count
Next
' popTotal now has the value of the sum of the populations
End Sub
End Class
If using a List(Of T) is too much, then just use the idea of separating the data from the user interface. It makes processing the data much simpler.

LINQ returning EnumerableRowCollection

I need to get the name of fee according to a specific id, I am trying to get the column directly instead of the whole row.
I have the following code
Dim fee As EnumerableRowCollection(Of String) = (From row In dtFee.AsEnumerable _
Where row.Field(Of SqlInt32)("idFee").Value = idFee _
Select row.Field(Of SqlString)("name").Value)
but when i access it through :
lblIdFee.Text = fee.FirstOrDefault
i get an exception of invalid convertion but if a declare the results as
Dim fee = ...
And if I get the result from fee through the toString() method because i do not have available FirstOrDefault i get:
System.Data.EnumerableRowCollection`1[System.String]
I don't know if a missing some reference library.
Change your variable type to
Dim fee As IEnumerable(Of String) ...
You are projecting to a collection of string values, not a collection of DataRows

VB.Net Sort a List using property name

I have a list containing Client objects. I want to sort the list by using it's property name ascending or descending, which I already have in the code in viewstate : ViewState("PropertyName") and ViewState("Order")
Dim objList As List(Of Client) = Session("ClientList")
objList.Sort(ViewState("PropertyName") + " " + ViewState("Order"))
datarepeater.datasource = objList
How can I achieve this ?
Normally, if you knew the property with which you wanted to sort, you would be able to just do something like this:
clients.Sort(Function(x, y) x.Name.CompareTo(y.Name))
In the above example, I am, of course sorting on the Name property (I don't know what properties the Client class has, I'm just using it as an example.
However, since you don't know which property you are going to use until run-time, you'll need to do something more complicated. If you really want to use the actual property names of the class, you could use Reflection to retrieve the value of the property dynamically, for instance:
clients.Sort(Function(x, y)
Dim xProperty As PropertyInfo = x.GetType().GetProperty(ViewState("PropertyName").ToString)
Dim yProperty As PropertyInfo = y.GetType().GetProperty(ViewState("PropertyName").ToString)
Dim xValue As Object = xProperty.GetValue(x)
Dim yValue As Object = yProperty.GetValue(y)
Return xValue.ToString().CompareTo(yValue.ToString())
End Function)
To reverse the sort order, just multiply the return value by -1, or switch which object you are comparing to what. For instance:
If ViewState("Order") = "Ascending" Then
Return xValue.ToString().CompareTo(yValue.ToString())
Else
Return yValue.ToString().CompareTo(xValue.ToString())
End If
You can also use Linq (using the same general method proposed by Steven Doggart):
sorted = lst.OrderBy(Function(x) x.GetType().GetProperty(_strColumnName01).GetValue(x)). _
ThenBy(Function(x) x.GetType().GetProperty(_strColumnName02).GetValue(x)).ToList()
In my particular example, I needed to sort by two columns, but for just a single column:
sorted = lst.OrderBy(Function(x) x.GetType().GetProperty(_strColumnName01).GetValue(x)).ToList()