For Each Next loop getting first row data only - vb.net

I am trying to populate a dataset with data from a dynamic SQL dataset created by a code generator (PDSA). If I want the first row of data, or I use a specific "Where" clause to retrieve 1 row, I have no problem. But, when I loop through the dataset that has four entries, instead of getting the four entries, I get the first row 4 times. Any idea why?
Code Example:
Dim DS_C as New DS
Dim dr_A As DS_C.Tbl_ARow
Me.DS_C.Tbl_A.Clear()
Dim bo As PDSA.DataLayer.tbl_BDC = New PDSA.BusinessLayer.tbl_B
With bo
.ConnectionString = AppConfig.SQLConnectString
.SelectFilter = PDSA.DataLayer.tbl_BDC.SelectFilters.All
.WhereFilter = tbl_BDC.WhereFilters.None
.Load()
End With
For Each dr As DataRow In bo.DataSet.Tables(0).Rows
dr_A = DS_C.Tbl_A.NewRow
With dr_A
.CustomerID = bo.CustomerID
.FirstName = bo.FirstName
.LastName = bo.LastName
.Street = bo.Street
.City = bo.City
.State = bo.State
.ZipCode = bo.ZipCode
End With
DS_C.Tbl_A.AddTbl_ARow(dr_A)
Next
If I try to change it to use dr instead of bo , it wont accept it.
I get:
.CustomerID = dr.CustomerID(CustomerID is not a member of System.Data.DataRow)
If I try using DS_C.Tbl_ARow
For Each dr As DS_C.Tbl_ARow In bo.DataSet.Tables(0).Rows
I get type 'DS_C.Tbl_ARow' is not defined
If I try :
For Each dr As DS.Tbl_ARow In bo.DataSet.Tables(0).Rows
I get:
System.InvalidCastException = {"Unable to cast object of type 'System.Data.DataRow' to type 'TblXLMajorPerilsRow'."}

You need to access it like this:
.CustomerID = dr("CustomerID");

Related

letting user type and add value to an already bound combobox

