(SQL, VB.NET) How Do I select multiple values from one row and assign them to variables? - sql

I am writing a SELECT query to use in my project. So far, I have
Dim conn As New OleDbConnection
Dim StudentID, GradeID, SubjectID As Integer
Dim YourGrade(4), YourSubject(4) As String
conn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source =H:\Year 13 Computer Science\Project\Usernames and Passwords.accdb"
conn.Open()
Dim sql = "Select * From Grades where StudentID =" & CurrentID
Dim cmd As New OleDbCommand(sql, conn)
Dim dr As OleDbDataReader = cmd.ExecuteReader
While dr.Read
StudentID = dr("StudentID")
GradeID = dr("GradeID")
SubjectID = dr("SubjectID")
End While
My issue is that I need to be able to have a dynamic number of SubjectIDs and GradeIDs to be selected, in case a student is taking more or fewer subjects than the normal three.
My Query produces:
StudentID GradeID SubjectID
1 2 1
1 4 13
1 3 19
the CurrentID is "1" for the purposes of this.
Each GradeID and SubjectID corresponds to values in other tables which I can work on later.
I need to be able to have each of those three Grade IDs in a separate value, an array could be used but I don't know how to code it. I attempted it earlier as shown by the "YourGrade(4), YourSubject(4)".
I intend to use the data to fill out a Data Grid.

Create a domain object for "Student" and then load the records into a list of Student Objects.
I don't know VB.net, but the equivalent Domain object in C# would look like this:
public class Student
{
public int StudentId { get; set; }
public int GradeId { get; set; }
public int SubjectId { get; set; }
}
And then the code to loop through the dataReader and populate the list:
List<Student> results = new List<Student>();
while (dr.Read())
{
results.Add(new Student()
{
StudentId = Convert.ToInt32(dr["StudentID"]),
GradeId = Convert.ToInt32(dr["GradeId"]),
SubjectId = Convert.ToInt32(dr["SubjectId"])
});
}
-- Edit 2/2/2017 --
Turns out there are free converters on the web. These are the VB.net equivalents to the snippets above.
Class:
Public Class Student
Public Property StudentId() As Integer
Get
Return m_StudentId
End Get
Set
m_StudentId = Value
End Set
End Property
Private m_StudentId As Integer
Public Property GradeId() As Integer
Get
Return m_GradeId
End Get
Set
m_GradeId = Value
End Set
End Property
Private m_GradeId As Integer
Public Property SubjectId() As Integer
Get
Return m_SubjectId
End Get
Set
m_SubjectId = Value
End Set
End Property
Private m_SubjectId As Integer
End Class
Database Code:
Dim results As New List(Of Student)()
While dr.Read()
results.Add(New Student() With { _
Key .StudentId = Convert.ToInt32(dr("StudentID")), _
Key .GradeId = Convert.ToInt32(dr("GradeId")), _
Key .SubjectId = Convert.ToInt32(dr("SubjectId")) _
})
End While

I did some digging, found out that to fill out a datagrid in the way I was attempting to, the simplest way is to use an INNER JOIN SQL statement. In SQL the query is:
SELECT Students.FirstName, Students.LastName, Subjects.SubjectName, GradeVals.Grade
FROM GradeVals INNER JOIN (Students INNER JOIN (Subjects INNER JOIN Grades ON Subjects.SubjectID = Grades.SubjectID) ON Students.StudentID = Grades.StudentID) ON GradeVals.GradeID = Grades.GradeID;
That's using my Table names and such as.

Related

Issues on loop for retrieve the dynamic sql parameter values from dynamic textboxes

