Grouping by multiple columns - vb.net

I am not sure if the title is misleading but I wasn't sure how to summarise this one.
I have a table in an SQL DB where a record exists as below:
I would like to display the measurement values of this item in a gridview as below:
I thought about selecting the target values to a list (and the same for the actual values) as below:
Dim cdc As New InternalCalibrationDataContext
Dim allTargetvalues = (From i In cdc.int_calibration_records
Where i.calibration_no = Request.QueryString(0) And
i.calibration_date = Request.QueryString(1)
Select i.measure1_target, i.measure2_target, i.measure3_target).ToList()
Then joining the lists together in some way although I am unsure of how I could join the lists or even if this is the correct approach to be taking?

Well, let me first say that measure1_target, measure2_target, etc. is almost always indicative of bad database design. These should probably be in another table as the "many" end of a 1-to-many relationship with the table you posted. So to answer one of your questions: No, this is not the correct approach to be taking.
With the structure of your table in its current state, your best option is probably something like this:
Dim cdc As New InternalCalibrationDataContext
Dim allTargetValues As New List(Of Whatever)
For Each targetValue In (From i In cdc.int_calibration_records
Where i.calibration_no = Request.QueryString(0) AndAlso
i.calibration_date = Request.QueryString(1)
Select i)
allTargetValues.Add(New Whatever With {.MeasureNumber = 1,
.Target = targetValue.measure1_target,
.Actual = targetValue.measure1_actual })
allTargetValues.Add(New Whatever With {.MeasureNumber = 2,
.Target = targetValue.measure2_target,
.Actual = targetValue.measure2_actual })
allTargetValues.Add(New Whatever With {.MeasureNumber = 3,
.Target = targetValue.measure3_target,
.Actual = targetValue.measure3_actual })
Next
The Whatever class would look like this:
Public Class Whatever
Public Property MeasureNumber As Integer
Public Property Target As Integer
Public Property Actual As Integer
End Class

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.

Linq Entity With Return Function

I have 2 tables:
Room_Type (ID_Room_Type, Name_Room_Type)
Room_Room (ID_Room_Type, Number_Room)
How to place a new column Temp in the RoomType table that will take its value as the sum of the rooms from the Room_Room table.
This code does not work :
Private Sub SimpleButton1_Click(sender As Object, e As EventArgs) Handles SimpleButton1.Click
Dim Db As New HotelEntities
Dim s = (From I In Db.Room_Type Select I.ID_Room_Type, I.Name_Room_Type, ColumnTemp = GetTotal_Room(I.ID_Room_Type)).ToList()
Me.DataGridView1.DataSource = s
End Sub
Private Function GetTotal_Room(id_room_type As Int32) As Int32
Dim Db As New HotelEntities
Return (From I In Db.Room_Room Where I.ID_Room_Type = id_room_type).Count
End Function
Message Error :LINQ to Entities does not recognize the method 'Int32
GetTotal_Room(Int32)' method, and this method cannot be translated
into a store expression.
I had a similar problem and I solved it by extracting the list first and then applying linq over it. You can try by altering your code as below.
Dim Db As New HotelEntities
Dim roomTypeList = Db.Room_Type.ToList()
Dim s = (From I In roomTypeList
Select I.ID_Room_Type, I.Name_Room_Type,
ColumnTemp = GetTotal_Room(I.ID_Room_Type)).ToList()
Me.DataGridView1.DataSource = s
Entity Framework will generate Linq query expressions to valid sql query.
As error message explain EF doesn't know how to convert your method GetTotal_Room to valid sql query.
Instead you can get required result from one query, without extra function.
From roomRoom In db.Room_Room
Join roomType In db.Room_Type On roomRoom.ID_Room_Type Equals roomType.ID_Room_Type
Select New With { roomRoom.ID_Room_Type, roomType.Name_Room_Type } Into roomtypes
Group roomtypes By roomtypes Into grouptypes
Select New With
{
Id = grouptypes.Key.ID_Room_Type,
Name = grouptypes.Key.Name_Room_Type,
Total = grouptypes.Count()
}
As additional notice, I don't know context of your application, but based on given sample, I think you can remove some prefixes and suffixes in table and column names
RoomType
Id
Name
Room
TypeId -- reference to RoomType.Id