I have one combobox in my app that gets populated with a dozen or so items from SQL SERVER. I am currently trying to allow user to be able to type in a new value into the combobox and save it to a table in SQL SERVER. I'm looking for something along the lines of a DropDownStyle of DropDown and DropDownList. Basically I'm hoping to let the user type in the combobox, however if the value is not there, i want to be able to give them an option of saving it (probably on lost focus). I'm wondering what would be a good way of doing something like that.
On lost focus should I basically go through each item that is currently in the drop down and check it against the value entered?
EDIT:
Sql="SELECT IDvalue, TextName from TblReference"
Dim objconn As New SqlConnection
objconn = New SqlConnection(conn)
objconn.Open()
Dim da As New SqlDataAdapter(sql, objconn)
Dim ds As New DataSet
da.Fill(ds, "Name")
If ds.Tables("Name").Rows.Count > 0 Then
Dim dr As DataRow = ds.Tables("Name ").NewRow
ds.Tables("Name").Rows.InsertAt(dr, 0)
With c
.DataSource = ds.Tables("Name")
.ValueMember = " IDvalue "
.DisplayMember = " TextName "
End With
End If
You are already adding a fake/blank row to a table, you can do the same thing for a new item.
' form level datatable var
Private cboeDT As DataTable
Initializing:
cboeDT = New DataTable
Dim sql = "SELECT Id, Descr FROM TABLENAME ORDER BY Descr"
Using dbcon As New MySqlConnection(MySQLConnStr)
Using cmd As New MySqlCommand(sql, dbcon)
dbcon.Open()
cboeDT.Load(cmd.ExecuteReader())
' probably always need this even
' when there are no table rows (???)
Dim dr = cboeDT.NewRow
dr("Id") = -1 ' need a way to identify it
dr("Descr") = ""
cboeDT.Rows.InsertAt(dr, 0)
End Using
End Using
cboeDT.DefaultView.Sort = "Descr ASC"
cboE.DataSource = cboeDT
cboE.DisplayMember = "Descr"
cboE.ValueMember = "Id"
Note Users tend to have a preference as to the order of these things. The simple creatures tend to prefer alphabetical over a DB Id they may never see. To accommodate them, the DefaultView is sorted so that any new rows added will display in the correct order.
Add new items in the Leave event (much like Steve's):
Private Sub cboE_Leave(sender ...
' if it is new, there will be no value
If cboE.SelectedValue Is Nothing Then
' alternatively, search for the text:
'Dim item = cboeDT.AsEnumerable().
' FirstOrDefault(Function(q) String.Compare(q.Field(Of String)("Descr"),
' cboE.Text, True) = 0)
'If item Is Nothing Then
' ' its new...
Dim newid = AddNewItem(cboE.Text)
Dim dr = cboeDT.NewRow
dr("Id") = newid
dr("Descr") = cboE.Text
cboeDT.Rows.Add(dr)
' fiddling with the DS looses the selection,
' put it back
cboE.SelectedValue = newid
End If
End Sub
If you want to search by text:
Dim item = cboeDT.AsEnumerable().
FirstOrDefault(Function(q) String.Compare(q.Field(Of String)("Descr"),
cboE.Text, True) = 0)
If item Is Nothing Then
' its new...
...
Inserting will vary a little depending on the actual db. A key step though is to capture and return the ID of the new item since it is needed for the CBO:
Private Function AddNewItem(newItem As String) As Int32
Dim sql = "INSERT INTO MY_TABLE (Descr) VALUES (#v); SELECT LAST_INSERT_ID();"
Dim newId = -1
Using dbcon As New MySqlConnection(MySQLConnStr)
Using cmd As New MySqlCommand(sql, dbcon)
dbcon.Open()
cmd.Parameters.Add("#v", MySqlDbType.String).Value = newItem
' MySql provides it in the command object
If cmd.ExecuteNonQuery() = 1 Then
newId = Convert.ToInt32(cmd.LastInsertedId)
End If
End Using
End Using
Return newId
End Function
As noted MySql provides the LastInsertedID as a command object property. In SQL SERVER, tack ...";SELECT LAST_INSERT_ID();" to the end of your SQL and then:
newId = Convert.ToInt32(cmd.ExecuteScalar())
This is not conceptually very different from Steve's answer, except it uses the DataTable you build rather than collections which makes it (very) slightly simpler.
The way I do this, is on window load I perform a new SQL query to get the list of values in the table, and load them into a combobox.
Then once focus is lost, it then checks what's currently typed into the combobox against the current values already loaded. If it doesn't exist, then it's not in SQL. Something like the following...
Dim found As Boolean = False
For i As Integer = 0 To comboBox.Items.Count - 1
Dim value As String = comboBox.Items(i).ToString()
If comboBox.Text = value Then
found = True
End If
Next
If found = False Then
'the item doesn't exist.. add it to SQL
Else
'the item exists.. no need to touch SQL
End If
First thing I would do is to build a simple class to hold your values through a List of this class
Public Class DataItem
Public Property IDValue As Integer
Public Property TextName as String
End Class
Now, instead of building an SqlDataAdapter and fill a dataset, work with an SqlDataReader and build a List(Of DataItem)
' Class global...
Dim allItems = new List(Of DataItem)()
Sql="SELECT IDvalue, TextName from TblReference"
' Using to avoid leaks on disposable objects
Using objconn As New SqlConnection(conn)
Using cmd As New SqlCommand(Sql, objconn)
objconn.Open()
Using reader = cmd.ExecuteReader()
While reader.Read()
Dim item = new DataItem() With { .IDValue = reader.GetInt32(0), .TextName = reader.GetString(1)}
allItems.Add(item)
End While
End Using
if allItems.Count > 0 Then
allItems.Insert(0, new DataItem() With {.IDValue = -1, .TextValue = ""}
Dim bs = new BindingList(Of DataItem)(allItems)
c.DataSource = bs
c.ValueMember = "IDvalue"
c.DisplayMember = "TextName"
End If
End Using
End Using
Now the code that you want to add to your Leave event for the combobox
Sub c_Leave(sender As Object, e As EventArgs) Handles c.Leave
If Not String.IsNullOrEmpty(c.Text) Then
Dim bs = DirectCast(c.DataSource, BindingList(Of DataItem))
if bs.FirstOrDefault(Function(x) x.TextName = c.Text) Is Nothing Then
Dim item = new DataItem() With { .IDValue = -1, .TextName = c.Text}
bs.Add(item)
' here add the code to insert in the database
End If
End If
End Sub

Logon fails if two tables are in the dataSet

My report fills two tables. It works on the local server, but when published to another server it gives the following error:
Unable to connect. Login failed.
When I take off the second table and only use the first table, the report works. How can I resolve this?
objDataTable = New Data.DataTable
objDataTable.TableName = "Table"
objDataTable.Columns.Add("pes_nom", GetType(String))
objRow = objDataTable.NewRow
objRow("pes_nom") = objProposta.clsPessoa.pesNom
objDataTable.Rows.Add(objRow)
objDataSet = New Data.DataSet
objDataSet.Tables.Add(objDataTable)
If objProposta.clsDependente.DtDependentes IsNot Nothing Then
Dim dtCloned As New Data.DataTable
dtCloned = objProposta.clsDependente.DtDependentes.Clone()
dtCloned.Columns(3).DataType = System.Type.GetType("System.String")
For Each row As Data.DataRow In objProposta.clsDependente.DtDependentes.Rows
dtCloned.ImportRow(row)
Next
dtCloned.TableName = "Dependentes"
objDataSet.Tables.Add(dtCloned)
End If
Bmgviewer1.PathReport = "RptTermoAdesaoHAP.rpt"
Bmgviewer1.DataSet = objDataSet
Bmgviewer1.DataBind()
The solution was insert all data in just one table with multiple rows.
I added the following code:
If objProposta.clsDependente.DtDependentes IsNot Nothing Then
For Each row As Data.DataRow In objProposta.clsDependente.DtDependentes.Rows
objRow("dep_nom") = row.Item("dep_nom").ToString()
objRow("dep_cpf_cgc") = row.Item("dep_cpf_cgc").ToString()
objDataTable.Rows.Add(objRow)
objRow = objDataTable.NewRow
Next
Else
objDataTable.Rows.Add(objRow)
End If
And it worked.

Join two queries from different data sources using VB.NET

I have two queries. One is an Oracle query, and one is a SQL Server query.
Oracle Columns: ID, Subject, Course
SQL Server Columns: ID, Recommended Subject, Recommended Course
I would like to join the two queries on ID. I need to find out which IDs have a subject that is not equal to the recommended subject or a course that is not equal to the recommended course. Then, display the results in a GridView.
Here's what I have tried to do so far. I have removed my SQL commands and connection strings.
Dim sConnectionString As String = ConfigurationManager.ConnectionStrings("sqlserver").ConnectionString
Dim sCN As New SqlConnection(sConnectionString)
Dim sCommandWrapper As SqlCommand = New SqlCommand("SQL", sCN)
Dim sDataAdapter As SqlDataAdapter = New SqlDataAdapter
sDataAdapter.SelectCommand = sCommandWrapper
Dim pConnectionString As String = ConfigurationManager.ConnectionStrings("oracle").ConnectionString
Dim pCN As New OleDbConnection(pConnectionString)
Dim pCommandWrapper As OleDbCommand = New OleDbCommand("SQL", pCN)
Dim pDataAdapter As OleDbDataAdapter = New OleDbDataAdapter
pDataAdapter.SelectCommand = pCommandWrapper
Dim stopDS As DataSet = New DataSet()
sDataAdapter.Fill(stopDS, "Recommendations")
pDataAdapter.Fill(stopDS, "Registrations")
Since you have your two results in a DataSet, you can define a relationship between the two tables using the DataSet.Relations property:
stopDS.Relations.Add(
"ID2ID",
stopDS.Tables("Recommendations").Columns("ID"),
stopDS.Tables("Registrations").Columns("ID")
)
You can then get the matching rows from either end of the relationship (apologies if I got the relationship the wrong way around!):
Dim rows() As DataRow = stopDS.Tables("Recommendations").Rows(0).GetChildRows("ID2ID")
Dim row As DataRow = stopDS.Tables("Registrations").Rows(0).GetParentRow("ID2ID")
' Can also use .GetParentRows(...) for an array.
Here is a complete example console app:
Module Module1
Sub Main()
Dim t1 = New DataTable()
t1.TableName = "Names"
t1.Columns.Add("ID", GetType(Integer))
t1.Columns.Add("Name", GetType(String))
Dim t2 = New DataTable()
t2.TableName = "Addresses"
t2.Columns.Add("ID", GetType(Integer))
t2.Columns.Add("Address", GetType(String))
Dim r As DataRow = Nothing
r = t1.NewRow()
r("ID") = 1
r("Name") = "Bob"
t1.Rows.Add(r)
r = t1.NewRow()
r("ID") = 2
r("Name") = "Joe"
t1.Rows.Add(r)
r = t1.NewRow()
r("ID") = 3
r("Name") = "Sue"
t1.Rows.Add(r)
r = t2.NewRow()
r("ID") = 1
r("Address") = "1 Main St"
t2.Rows.Add(r)
r = t2.NewRow()
r("ID") = 3
r("Address") = "2 Any St"
t2.Rows.Add(r)
Dim ds = New DataSet()
ds.Tables.Add(t1)
ds.Tables.Add(t2)
' Define relationship between the ID columns
ds.Relations.Add(
"NameToAddress",
ds.Tables("Names").Columns("ID"),
ds.Tables("Addresses").Columns("ID"))
For Each nameRow In t1.AsEnumerable()
Console.WriteLine("Name: {0}", nameRow.Field(Of String)("Name"))
For Each addrRow In nameRow.GetChildRows("NameToAddress")
Console.WriteLine("--Addr: {0}", addrRow.Field(Of String)("Address"))
Next
Next
Console.WriteLine("==========")
For Each addrRow In t2.AsEnumerable()
Console.WriteLine("Addr: {0}", addrRow.Field(Of String)("Address"))
Dim pr = addrRow.GetParentRow("NameToAddress")
If pr IsNot Nothing Then
Console.WriteLine("++Name: {0}", pr.Field(Of String)("Name"))
End If
For Each nameRow In addrRow.GetParentRows("NameToAddress")
Console.WriteLine("--Name: {0}", nameRow.Field(Of String)("Name"))
Next
Next
Console.ReadLine()
End Sub
End Module
The results are:
Name: Bob
--Addr: 1 Main St
Name: Joe
Name: Sue
--Addr: 2 Any St
==========
Addr: 1 Main St
++Name: Bob
--Name: Bob
Addr: 2 Any St
++Name: Sue
--Name: Sue
I really like Mike's approach, but let me suggest another one and you can choose whichever more suitable for you.
You can set up a linked server to your Oracle database. It is described in this answer. Alternatively you can follow this article.
Then simply join the two tables and get the results:
SELECT * FROM SqlTable s
INNER JOIN OracleServer.OracleDB..OracleTable o ON o.ID = s.ID
AND (s.Course != o.[Recommended Course] OR s.Subject != o.[Recommended Subject])
Unfortunately I don't have oracle installed to completely test this myself, but I hope you get in the direction.

datarow variable assign to dataset's table newrow

I'm trying to define a datarow to hold values and then add it to data set
indgv: datagridview with values in it
dsdetails: a dataset with a table named details
If indgv.Rows.Count > 0 Then
Dim dr As DataRow
dr = dsdetails.Tables("details").NewRow
For Each row As DataGridViewRow In indgv.Rows
dr("mat") = row.Cells("icode").Value
dr("dateoftrans") = Me.DateTimePicker1.Value
dr("numoftrans") = transnum.Text
dr("type") = 1
dr("doc") = doctyp.SelectedValue
dr("amount") = row.Cells("iamo").Value
dsdetails.Tables("details").Rows.Add(dr)
Next
adpdetails.Update(dsdetails, "details")
End If
running this causes the following error
Object reference not set to an instance of an object.
how to rephrase the declaration with 'New' to avoid the problen
BTW : when using new as the following
Dim dr As New DataRow = dsdetails.Tables("details").NewRow
it shows design time error
Type 'dsdetails.Tables' is not defined.
Try this code:
If indgv.Rows.Count > 0 Then
Dim tbl As DataTable = dsdetails.Tables("details")
Dim dr As DataRow
For Each row As DataGridViewRow In indgv.Rows
dr = tbl.NewRow 'Create a new row inside the loop!
dr("mat") = row.Cells("icode").Value
dr("dateoftrans") = Me.DateTimePicker1.Value
dr("numoftrans") = transnum.Text
dr("type") = 1
dr("doc") = doctyp.SelectedValue
dr("amount") = row.Cells("iamo").Value
tbl.Rows.Add(dr)
Next
adpdetails.Update(tbl)
End If
If all you need is to copy rows from one table to another, DataTable class has a Copy method you may want to use. It works like this:
Dim dtCopy As New DataTable()
dtCopy = dt.Copy()
If you have a datagridview control bound to a table, you could also use this form:
Dim dtCopy As New DataTable()
dtCopy = DirectCast(dataGridViewX1.DataSource, DataTable).Copy()
The Copy method will copy the datatable structure and the data.
If you want to copy the structure only without the data you could use Clone method.

how to search DataTable for specific record?

Hi,
I have a windows form with 10 text fields and 1 combobox.
When the user selects a record in the combo-box I want to find that record in my form datatable variable (called dtBranches) then populate my 10 textfields from the datarow.
I tried this:
Dim dr As System.Data.DataRow
If mSortCode > 0 Then
dr = dtBranches.Select("SortCode='" & mSortCode & "'")
Me.txtBranch.Text = dr("Branch").ToString()
Me.txtBankName.Text = dr("BankName").ToString()
Me.txtBranchTitle.Text = dr("BranchTitle").ToString()
Me.txtReference.Text = dr("Ref").ToString
Me.txtAddr1.Text = dr("Address1").ToString
Me.txtAddr2.Text = dr("Address2").ToString
Me.txtAddr3.Text = dr("Address3").ToString
Me.txtPostCode.Text = dr("PostCode").ToString
Me.txtTelNo.Text = dr("TelephoneNumber").ToString
Me.txtTown.Text = dr("Town").ToString
Me.txtTelNo.Text = dr("TelephoneNumber").ToString
end if
but can't get it to compile...
What's the correct and best way to do this please?
thanks
Philip
DataTable.Select returns an array of DataRows. You need to declare an array to receive the result
Dim dr() As System.Data.DataRow
Of course then you need to check if you have rows returned and address the first row in the array
dr = dtBranches.Select("SortCode='" & mSortCode & "'")
If dr.Length > 0 Then
Me.txtBranch.Text = dr(0)("Branch").ToString()
Me.txtBankName.Text = dr(0)("BankName").ToString()
...... and so on ...
I would use Linq-ToDataSet and the strongly typed Field method instead:
Dim matches = From row In dtBranches
Let SortCode = row.Field(Of String)("SortCode")
Where SortCode = mSortCode
If matches.Any() Then
Dim row = matches.First()
Me.txtBranch.Text = row.Field(Of String)("Branch")
Me.txtBankName.Text = row.Field(Of String)("BankName")
Me.txtBranchTitle.Text = row.Field(Of String)("BranchTitle")
Me.txtReference.Text = row.Field(Of String)("Ref")
Me.txtAddr1.Text = row.Field(Of String)("Address1")
Me.txtAddr2.Text = row.Field(Of String)("Address2")
Me.txtAddr3.Text = row.Field(Of String)("Address3")
Me.txtPostCode.Text = row.Field(Of String)("PostCode")
Me.txtTelNo.Text = row.Field(Of String)("TelephoneNumber")
Me.txtTown.Text = row.Field(Of String)("Town")
Else
MesageBox.Show("SortCode not found.")
End If
If you want to compare case-insensitively, replace the Where above with:
Where StringComparer.OrdinalIgnoreCase.Equals(SortCode, mSortCode)
By the way, you are assigning the telephone number twice.