all. I am a new VB.NET beginner. I am facing the issue of how to pass the dynamic SQL parameter values from the dynamic textboxes to search the data. I had added control of dynamic textboxes and labels and would like to search the data on the database table based on the dynamic textboxes value inputted from the user. Currently, I can only able to search the data from 1 dynamic textbox value only.
I want all the values from the dynamic textboxes that the user had been entered can be retrieved, and search the data based on the SQL parameter variable name and value entered by the user.
Can someone give me some solutions on how to solve this problem? I had stuck on this problem for a few days. Thank you for all the help!
What I had tried:
Private Sub FilterData()
Dim count As Integer = 0
'filterdata for radiobutton
Try
For Each TextBox As TextBox In grp2.Controls.OfType(Of TextBox)()
For Each Label As Label In grp2.Controls.OfType(Of Label)()
Using connection As New SqlConnection("connectionString")
'user key in the SQL statement
sql = TextBox1.Text
Dim sql2 As String
sql2 = sql
Dim sp1 As String() = sql.Split(New String() {"where"}, StringSplitOptions.None)
sql = sp1(0) & " where " & Label.Text & " = #parameter or " & Label.Text & " =#parameter"
If (TextBox.Text <> "") Then
count += 1
For j As Integer = 0 To count - 1
Using cmd As New SqlCommand(sql, connection)
cmd.Parameters.AddWithValue("#parameter", TextBox.Text)
'cmd.Parameters.Add("#parameter", SqlDbType.NVarChar, 20).Value = TextBox.Text
connection.Open()
Dim dt As New DataTable()
Dim reader As SqlDataReader
reader = cmd.ExecuteReader()
dt.Load(reader)
DataGridView1.DataSource = dt
End Using
Next
Else
GetData()
End If
'cmd.Dispose()
connection.Close()
End Using
Next
Next
Catch ex As Exception
'MsgBox(ex.Message)
End Try
End Sub
Firstly, if you have a 1:1 correspondence between Labels and TextBoxes then you should not be using two nested For Each loops, because that is going to pair up each and every Label with each and every TextBox. What you should be doing is creating arrays and then using a single For loop to access the pairs of controls:
Dim labels = grp2.Controls.OfType(Of Label)().ToArray()
Dim textBoxes = grp2.Controls.OfType(Of TextBox)().ToArray()
For i = 0 To labels.getUpperBound(0)
Dim label = labels(i)
Dim textBox = textBoxes(i)
'...
Next
As for build the SQL and adding the parameters, I would tend to do it something like this:
Dim labels = grp2.Controls.OfType(Of Label)().ToArray()
Dim textBoxes = grp2.Controls.OfType(Of TextBox)().ToArray()
Dim criteria As New List(Of String)
Dim command As New SqlCommand
For i = 0 To labels.getUpperBound(0)
Dim label = labels(i)
Dim textBox = textBoxes(i)
Dim parameterName = "#" & label.Text.Replace(" ", "_")
criteria.Add($"[{label.Text}] = {parameterName}")
command.Parameters.AddWithValue(parameterName, textBox.Text)
Next
Dim sql = "SELECT * FROM MyTable"
If criteria.Any() Then
sql &= " WHERE " & String.Join(" OR ", criteria)
End If
command.CommandText = sql
I think that you should begin to separate UI and data logic here is an example of implementation:
First you have a table in database:
CREATE TABLE Customer (
Id INT IDENTITY (1, 1) PRIMARY KEY,
FirstName VARCHAR (255) NOT NULL,
LastName VARCHAR (255) NOT NULL,
Phone VARCHAR (25),
Email VARCHAR (255) NOT NULL,
Street VARCHAR (255),
City VARCHAR (50),
State VARCHAR (25),
ZipCode VARCHAR (5)
);
Then you create the underlying entity in VB. Net:
Public Class Customer
Public Property Id As Integer
Public Property FirstName As String
Public Property LastName As String
Public Property Phone As String
Public Property Email As String
Public Property Street As String
Public Property City As String
Public Property State As String
Public Property ZipCode As String
End Class
Data loader
Now you need a data access component that loads records to a list of this above entity here a nice implementation:
Imports System.Data.SqlClient
Public Class CustomerDataAccess
Public Property ConStr As String
Public Sub New(ByVal constr As String)
constr = constr
End Sub
Public Function GetCustomersByCriterias(constraints As Object) As List(Of Customer)
Dim query As String = "SELECT Id, FirstName, LastName, Phone, Email, Street, City, State, ZipCode
FROM [dbo].[Customer] "
Dim result = New List(Of Customer)()
Using con = New SqlConnection(ConStr)
Using cmd = con.CreateCommand()
cmd.CommandType = CommandType.Text
cmd.CommandText = query
'' here the magic to add dynamic criteria coming from constraints
cmd.ApplyConstraints(Of Customer)(constraints)
con.Open()
LoadCustomerData(cmd, result)
End Using
End Using
Return result
End Function
Private Sub LoadCustomerData(ByVal cmd As SqlCommand, ByVal result As List(Of Customer))
Using rdr = cmd.ExecuteReader()
Dim idIdx As Integer = rdr.GetOrdinal("Id")
Dim firstNameIdx As Integer = rdr.GetOrdinal("FirstName")
Dim lastNameIdx As Integer = rdr.GetOrdinal("LastName")
Dim phoneIdx As Integer = rdr.GetOrdinal("Phone")
Dim emailIdx As Integer = rdr.GetOrdinal("Email")
Dim streetIdx As Integer = rdr.GetOrdinal("Street")
Dim cityIdx As Integer = rdr.GetOrdinal("City")
Dim stateIdx As Integer = rdr.GetOrdinal("State")
Dim zipCodeIdx As Integer = rdr.GetOrdinal("ZipCode")
While rdr.Read()
Dim item = New Customer()
item.Id = rdr.GetValueOrDefault(Of Integer)(idIdx)
item.FirstName = rdr.GetValueOrDefault(Of String)(firstNameIdx)
item.LastName = rdr.GetValueOrDefault(Of String)(lastNameIdx)
item.Phone = rdr.GetValueOrDefault(Of String)(phoneIdx)
item.Email = rdr.GetValueOrDefault(Of String)(emailIdx)
item.Street = rdr.GetValueOrDefault(Of String)(streetIdx)
item.City = rdr.GetValueOrDefault(Of String)(cityIdx)
item.State = rdr.GetValueOrDefault(Of String)(stateIdx)
item.ZipCode = rdr.GetValueOrDefault(Of String)(zipCodeIdx)
result.Add(item)
End While
End Using
End Sub
End Class
Extensions methods
Below are extensions methods referenced above that do the magic you are looking for:
DataReader extensions to make it easy to read values from SalDataReader with Dbnull exfeptional case and casting
Module DataReaderExtenions
<Extension()>
Function GetValueOrDefault(Of T)(row As IDataRecord, fieldName As String) As T
Dim ordinal = row.GetOrdinal(fieldName)
Return row.GetValueOrDefault(Of T)(ordinal)
End Function
<Extension()>
Function GetValueOrDefault(Of T)(row As IDataRecord, ordinal As Integer) As T
Return (If(row.IsDBNull(ordinal), Nothing, row.GetValue(ordinal)))
End Function
<Extension()>
Function GetValueOrDefaultSqlite(Of T)(row As IDataRecord, fieldName As String) As T
Dim ordinal = row.GetOrdinal(fieldName)
Return row.GetValueOrDefault(Of T)(ordinal)
End Function
<Extension()>
Function GetValueOrDefaultSqlite(Of T)(row As IDataRecord, ordinal As Integer) As T
Return (If(row.IsDBNull(ordinal), Nothing, Convert.ChangeType(row.GetValue(ordinal), GetType(T))))
End Function
End Module
Command extensions that lets you extract criteria from an anonymous object values:
Imports System.Reflection
Imports System.Runtime.CompilerServices
Module CommandExtensions
<Extension()>
Function AddParameter(command As IDbCommand, name As String, value As Object) As IDataParameter
If command Is Nothing Then Throw New ArgumentNullException("command")
If name Is Nothing Then Throw New ArgumentNullException("name")
Dim p = command.CreateParameter()
p.ParameterName = name
p.Value = If(value, DBNull.Value)
command.Parameters.Add(p)
Return p
End Function
<Extension()>
Function ToDictionary(data As Object) As Dictionary(Of String, Object)
If TypeOf data Is String OrElse data.[GetType]().IsPrimitive Then Return New Dictionary(Of String, Object)()
Return (From [property] In data.[GetType]().GetProperties(BindingFlags.[Public] Or BindingFlags.Instance)
Where [property].CanRead
Select [property]).ToDictionary(Function([property]) [property].Name, Function([property]) [property].GetValue(data, Nothing))
End Function
<Extension()>
Sub ApplyConstraints(Of TEntity)(cmd As IDbCommand, constraints As Object)
If constraints Is Nothing Then Return
Dim dictionary = constraints.ToDictionary()
Dim whereClause = " WHERE "
For Each kvp In dictionary
Dim columnName = kvp.Key
Dim propertyName = kvp.Key
Dim prefix = "#"c
Dim value = kvp.Value
whereClause += $"{columnName} **like** {prefix}{propertyName} AND "
cmd.AddParameter(propertyName, value)
Next
If String.IsNullOrEmpty(whereClause) Then Return
cmd.CommandText += whereClause.Remove(whereClause.Length - 5, 5)
End Sub
End Module
Example:
After coded all these stuff now you can do the following:
Dim DataGridView1 As DataGridView = New DataGridView()
Dim ConStr As String = ConfigurationManager.ConnectionStrings("MyApp").ConnectionString
Dim dal As CustomerDataAccess = New CustomerDataAccess(ConStr)
Dim criterias = New With {.FirstName = "%James%", .LastName = "%Nadin%"}
DataGridView1.DataSource = dal.GetCustomersByCriterias(criterias)
Despite all this code you are still need to bind your textbox (after naming them correctly) to a SearchEntity and use this entity to provide criterias
I hope this material can help you tackle your issue and incite you to improve your architecture & dev skills