How to bind a DataGridView to a list of custom classes

I have a list of custom classes that I am building using a TableAdapter.
I want to add these to a DataGridView binding certain columns only.
I have tried the code below to fill and bind the data:
lAllBookings = (From r As DataRow In BookingsTableAdapter1.GetDataWithItems().Rows
Select New Booking With {.bookingID = r.Item("BookingID"), _
.itemID = r.Item("ItemID"), _
.bookedOutDate = r.Field(Of DateTime?)("BookedOutDate"), _
.bookedInDate = r.Field(Of DateTime?)("BookedInDate"), _
.identType = r.Item("IdentType"), _
.identString = r.Item("IdentString"), _
.image = r.Item("Image"), _
.complete = r.Item("Complete"), _
.notes = r.Item("Notes"), _
.itemName = r.Item("ItemName"), _
.itemBC = r.Item("ItemBarcode")}).ToList
dgvBookings.Columns("BookingID").DataPropertyName = "bookingID"
dgvBookings.Columns("ItemIdent").DataPropertyName = "itemName"
dgvBookings.Columns("BookedOut").DataPropertyName = "bookedOutDate"
dgvBookings.Columns("IdentString").DataPropertyName = "identString"
dgvBookings.DataSource = lAllBookings
Now when I do this I get the correct number of rows but all fields are blank.
I've run through a few questions on SO and a few tutorials but they all seem to do things slightly different to what I need.
Is there a way I can fill the DataGridView using my list of items?
I'd rather avoid using a DataSet if I can as I've built a lot of other code on this List<Of Class> type.
Edit - Here is the Class Booking declaration:
Public Class Booking
Public bookingID As Integer
Public itemID As Integer
Public itemName As String
Public itemBC As String
Public identType As Short
Public identString As String
Public image As Byte()
Public complete As Boolean
Public notes As String
Public bookedInDate As DateTime?
Public bookedOutDate As DateTime?
End Class
I know is really late for you but maybe it can help someone.
I had the same problem that you but using vb.net.
Finally I found the solution: Your class was not exposing properties but variables. The binding system looks for properties and can't find them so you get the empty rows.
Try to complete your class adding {get; set;} (or the Property attribute if using vb.net) and everything will work.
Hope it can be helpful.
I am not sure that you are getting in IAllBookings all the information you want. In any case, you are not passing it rightly to the dgvBookings. Here you have a couple of small codes to help you to understand how this works better:
dgvBookings.Columns.Clear()
Dim newTable As New DataTable
newTable.Columns.Add("Column1")
newTable.Columns.Add("Column2")
newTable.Columns.Add("Column3")
newTable.Rows.Add("1", "2", "3")
newTable.Rows.Add("1", "2", "3")
newTable.Rows.Add("1", "2", "3")
dgvBookings.DataSource = newTable
The newTable emulates perfectly the DataGridView structure (columns & rows) and thus it can be given as a DataSource directly.
Unlikely the case of a simple List:
dgvBookings.Columns.Clear()
Dim newList = New List(Of String)
newList.Add("1")
newList.Add("2")
newList.Add("3")
dgvBookings.DataSource = newList
You are providing less information than expected (1D vs. the expected 2D) and thus the result is not the one you want. You need to provide more information; for example: instead of relying on DataSource, you might add the rows one by one:
dgvBookings.Columns.Add("Column1", "Column1")
For Each item In newList
dgvBookings.Rows.Add(item)
Next
I hope that this answer will help you to understand better how to deal with DataGridView and with the different data sources.
-- UPDATE
Row by row option applied to your specific case.
For Each item As Booking In lAllBookings
With item
dgvBookings.Rows.Add(.bookingID.ToString(), .itemID.ToString(), .bookedOutDate.ToString(), .identString.ToString())
End With
Next

