LINQ returning EnumerableRowCollection - vb.net

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

Related

Function to lookup arbitrary column in DataTable and return value in another arbitrary column

I'm trying to write a generic function which can be used to look up an arbitrary value in an arbitrary column in an arbitrary DataTable, and return the corresponding value in another arbitrary column in the same DataTable. I'm not concerned with multiple values or multiple matches; the data is organised such that they don't occur anyway, all I want is for it to return the first match if it exists, or nothing if it doesn't.
I'm basing the code on this very simple example :
Private Function TableLookup(dtb As DataTable, lookupFieldName As String, lookupFieldValue As Integer, returnFieldName As String) As String
Dim result As String
Dim matches = From row In dtb Let lookup = row.Field(Of Integer)(lookupFieldName) Where lookup = lookupFieldValue
If matches.Any Then result = matches.First().row.Field(Of String)(returnFieldName)
Return result
End Function
But obviously that only works if the lookupField is an Integer field and the returnField is a String field. Because the function needs to handle arbitrary columns, those columns could have arbitrary DataTypes? And the value being returned is also arbitrary (could be an Integer, could be a String... etc.)
Obviously I can determine what the DataTypes are for each column easily enough :
Dim lookupFieldType As Type = dtb.Columns("lookupFieldName").DataType
Dim returnFieldType As Type = dtb.Columns("returnFieldName").DataType
But that's still no use as row.Field(Of T) is strongly-typed; I can't use a variable to specify the DataType :
Dim matches = From row In dtb Let lookup = row.Field(Of lookupFieldType)(lookupFieldName) Where lookup = lookupFieldValue
If matches.Any Then result = matches.First().row.Field(Of returnFieldType)(returnFieldName)
Have a feeling I'm going about this in completely the wrong way to begin with but it seems like there should be a straightforward way of looking up arbitrary columns in data tables (otherwise what's the point in having them, right?)
Any suggestions?
If you will know what the types of both columns will be when you call the method, you can make it generic like this:
Private Function TableLookup(Of TKey As IEquatable(Of TKey), TResult)(table As DataTable,
keyColumnName As String,
key As TKey,
resultColumnName As String) As TResult
Dim row = table.AsEnumerable().FirstOrDefault(Function(dr) dr.Field(Of TKey)(keyColumnName).Equals(key))
Return If(row Is Nothing, Nothing, row.Field(Of TResult)(resultColumnName))
End Function
This method might be called like so:
Dim name As String = TableLookup(Of Integer, String)(myDataTable,
"Id",
id,
"Name")
If you won't know what the column types are, you could use something like this:
Private Function TableLookup(table As DataTable,
keyColumnName As String,
key As Object,
resultColumnName As String) As Object
Dim keyType = key.GetType()
If keyType IsNot table.Columns(keyColumnName).DataType Then
Return Nothing
End If
Dim filterExpression As String
If keyType Is GetType(String) Then
filterExpression = $"{keyColumnName} = '{key}'"
ElseIf keyType Is GetType(Date) Then
filterExpression = $"{keyColumnName} = #{key:M/dd/yyyy h:mm:ss tt}#"
Else
filterExpression = $"{keyColumnName} = {key}"
End If
Dim row = table.Select(filterExpression).FirstOrDefault()
Return If(row Is Nothing, Nothing, row(resultColumnName))
End Function

VB Running grouped LINQ into DataTable

I'm trying to run a LINQ to datatable, that normally works for me. This time I'm trying something new to me, Grouping and taking the first row from each group. questionGroups is of type IEnumerable(Of Object). When I open the query in the debugger I see a collection of DataRows, each with an ItemArray of the values I expect in the columns. How do I run this query to a DataTable, or select the two columns I want and run those into a Dictionary?
Public member 'ToTable' on type 'WhereSelectEnumerableIterator(Of
VB$AnonymousType_0(Of Object,IEnumerable(Of Object)),Object)' not
found.
Dim answerGroup As String = "QuestionSortKey"
Dim answerNo As String = "AnswerNo"
Dim surveyDefinitionNo As String = "Pk_SurveyDefinitionNo"
Dim query = _
From rows In surveyAnswerKeys.Rows _
Where rows(answerNo) IsNot Nothing _
Order By Guid.NewGuid() _
Group By questionSortKey = rows(answerGroup) _
Into questionGroups = Group _
Select questionGroups.First()
Dim randomAnswerNos As DataTable = query.ToTable
Edit: This question is an offshoot of this one: VB LINQ - Take one random row from each group