Upgrading to Entity Framework

I am trying to recover the id to the last document created by a user within my program. The original SQL work is not my own, but I have been tasked with upgrading to EF. Code snippets below. Just need to know how to change the syntax so it works using EF instead. (Using VB.Net)
'query for the max item created by the user
SqlString = "SELECT max(IdDocuments) as MaxId FROM Documents WHERE ModifiedBy='" + Environment.UserName.ToLower + "' ;"
SqlDataAdapter = New SqlDataAdapter(SqlString, SqlConnectionString)
TableNow = New DataTable
SqlDataAdapter.Fill(TableNow)
SqlDataAdapter.SelectCommand.Connection.Close()`
What I have tried already:
DocNow = (From a In Db.Documents Where a.ModifiedBy = Environment.UserName.ToLower)
Connection to DB defined as:
'query the database
Dim IdNow As Integer = DocumentId
Dim DocNow As IEnumerable(Of Documents) = (From a In Db.Documents Where a.IdDocuments = IdNow).ToList
Here is Current code after help from bwyn (SQL included):
'if is new, get the last document for this user
If IsNew Then
'query for the max item created by the user
'SqlString = "SELECT max(IdDocuments) as MaxId FROM Documents WHERE ModifiedBy='" + Environment.UserName.ToLower + "' ;"
'SqlDataAdapter = New SqlDataAdapter(SqlString, SqlConnectionString)
'TableNow = New DataTable
'SqlDataAdapter.Fill(TableNow)
'SqlDataAdapter.SelectCommand.Connection.Close()
Dim context As New Context()
Dim lastId As Integer
Dim currentUser As String = Environment.UserName.ToLower()
lastId = context.Documents.Where(Function(doc) doc.ModifiedBy = currentUser).Select(Function(doc) doc.IdDocuments).Max()
'set to the document id
DocumentId = lastId
End If
This will query for the current user's documents, select the ids, and then return the max id:
Dim context As New Context()
Dim lastId As Integer
Dim currentUser As String = Environment.UserName.ToLower()
lastId = context.Documents.Where(Function(doc) doc.ModifiedBy = currentUser).Select(Function(doc) doc.Id).Max()
Edit: My context class
Public Class Context
Inherits DbContext
Public Property Documents As DbSet(Of Document)
End Class

Left Outer Join doesnt show empty elements

I try to perform an left outer join within linq. My Datasource is:
Public Class Department
Public Property ID As Integer
Public Property Name As String
Public Shared Function GetAllDepartments() As List(Of Department)
Return New List(Of Department) From { _
New Department With {.ID = 1, .Name = "IT"},
New Department With {.ID = 2, .Name = "HR"},
New Department With {.ID = 3, .Name = "Payroll"}
}
End Function
End Class
Public Class Employee
Public Property ID As Integer
Public Property Name As String
Public Property DepartmentID As Integer
Public Shared Function GetAllEmployees() As List(Of Employee)
Return New List(Of Employee) From { _
New Employee With {.ID = 1, .Name = "Mark", .DepartmentID = 1},
New Employee With {.ID = 10, .Name = "Andy"}
}
End Function
End Class
My query is:
'Left Outer Join
Dim result = From emp In Employee.GetAllEmployees
Group Join dep In Department.GetAllDepartments
On emp.DepartmentID Equals dep.ID Into eGroup = Group
From all In eGroup.DefaultIfEmpty
Select New With {
.Person = emp.Name,
.DepartmentName = If(all.Name Is Nothing, "No Department", all.Name)}
What I expect to get is:
Mark IT
Andy No Department
But what I get is
Mark IT
Could anyone tell me, what is wrong with the query? I just cannot see it, even after reading msdn help for it over again, I just cannot find what is wrong.
I cannot reproduce this behavior because when I try to run your code it throws a NullReferenceException.
However, if I change this code:
.DepartmentName = If(all.Name Is Nothing, "No Department", all.Name)}
To this code:
.DepartmentName = If((all Is Nothing), "No Department", all.Name)}
I get the expected output:
Mark, ITAndy, No Department

Group DataSet Column Values into Comma Separated String using LINQ

How do I combine column values in a dataset using LINQ into a single string with comma separated values in VB.NET ?
I have one table with following structure
ID Name
728 Jim
728 Katie
728 Rich
How do I combine these into a single row like following
ID Name
728 Jim,Katie,Rich
Please note I am using a LINQ to Dataset so please respond in the applicable syntax.
Here is an example (using LINQ to objects, but should be easy to adjust for LINQ to DataSet):
Class Record
Public Property ID As Integer
Public Property Name As String
Sub New(id As Integer, name As String)
Me.ID = id
Me.Name = name
End Sub
End Class
Sub Main()
Dim recordList As New List(Of Record)
recordList.Add(New Record(728, "Jim"))
recordList.Add(New Record(728, "Katie"))
recordList.Add(New Record(728, "Rich"))
recordList.Add(New Record(729, "John"))
recordList.Add(New Record(729, "Michael"))
Dim v = From r As Record In recordList
Group By ID = r.ID Into Records = Group
Select ID, Name = String.Join(","c, Records.Select(Function(x) x.Name))
End Sub
This should do what you want:
Dim result = list.GroupBy(Function(a) a.ID) _
.Select(Function(g) New With {.ID = g.Key, .csvList = g.Select(Function(n) n.Name) _
.Aggregate(Function(accumulation, current) accumulation + "," + current)}) _
.ToList()
This is an example using LINQ to Dataset:
Dim grouped =
From row In dt.AsEnumerable()
Group row By id = row.Field(Of Integer)("ID") Into Group
Select ID, Name = String.Join(",", From i In Group Select i.Field(Of String)("Name"))
pretty late but i also ran into same problem and this is my solution. Hope this helps someone
Dim grouped =
a.AsEnumerable().
GroupBy(Function(row) row.Field(Of Integer)("ID")).
Select(Function(group, ID)
Return New With
{
.ID= ID,
.Name= String.Join(",", group.Select(Function(row) row.Field(Of String)("Name")))
}
End Function)

When combo box data selected, fill out textbox with data from Database

I am working on an application and I have a question, I have a Combo box that is bound to a table in my databse, when data is selected in the combo box I would like 'textbox 1' and 'textbox2' to be automatically filled out with data from the table, is this possible? so say for instance when I select 'Richard' on the combo box, 'Richards' 'Address1' and 'Postcode' are filled out in text boxes. I have a sample code that does this but it only seems to work with Int32, see insterted below.
Public Class Form1
Private Sub cmdGetByIdentifier_Click(sender As Object, e As EventArgs) Handles cmdGetByIdentifier.Click
If Not String.IsNullOrWhiteSpace(txtIdentifier1.Text) Then
Dim Identifier As Int32 = 0
If Int32.TryParse(txtIdentifier1.Text, Identifier) Then
txtCompanyName1.Text = GetCustomerNameByIdentifier(Identifier)
Else
MessageBox.Show("'" & txtIdentifier1.Text & "' is not a valid integer.")
End If
Else
MessageBox.Show("Must enter an identifier to get a company name.")
End If
End Sub
Private Sub cmdGetCustomer_Click(sender As Object, e As EventArgs) Handles cmdGetCustomer.Click
If Not String.IsNullOrWhiteSpace(txtIdentifier2.Text) Then
Dim Identifier As Int32 = 0
If Int32.TryParse(txtIdentifier2.Text, Identifier) Then
Dim Cust As Customer = GetCustomer(Identifier)
txtCompanyName2.Text = Cust.Name
txtContactName2.Text = Cust.ContactName
Else
MessageBox.Show("'" & txtIdentifier1.Text & "' is not a valid integer.")
End If
Else
MessageBox.Show("Must enter an identifier to get a company name.")
End If
End Sub
End Class
Module DatabaseOperations
Public Function GetCustomerNameByIdentifier(ByVal Identifier As Int32) As String
Dim CompanyName As String = ""
Dim Builder As New OleDb.OleDbConnectionStringBuilder With {.Provider = "Microsoft.ACE.OLEDB.12.0", .DataSource = IO.Path.Combine(Application.StartupPath, "Database1.accdb")}
Using cn As New OleDb.OleDbConnection With {.ConnectionString = Builder.ConnectionString}
Using cmd As New OleDb.OleDbCommand With {.Connection = cn}
cmd.CommandText = "SELECT CompanyName FROM Customer WHERE Identifier =#P1"
Dim NameParameter As New OleDb.OleDbParameter With {.DbType = DbType.Int32, .ParameterName = "P1", .Value = Identifier}
cmd.Parameters.Add(NameParameter)
cn.Open()
CompanyName = CStr(cmd.ExecuteScalar)
End Using
End Using
Return CompanyName
End Function
Public Function GetCustomer(ByVal Identifier As Int32) As Customer
Dim Customer As New Customer
Dim CompanyName As String = ""
Dim Builder As New OleDb.OleDbConnectionStringBuilder With {.Provider = "Microsoft.ACE.OLEDB.12.0", .DataSource = IO.Path.Combine(Application.StartupPath, "Database1.accdb")}
Using cn As New OleDb.OleDbConnection With {.ConnectionString = Builder.ConnectionString}
Using cmd As New OleDb.OleDbCommand With {.Connection = cn}
cmd.CommandText = "SELECT Identifier, CompanyName,ContactName FROM Customer WHERE Identifier =#P1"
Dim NameParameter As New OleDb.OleDbParameter With {.DbType = DbType.Int32, .ParameterName = "P1", .Value = Identifier}
cmd.Parameters.Add(NameParameter)
cn.Open()
Dim Reader As OleDb.OleDbDataReader = cmd.ExecuteReader
If Reader.HasRows Then
Reader.Read()
Customer.Identifier = Identifier
Customer.Name = Reader.GetString(1)
Customer.ContactName = Reader.GetString(2)
End If
End Using
End Using
Return Customer
End Function
End Module
Public Class Customer
Public Property Identifier As Int32
Public Property Name As String
Public Property ContactName As String
Public Sub New()
End Sub
End Class
1.Refer Below Link To Fetch Data from Sql server
2.Create a Function Which Will Return your Data with ID as parameter
Dataadapter with dataset - sql sever
Not 100% sure about using Access but in theory what you would do is have a customer object
You would have a SQL Stored Procedure
Select * from Customers where id=#ID
...
Then a class
Public Class Customer
public property ID as Integer
Public property Name as string
public Property Surname as string
....
End Class
In your code where you access the data you would have something like
Public Function GetCustomerDetailsByID (byval CustomerID as Integer) As Customer ' Or whatever you want to return it as
Dim myCust as New Customer
' Code to access SQL
' Call your SQL procedure called GetCustByID(CustID)
With myCust
.Name = valueFromYourProcedure
End With
End Function
In your client app, you would then call the above, then assign the values to your controls i.e.
Dim SingleCustomer as Customer = GetCustomerDetailsByID(1) ' i.e. value form your dropdown
txtBoxName.Text = SingleCustomer.Name
Thats a very rough example and isnt fully complete but should give you some idea of how to go about this. Theres a lot of ways to do the above and everyone has their own way so you may need to do some extra research to be comfortable with it.
Hope this helps