VB.Net Linq InvalidCastException for Group By - vb.net

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 = ...
}

Related

Unable to cast object error when attempting to return key/value pairs by querying a datacontext

I am attempting to get data from a datacontext. Normally, I've had no problems doing it, but I'm having trouble trying to return a list of key/value pairs.
Basically I'm attempting to grab all unique names from a table as the key column and the number of times they appear in the table as the value column.
My data would look like this:
apple 5
banana 1
dragonfruit 3
.
.
.
The full error message is:
Unable to cast object of type 'System.Data.Linq.DataQuery1[VB$AnonymousType_32[System.String,System.Int32]]' to type 'System.Collections.Generic.List`1
The code I'm using is this:
Dim indicators As List(Of Object)
Public Sub GetIndicatorData()
Using context = new A_DataContext
indicators = (From p In chartdata Group p By __groupByKey1__ = p.INDK8R Into g = Group
Select New With {.name = __groupByKey1__, .count = g.Count()}).AsEnumerable()
indDataSource = indicators
End Sub
but I've also tried to:
Return indicators as a list
Return indicators as an enumerable.
Use a class to encapsulate your anonymous type
Public Class NameAndCount
Public ReadOnly Property Name As String
Public ReadOnly Property Count as Integer
Public Sub New(name As String, count As Integer)
Me.Name = name
Me.Count = count
End Sub
End Class
' ...
Private indicators As IEnumerable(Of NameAndCount)
Public Sub GetIndicatorData()
Using context = new A_DataContext
indicators = From p In chartdata Group p By __groupByKey1__ = p.INDK8R Into g = Group
Select New NameAndCount(__groupByKey1__, g.Count())
indDataSource = indicators.ToList()
End Using
End Sub
Figured it out. I changed the declaration of indicators to simply be an object then removed the .enumerable (or .toList) from the query result.
Thank you for the consideration and time.

Populate combobox with a class - data from a sql query

Iam frustrated to accomplish a simply population of a combobox, below I have added one new item to the combobox everything seems to be fine.
Question 1 : But how could I get there the information's from the sql query, without having to add it all manually. [ I suppose by simply adding Items.Add line to the while loop ], but here is another thing - The start data is a database record previewer, So it is Simple Name Simple Surname [/] with a dropdown menu with all customers,
Question 2. The data I get from the mysql result is id,name,surname how to point it as the current displayed name/surname and for later purposes - like a update get the selected id of another customer from the dropdown? I don't need the insert command or code just need the information how can I get the id from a selection. If something is unclear don't hesitate to ask.
'Select the first item ( the selection would be a ID of the customer which isn't the index at all)
ComboBoxEdit.SelectedIndex = 0
Form
Dim properties As DevExpress.XtraEditors.Repository.RepositoryItemComboBox = _
ComboBoxEdit.Properties
properties.Items.Add(New Customers(1, "Ta", "t").ToString)
'Select the first item ( the selection would be a ID of the customer which isn't the index at all)
ComboBoxEdit.SelectedIndex = 0
Getting customers into Class Customers ( I guess its that way I need to do it )
Public Function init_customers()
' Create a list of strings.
Dim sql As String
Dim myReader As MySqlDataReader
con.Open()
sql = "select * from customers"
'bind the connection and query
With cmd
.Connection = con
.CommandText = sql
End With
myReader = cmd.ExecuteReader()
While myReader.Read()
list.Add(New Customers(myReader.GetInt64(0), myReader.GetString(1), myReader.GetString(2)))
End While
con.Close()
'Return list
End Function
The class customers
Public Class Customers
Public Sub New(ByVal id As Integer, ByVal name As String, ByVal surname As String)
Me.ID = id
Me.Imie = name
Me.Nazwisko = surname
End Sub
#Region "Get/Set"
Public Property ID() As Integer
Get
Return Me._id
End Get
Set(ByVal value As Integer)
Me._id = value
End Set
End Property
Public Property Imie() As String
Get
Return Me._imie
End Get
Set(ByVal value As String)
Me._imie = value
End Set
End Property
Public Property Nazwisko() As String
Get
Return Me._nazwisko
End Get
Set(ByVal value As String)
Me._nazwisko = value
End Set
End Property
Public ReadOnly Property Surname() As Decimal
Get
Return Me._nazwisko
End Get
End Property
Public Overrides Function ToString() As String
Return _imie + " " + _nazwisko
End Function
#End Region
Private _id As Integer
Private _imie As String
Private _nazwisko As String
End Class
=========== Edit 2 =====================
Ok my dropdown is populated
As I said this is a record preview form so how can I get now the default selection of the combobox.
The thing is I pass there a string
Form1.GridView1.GetRowCellValue(Form1.GridView1.FocusedRowHandle, "Wystawione_na").ToString()
This code returns me SimpleName SimpleSurname - as a one string
Same method is applied to combobox display.
How can I get now the Id of the item, it has to somehow compared and returning a id so it could be set cmbx.SelectedIndex = 0 as the id of customer selection
I take a simpler route, not sure if it's the most efficient though:
Dim Conn As New SqlConnection
Conn.ConnectionString = sYourConnectionString
Conn.Open()
Dim da As New SqlDataAdapter("select * from customers", Conn)
Dim ds As New DataSet
da.Fill(ds, sSql)
cmbxCustomers.DataSource = ds.Tables(0)
cmbxCustomers.ValueMember = "ID" 'or whatever column you want
Of course, I normally use a wrapper class to do almost all of the above code, but the last two lines apply to part of your question.
As far as retrieving that data later based on the ID selected: well you can simply use a DataSet (or DataTable, my preference) class member variable so the data is stored the from the initial load and iterate through the table looking for the row that matches the ID you're wanting information from.