LINQ GroupBy Usage

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)

How do I get a value from Datatable after Select Method in VB.net

I'm trying to get value from Datatable after Select Method.
Select Method should return only one record.
Dim y As DataTable = CType(HttpContext.Current.Session("tb"), DataTable)
Dim products = y.[Select]("StnID" = "'" + stnID + "'")
Dim size = DirectCast(Products(0)("Shape"), String)
if I replace products with y--(DirectCast(y.Rows(0)("Shape"), String), it works.(which is before Select method, so it's useless)
products is datarow object and I can't seem to get the value same way as datatable.
How do I get value from datarows?
The select Method of the datatable object returns a collection of rows so you have to iterate throw the rows to get desired value;

VB.Net Linq InvalidCastException for Group By

I have a Linq Group By query that works. Here's the query:
Dim query = From fb As Feedback In lst Where fb.Seller.login_name.ToLower = UserName.ToLower
Order By fb.transaction_id Descending, fb.creation Descending _
Group fb By fb.transaction_id _
Into Group
I don't like working with anonymous types so I'm trying to delare the result and am hitting an InvalidCastException.
Here's the type information:
Debug.Print(query.GetType.ToString)
Returns:
System.Linq.GroupedEnumerable`4[Feedback,System.Nullable`1[System.Int32],Feedback,VB$AnonymousType_0`2[System.Nullable`1[System.Int32],System.Collections.Generic.IEnumerable`1[Feedback]]]
and the inner type:
Debug.Print(item.GetType.ToString)
Returns:
VB$AnonymousType_0`2[System.Nullable`1[System.Int32],System.Collections.Generic.IEnumerable`1[Feedback]]
So, armed with this information, here's the declaration used:
Dim query As IEnumerable(Of IGrouping(Of Int32?, IEnumerable(Of Feedback)))
Here's the error returned:
Unable to cast object of type 'System.Linq.GroupedEnumerable`4[Feedback,System.Nullable`1[System.Int32],Feedback,VB$AnonymousType_0`2[System.Nullable`1[System.Int32],System.Collections.Generic.IEnumerable`1[Feedback]]]' to type 'System.Collections.Generic.IEnumerable`1[System.Linq.IGrouping`2[System.Nullable`1[System.Int32],System.Collections.Generic.IEnumerable`1[Feedback]]]'.
The Ugly solution is to define an object and step through the results as follows:
Class FeedbackDataItem
Sub New(ByVal transaction_id As Integer)
_transaction_id = transaction_id
_feedbacks = New Feedbacks(Of Feedback)
End Sub
Private _transaction_id As Integer
Public Property transaction_id() As Integer
Get
Return _transaction_id
End Get
Set(ByVal value As Integer)
_transaction_id = value
End Set
End Property
Private _feedbacks As Feedbacks(Of Feedback)
Public Property Feedbacks() As Feedbacks(Of Feedback)
Get
Return _feedbacks
End Get
Set(ByVal value As Feedbacks(Of Feedback))
_feedbacks = value
End Set
End Property
End Class
And to load up the collection:
Dim FeedbackData As New List(Of FeedbackDataItem)
For Each item In query
Dim fbi As New FeedbackDataItem(item.transaction_id)
fbi.Feedbacks.AddRange(item.Group)
FeedbackData.Add(fbi)
Next
I'm stumped as to the reason why I'm getting this error. It would be nicer to just define the results and retrun them rather that man-handle the data. Am I missing something?
The result of your query is an IEnumerable(Of anonymous type). If you don't want it to be anonymous, you need to strongly type it.
One way is to use the extension method syntax for the group by:
Dim feedbacks As IEnumerable(Of Feedback) =
From fb As Feedback In lst Where fb.Seller.login_name.ToLower = username.ToLower
Order By fb.transaction_id Descending, fb.creation Descending
Dim grouped As IEnumerable(Of IGrouping(Of Integer?, Feedback)) =
feedbacks.GroupBy(Function(fb) fb.transaction_id)
Put a select at the end of the statement to get it into whatever type you want.
Dim query = From fb As Feedback In lst _
Where fb.Seller.login_name.ToLower = UserName.ToLower
Order By fb.transaction_id Descending, fb.creation Descending _
Group gr By fb.transaction_id
Into Group
Select new FeedbackDataItem with {
.transaction_id = gr.transaction_id, _
.FeedBacks = ...
}