How do you assign values to structure elements in a List in VB.NET?

I have a user-defined structure in a list that I am trying to change the value for in an individual element within the list of structures. Accessing the element is not a problem. However, when I try to update the value, the compiler complains:
"Expression is a value and therefore cannot be the target of the
assignment"
For example:
Public Structure Person
Dim first as String
Dim last as String
Dim age as Integer
End Structure
_
Public Sub ListTest()
Dim newPerson as Person
Dim records as List (Of Person)
records = new List (Of Person)
person.first = "Yogi"
person.last = "bear"
person.age = 35
records.Add(person)
records(0).first = "Papa" ' <<== Causes the error
End Sub
As the other comments said, when you refer to records(0), you get a copy of the struct since it is a value type. What you can do (if you can't change it to a Class) is something like this:
Dim p As Person = records(0)
p.first = "Papa"
records(0) = p
Although, I think it's just easier to use a Class.
There are actually two important concepts to remember here.
One is that, as Hans and Chris have pointed out, Structure Person declares a value type of which copies are passed between method calls.
You can still access (i.e., get and set) the members of a value type, though. After all, this works:
Dim people(0) As Person
people(0).first = "Yogi"
people(0).last = "Bear"
people(0).age = 35
So the other important point to realize is that records(0) accesses the List(Of Person) class's special Item property, which is a sugary wrapper around two method calls (a getter and setter). It is not a direct array access; if it were (i.e., if records were an array), your original code would actually have worked.
I had the same problem, and I fixed it by adding a simple Sub to the structure that changes the value of the property.
Public Structure Person
Dim first as String
Dim last as String
Dim age as Integer
Public Sub ChangeFirst(value as String)
me.first = value
End Sub
End Structure

Update columns in one LINQ object with another LINQ object

I have two LINQ objects which have exactly the same columns and I would like to be able to update one with the fields from the other. I first create a new object from some data in a file, then I query the database for an existing item with the same ID. What I would like to be able to do is update the existing objects details with the newly created objects details.
So far the way I have been doing it is to list all the columns and update them manually but as you can see this can cause maintenance headaches.
With OldCaller
.ADDRESS = NewCaller.ADDRESS
.COMPANY = NewCaller.COMPANY
.CONTACT_HOURS = NewCaller.CONTACT_HOURS
.CONTACT_NAME = NewCaller.CONTACT_NAME
.CUSTOMER_ID = NewCaller.CUSTOMER_ID
.EMAIL_ADDRESS = NewCaller.EMAIL_ADDRESS
.FAX_NUMBER = NewCaller.FAX_NUMBER
.FAX_TYPE = NewCaller.FAX_TYPE
.MOBILE = NewCaller.MOBILE
.POSTCODE = NewCaller.POSTCODE
.PUBLIC_ADDRESS = NewCaller.PUBLIC_ADDRESS
.PUBLIC_TELEPHONE = NewCaller.PUBLIC_TELEPHONE
.STATE = NewCaller.STATE
.SUBURB = NewCaller.SUBURB
.TELEPHONE = NewCaller.TELEPHONE
End With
I would like to be able to find a way to clean this up a bit. Does anyone know of a better way to do what I need.
I do this sort of thing when I create an instance of an object from a template. Basically I have a method that iterates over the public properties of the template, finds the corresponding property in the object being created, and invokes the property setter on the new object, all via reflection.
I haven't tested this yet but this is what I have come up with.
Dim _OldCallerProperties = OldCaller.GetType().GetProperties(Reflection.BindingFlags.Public)
Dim _NewCallerProperties = NewCaller.GetType.GetProperties(Reflection.BindingFlags.Public)
For Each Prop In _OldCallerProperties
Dim _matchingProperty = _NewCallerProperties.Where(Function(p) p.Name = Prop.Name).FirstOrDefault
Dim _newvalue = _matchingProperty.GetValue(_matchingProperty, Nothing)
Prop.SetValue(Prop, _newvalue, Nothing)
Next
Like I said I haven't tested it but I'm sure it should work.