InvalidCastException when trying to Sum datatable rows with LINQ

Hi i am trying to sum all my datatable values to one row. but i retrieve a InvalidCastException:
Failed to convert an object of
typeWhereSelectEnumerableIterator2[System.Linq.IGrouping2[System.Object,System.Data.DataRow],VB$AnonymousType_0`4[System.Object,System.Double,System.Decimal,System.Decimal]]
to type System.Data.DataTable.
SQL Datatypes:
NAME_AGE string
LON money
sal_tjformon money
sal_sjuklon money
Private Function GroupByName(dataTable As DataTable) As DataTable
Dim result = dataTable.AsEnumerable().GroupBy(
Function(row) row.Item("NAME_AGE")).Select(Function(group) New With {
.Grp = group.Key,
.LON = group.Sum(Function(r) Decimal.Parse(r.Item("LON"))),
.sal_tjformon = group.Sum(Function(r) Decimal.Parse(r.Item("sal_tjformon"))),
.sal_sjuklon = group.Sum(Function(r) Decimal.Parse(r.Item("sal_sjuklon")))
})
Return result
The LINQ statement returns an IEnumerable(Of <anonymous_type>). There are two problems with this. First of all, your function returns a DataTable, which your object definitely is not. Secondly of all, you can't return an anonymous type from a function call.
If you want to return the select result, you have to create an explicit type (a class) and return the IEnumerable(Of MyType), like in the code below. I strongly advice to set an explicit type to the Grp property (like String?).
Class GroupNameAgeResult
Public Property Grp As Object
Public Property LON As Decimal
Public Property sal_tjformon As Decimal
Public Property sal_sjuklon As Decimal
End Class
Private Function GroupByName(dataTable As DataTable) As IEnumerable(Of GroupNameAgeResult)
Dim result = dataTable.AsEnumerable().GroupBy(Function(row) row.Item("NAME_AGE")) _
.Select(Function(grp) New GroupNameAgeResult() With
{.Grp = grp.Key,
.LON = grp.Sum(Function(r) Decimal.Parse(r.Item("LON").ToString)),
.sal_tjformon = grp.Sum(Function(r) Decimal.Parse(r.Item("sal_tjformon").ToString)),
.sal_sjuklon = grp.Sum(Function(r) Decimal.Parse(r.Item("sal_sjuklon").ToString))})
Return result
End Function
If you want to return a DataTable, you can define this, loop over the groups and add a row. You can return afterwards the result. See example code below.
Private Function GroupByName(dataTable As DataTable) As DataTable
Dim result As New DataTable()
result.Columns.Add("Grp", GetType(Object))
result.Columns.Add("LON", GetType(Decimal))
result.Columns.Add("sal_tjformon", GetType(Decimal))
result.Columns.Add("sal_sjuklon", GetType(Decimal))
For Each grp In dataTable.AsEnumerable().GroupBy(Function(row) row.Item("NAME_AGE"))
Dim row As DataRow = result.NewRow()
row.Item("Grp") = grp.Key
row.Item("LON") = grp.Sum(Function(r) Decimal.Parse(r.Item("LON").ToString))
row.Item("sal_tjformon") = grp.Sum(Function(r) Decimal.Parse(r.Item("sal_tjformon").ToString))
row.Item("sal_sjuklon") = grp.Sum(Function(r) Decimal.Parse(r.Item("sal_sjuklon").ToString))
result.Rows.Add(row)
Next
Return result
End Function
Last but not least. I strongly advice you to turn on "Option strict" (you can set this in the project properties -> Compile). You'll notice many more (possible) errors with your code (even the small function from this question).

VB.Net I'm trying to write an extension for a generic linq search, however I'm not sure how to return more than one result 0.o

I'm a bit new to vb.net and used to working in perl, this is what I'd like to do.
I wanted something similar to DBIX::Class::Resultset's search (from cpan) in my vb.net project, so that I can give my function a hash containing keys and values to search on a table.
Currently it returns a single matching result of type T where I want it to return all results as a data.linq.table(of T)
How should I alter my expression.lambda so that I can say table.Select(Predicate) to get a set of results? After that I think it should be as simple as saying results.intersect(result) instead of Return test.
Any help will be very much appreciated.
Thanks in advance
-Paul
<System.Runtime.CompilerServices.Extension()> _
Public Function Search(Of T As Class)(ByVal context As DataContext, _
ByVal parameters As Hashtable) As T
Dim table = context.GetTable(Of T)()
Dim results As Data.Linq.Table(Of T)
For Each Parameter As DictionaryEntry In parameters
Dim column As Object = Parameter.Key
Dim value As String = Parameter.Value
Dim param = Expression.Parameter(GetType(T), column)
Dim Predicate = Expression.Lambda(Of Func(Of T, Boolean)) _
(Expression.[Call](Expression.Convert(Expression.Property(param, column), _
GetType(String)), GetType(String).GetMethod("Contains"), _
Expression.Constant(value)), New ParameterExpression() {param})
Dim test = table.First(Predicate)
Return test
' result.intersect(result)
Next
'Return results
End Function
This works assuming you want an "AND" conjunction between predicates
For instance:
Dim h = New System.Collections.Hashtable
h.Add("FieldA", "01 5149")
h.Add("FieldB", "WESTERN")
Dim t = (New DBDataContext).Search(Of DBrecord)(h)
Debug.Print(t.Count.ToString)
Would return those records where fieldA matched AND fieldb matched.
If you wanted OR, DiceGuy's right, use UNION.
Here's the search...
Note, I used STARTSWITH instead of contains because it's alot faster for large sets
You can always change it back.
<System.Runtime.CompilerServices.Extension()> _
Public Function Search(Of T As Class)(ByVal context As DataContext, _
ByVal parameters As Hashtable) As IQueryable(Of T)
Dim table = context.GetTable(Of T)()
Dim results As IQueryable(Of T) = Nothing
For Each Parameter As DictionaryEntry In parameters
Dim column = DirectCast(Parameter.Key, String)
Dim value As String = DirectCast(Parameter.Value, String)
Dim param = Expression.Parameter(GetType(T), column)
Dim Predicate = Expression.Lambda(Of Func(Of T, Boolean)) _
(Expression.[Call](Expression.Convert(Expression.Property(param, column), _
GetType(String)), GetType(String).GetMethod("StartsWith", New Type() {GetType(String)}), _
Expression.Constant(value)), New ParameterExpression() {param})
Dim r = table.Where(Predicate)
If results Is Nothing Then
results = r
Else
results = results.Intersect(r)
End If
Next
Return results
End Function
Well, for starters let's change the return type to Data.Linq.Table(Of T).
Then instead of table.First(Predicate), try table.Where(Predicate)
Finally 'Intersect' will only give you results that contain all your parameters. If that's what you want, then fantastic! If not, then try 'Union' instead.
Let me know where that gets you and we can work from there.

Convert anonymous type to strong type for grouping query?

I've pieced together some information from other posts but I'm stuck.
The first part works fine. Basically I query the database using LINQ and then I loop through the results generating a report.
Dim results As System.Linq.IQueryable(Of Bud900Reports.tblBud_CONNECT)
If options.Type = reportType.Organization Then
results = From p In db.tblBud_CONNECTs _
Where p.TableOldName = "tblFY" & options.FiscalYear And p.DEPTID = options.GroupFilter _
Order By p.SOURCE_CODE, p.ROW_CODE_903 _
Select p
ElseIf options.Type = reportType.Division Then
'Here is my problem line
End If
For each result in results
'loop through code generating report
Next
Now instead of having three function with alot of dupelicate code, if the reportType is Division I want to run this query and put it into the results set.
results = (From p In db.tblBud_CONNECTs _
Where p.TableOldName = "tblFY" & options.FiscalYear And p.DIVISION_CODE = options.GroupFilter _
Group p By p.DIVISION_CODE, p.SOURCE_CODE, p.ROW_CODE_903 Into _
OrigEft = Sum(p.OrigEft), OrigAmt = Sum(p.OrigAmt), ABEft = Sum(p.ABEft), ABAmt = Sum(p.ABAmt) _
Order By DIVISION_CODE, SOURCE_CODE, ROW_CODE_903 _
Select DIVISION_CODE, SOURCE_CODE, ROW_CODE_903, OrigEft, OrigAmt, ABEft, ABAmt)
It's the same data just grouped and summed up. But it comes through as an anonymous type. I tried to do "select new tblBud_CONNECTs with {.DIVISION_CODE = DIVISION_CODE, ...}" but it gave me the error of "Explicit construction of entity type tblBud_CONNECTs is not allowed".
How can I do what I want? It seems that I should be able to. Thanks.
For completeness I'll answer my own question.
First I created a class to hold the results.
Private Class results
Private mDivCode As String
Public Property DivCode() As String
Get
Return mDivCode
End Get
Set(ByVal value As String)
mDivCode = value
End Set
End Property
Private mSourceCode As Short
Public Property SourceCode() As Short
Get
Return mSourceCode
End Get
Set(ByVal value As Short)
mSourceCode = value
End Set
End Property
Private mRowCode As Short
Public Property RowCode() As Short
Get
Return mRowCode
End Get
Set(ByVal value As Short)
mRowCode = value
End Set
End Property
Private mOrigEft As Decimal
Public Property OrigEft() As Decimal
Get
Return mOrigEft
End Get
Set(ByVal value As Decimal)
mOrigEft = value
End Set
End Property
Private mOrigAmt As Decimal
Public Property OrigAmt() As Decimal
Get
Return mOrigAmt
End Get
Set(ByVal value As Decimal)
mOrigAmt = value
End Set
End Property
Private mABEft As Decimal
Public Property ABEft() As Decimal
Get
Return mABEft
End Get
Set(ByVal value As Decimal)
mABEft = value
End Set
End Property
Private mABAmt As Decimal
Public Property ABAmt() As Decimal
Get
Return mABAmt
End Get
Set(ByVal value As Decimal)
mABAmt = value
End Set
End Property
End Class
Then I set a variable to hold the results.
Dim results As System.Linq.IQueryable(Of results)
Then I made my linq query fill up the results like so.
results = (From p In db.tblBud_CONNECTs _
Where p.TableOldName = "tblFY" & options.FiscalYear And p.DEPTID = options.GroupFilter _
Order By p.SOURCE_CODE, p.ROW_CODE_903 _
Select New results With {.DivCode = p.DIVISION_CODE, .SourceCode = p.SOURCE_CODE.Value, .RowCode = p.ROW_CODE_903.Value, _
.OrigEft = p.OrigEft.Value, .OrigAmt = p.OrigAmt.Value, .ABEft = p.ABEft.Value, .ABAmt = p.ABAmt.Value})
That's how I ended up doing what I wanted.
The solution is to create a collection of the Division instances and populate this collection using 'results'.
Then you can use the Attach method to enable change tracking for these entities.