Firebird ADO.NET issue with loading a Dataset from the FbDataAdapter - vb.net

I have several freely available data access layers on my business site. I have always followed the Firebird database on their site and have now decided to build a new data access layer for this database engine.
I am using the ADO.NET provider recommended on the Firebird site (https://www.firebirdsql.org/en/net-provider/). The version being used is 5.5.
I have developed a simple stored procedure to process returning several columns of data, which has been tested successfully in my database manager. It has also been tested successfully with the data-reader within the test-client I am building for my Firebird data access layer.
However, when I attempt to test the stored-procedure against the return of a dataset, I consistently receive the following error...
Unable to cast object of type 'System.String' to type 'System.Byte[]'
Even a simple inline query with a parameter yields the same error.
Yet these very same tests work fine with my data-reader processes.
This error is always yielded when it comes to my code in the process that "fills" a dataset from the Firebird data-adapter.
The code I am testing is below
Public Overloads Function ExecuteDataSet(ByVal psConnectionString As String, _
ByVal psQuery As String, _
ByRef poParameterStructures As ArrayList) As DataSet
Dim loParameterStructures As New ArrayList()
Dim loDataSet As DataSet
Dim loFbSqlConnection As FbConnection = Nothing
Dim loFbSqlParameter As FbParameter
Dim loFbSqlCommand As FbCommand = Nothing
Dim loFbSqlDataAdapter As FbDataAdapter = Nothing
Try
'DataTable dt = new DataTable();
'FbDataAdapter da = new FbDataAdapter("select_mytable", ConfigurationManager.ConnectionStrings["mydb"].ConnectionString);
'da.SelectCommand.CommandType = CommandType.StoredProcedure;
'da.SelectCommand.Parameters.Add("#id", 123);
'da.Fill(dt);
loFbSqlConnection = Get_DBConnection(psConnectionString)
loFbSqlCommand = New FbCommand(psQuery, loFbSqlConnection)
loFbSqlCommand.CommandTimeout = ciTimeOut
' check for parameterized inline SQL or stored-procedure
If ((psQuery.IndexOf("#") > -1) Or _
(psQuery.ToUpper().IndexOf("SELECT ") > -1)) Then
loFbSqlCommand.CommandType = CommandType.Text
Else
loFbSqlCommand.CommandType = CommandType.StoredProcedure
End If
' create parameters for stored-procedure
If (Not poParameterStructures Is Nothing) Then
For Each loFbSqlHelperParameterStructure As FbSqlHelperParameterStructure In poParameterStructures
loFbSqlParameter = New FbParameter()
loFbSqlParameter.ParameterName = loFbSqlHelperParameterStructure.PARAMETER_NAME
loFbSqlParameter.DbType = loFbSqlHelperParameterStructure.PARAMETER_FBSQLDBTYPE
loFbSqlParameter.Direction = loFbSqlHelperParameterStructure.PARAMETER_DIRECTION
Select Case loFbSqlParameter.Direction
Case ParameterDirection.Input:
loFbSqlParameter.Value = loFbSqlHelperParameterStructure.PARAMETER_VALUE
loFbSqlParameter.Size = loFbSqlHelperParameterStructure.PARAMETER_SIZE
Case ParameterDirection.Output:
loFbSqlParameter.Size = loFbSqlHelperParameterStructure.PARAMETER_SIZE
End Select
loFbSqlCommand.Parameters.Add(loFbSqlParameter)
Next
End If
loFbSqlDataAdapter = New FbDataAdapter()
loFbSqlDataAdapter.SelectCommand = loFbSqlCommand
loDataSet = New DataSet()
loFbSqlDataAdapter.Fill(loDataSet)
Catch loFbSqlException As FbException
Throw loFbSqlException
Catch loException As Exception
Throw loException
Finally
loFbSqlCommand.Dispose()
loFbSqlConnection.Close()
loFbSqlConnection.Dispose()
End Try
poParameterStructures = loParameterStructures
Return (loDataSet)
End Function
The code above has also been successfully used with my other data access layers that support SQL Server, SQL Server CE, MySQL, and PostgreSQL.
However, in the case of the Firebird database, no matter what code-setup from the all of the documentation I have gone through on acquiring a data-set from the Firebird database yields exactly the same error noted above. Thus the code provided here is the original code that is used successfully in my other data access layers.

I finally found the problem with the code above...
If you look in my "Finally" statement you will see the following line of code... "loFbSqlCommand.Dispose()"
Though, this line of code has been used successfully in other data access layers I distribute, for the Firebird ADO.NET data provider it breaks a chain of reference pointers up into the returned dataset, which is a completely separate object from the FbDataAdapter.
Remove this line of code or comment it out and the code works fine.

Related

Problems updating a database using vb.net, oledbdataadapter

After going over multiple questions/answers on Stackoverflow and other boards I'm still lost on why I can't update an Access database from a datatable. I'm trying to take data from a datatable and insert that data into an Access table if it is blank, and replace the table if it already has data. I can successfully replace the table, but the data from the datatable does not get added.
However, the method which I'm using does not appear to work. My datatable comes from a bound datagridsource and the Access layer is called like this:
ConnectedDB.UpdateTable(DBTable, bsDataSource.DataSource)
Where ConnectedDB is the Access Layer class, DBTable is the string containing the Access table name, and bsDataSource is the bound data. As you can see, I passed the .Datasource to turn it into a datatable.
Here is the original (pre-Jan 29th) section of my work to add the datatable back into the Access table:
Public Function UpdateTable(strTable As String, dgDataTable As DataTable) As Boolean
Dim DS As New DataSet
dgDataTable.TableName = strTable
DS.Tables.Add(dgDataTable)
Using OpenCon = New OleDb.OleDbConnection(strConnectionString)
Using DataAdapter As New OleDbDataAdapter("SELECT * FROM " & strTable, OpenCon)
Dim DBcmd As OleDbCommandBuilder = New OleDbCommandBuilder(DataAdapter)
DBcmd.QuotePrefix = "["
DBcmd.QuoteSuffix = "]"
DataAdapter.UpdateCommand = DBcmd.GetUpdateCommand()
Try
OpenCon.Open()
DataAdapter.Fill(DS.Tables(strTable))
If DataAdapter.Update(DS.Tables(strTable)) > 0 Then
Return True
Else Return False
End If
Catch exo As Exception
MessageBox.Show(exo.Message)
Return False
End Try
End Using
End Using
End Function
My function tries to update an existing Access table with the name represented as strTable with the information in the datatable, dgDataTable from a datagridview. Each run hits the update check > 0 and returns a false which means syntax wise it should be working (i.e. no error messages). I have traced the table and it has all the data it should have (so the information is getting passed correctly from the grid through the update commands). I was playing with applying it in a dataset but I'm not sure I really need that.
I was tracing the variables through the update method and I think I found out why it won't update but I'm not sure what to do about it. The query it comes up with is like this:
UPDATE [RtoC] SET [R] = ?, [C] = ?, [Type] = ?, [Unknown] = ? WHERE (([R] = ?) AND ([C] = ?) AND ([Type] = ?) AND ((? = 1 AND [Unknown] IS NULL) OR ([Unknown] = ?)))
The Access table name is RtoC with fields R, C, Type, and unknown.
I'm thinking the "?" are not getting filled in causing the query to just not apply data back to Access. I'm not sure though how to set those items.
EDIT 1/29/20: I used the code changes I and jmcihinney document below and it does insert the lines into the Access table. This edit alters the question to be more specific about what I'm am trying to do, and how the datatable is created. Hopefully this clears up some wording on my part and provides some basis for the alteration of the row state.
The issue is that the Fill method of that data adapter calls AcceptChanges on the DataTable after populating it, thus there are no changes to save when you call Update.
That call to Fill shouldn't be there anyway though, because you don't want to retrieve any data, just save changes. You've got a whole lot of pointless code there. It should look more like this:
Public Function UpdateTable(strTable As String, dgDataTable As DataTable) As Boolean
Using DataAdapter As New OleDbDataAdapter("SELECT * FROM " & strTable, strConnectionString)
Dim DBcmd As OleDbCommandBuilder = New OleDbCommandBuilder(DataAdapter)
DBcmd.QuotePrefix = "["
DBcmd.QuoteSuffix = "]"
Try
Return DataAdapter.Update(dgDataTable) > 0
Catch exo As Exception
MessageBox.Show(exo.Message)
Return False
End Try
End Using
End Function
I took another tack at manipulating the database and in looking that up, I found the answer provided by jmcilhinney back in 2014! [Bulk Insert From DataTable to Access Database
In a for each loop across the rows of my datatable I set this:
row.SetAdded()
If I was filling I would have done something like:
DataAdapter.AcceptChangesDuringFill = True
Before the Fill command.
Unless this method has changed or there is a better way, I'll mark the link as the answer.
Thanks jmcilhinney....twice!

CommandText Property Has Not Been Initialized, retrieving data

Private Sub ButtonSubmitID_Click(sender As Object, e As EventArgs) Handles ButtonSubmitID.Click
Dim comm As New SqlCommand
Dim conn As New SqlConnection
conn.ConnectionString = "Data Source = localhost\SQLEXPRESS; Initial Catalog = test2Forms; Integrated Security = SSPI;"
comm.Connection = conn
Dim ID = TextBoxID.Text
comm.Parameters.AddWithValue("#ID", ID)
Dim adapter As SqlDataAdapter = New SqlDataAdapter(comm.CommandText, comm.Connection)
comm.CommandText = "SELECT * FROM withActivityLog3 WHERE ID = #ID"
Dim records As DataSet = New DataSet
adapter.Fill(records)
DataGridView2.DataSource = records
End Sub
CommandText property has not been initialized is the error I am receiving. I am able to pull all the data from the database into the GridView on the Form Load but when I try to narrow it down to one ID using a WHERE clause on the button trigger, it comes up with the above error. I've used the debugger to trace through one step at a time and the command and connection strings look correct. I've also successfully duplicated the query on my database using the SQL Server command line. I'm searching on a primary key (ID) so the expected results would be one uniquely identified row from the database.
As for the problem you know you have:
' initialize DataAdapter with (EMPTY) commandtext
Dim adapter As SqlDataAdapter = New SqlDataAdapter(comm.CommandText, comm.Connection)
' initialize Command Text
comm.CommandText = "SELECT * FROM withActivityLog3 WHERE ID = #ID"
When you pass the CommandText to the DataAdapter, it is empty because you havent set it yet which results in the error.
There is a fair amount of inefficiency in your code though. Rewritten:
' form level conn string
Private TheConnString As String = "Data Source = localhost\..."
Private Sub ButtonSubmitID_Click(sender ...
Dim dt As New DataTable
Using dbcon As New MySqlConnection(TheConnString)
Using cmd As New MySqlCommand("select * from Sample where Id = #id", dbcon)
cmd.Parameters.Add("#id", MySqlDbType.Int32).Value = Convert.ToInt32(TextBox2.Text)
dbcon.Open()
dt.Load(cmd.ExecuteReader)
dgvA.DataSource = dt
End Using
End Using
End Sub
Note: this uses MySQL but the concepts are the same for Sqlite, Access, SQL Server etc
There is no need to type or paste the connection string and over everywhere it is used. One form level variable will allow DRY (Dont Repeat Yourself) code.
Anything which implements the Dispose() method should be disposed of. That includes nearly all the DB Provider objects. The Using statement allows you to declare and initialize an object and at the End Using it is disposed of. Failing to Dispose of things can cause leaks and even run out of connections or resources to create things like DB Command objects.
There is no need to create a local DbDataAdapter. These are very powerful and useful critters meant to do much more than fill a DataTable. If that is all you are doing, you can use ExecuteReader method on the DbCommand object.
Nor do you need a local DataSet. Contrary to the name, these do not hold data, but DataTables. Since there is only one and it is local (goes out of scope when the method ends), you dont need a DataSet to store it.
The Add method should be used rather than AddWithValue. The code above specifies the datatype for the parameter so there is no guesswork required of the compiler. Of course with that comes the need to convert the text to a number...
...Since this is user input, you should not trust the user, so Integer.Tryparse would be more appropriate: I like pie will not convert to an integer. Data Validation is something you should do before you commence the DB ops.
Dim ID = TextBoxID.Text as used is pointless code. You do not need to move the textbox text into a new variable in order to use it. However, ID might be used to store the integer value

Function to check if Input is in column in SQL Server database

Okay, I'm creating an application which requires the user to input there Gamer-Tag during the registration process. Only 'Members' of a group may have access to the program and so I need the application to refuse anyone who's Gamer-Tag is not in the SQL Server database.
I adapted a function I created to log-in with your details and changed it to check if the Gamer-Tag is on the database, but I'm receiving a strange error:
The column 'Gamer_Tag' is not a Member of 'Members_Details' Table.
Please note, that's an error and not what I am wanting it to do. It's saying the column 'Gamer_Tag' isn't in the table, but I know it is! I've checked spelling and spacing and everything's as it should be, so I'm assuming it's something wrong with the code.
The (relevant) register button code:
(Note, RegUserName.Text is the textbox that contains the users Gamer-Tag.
Dim dbManager As New DatabaseManager()
If dbManager.CheckGamerTagisMember(RegUserName.Text) Then
MsgBox("Gamer-Tag Not A Member.")
My.Settings.RegisterCount = 1
My.Settings.Save()
RegisterBTN.Enabled = True
MsgBox("Registration Failed.")
GoTo Ender
Else
MsgBox("Gamer-Tag is A Member.")
GoTo Ender
End If
And the function this is calling:
Public Function CheckGamerTagisMember(ByVal gamertag As String) As Boolean
Connection = New SqlConnection("Data Source =" & My.Settings.ServerIP & ";Initial Catalog=Members_Details;Integrated Security=False;User=" & My.Settings.UserName & ";Password=********;")
Connection.Open()
Dim gamertagDatSet As New DataSet()
usersDataAdapter.FillSchema(gamertagDatSet, SchemaType.Source, "Members_Details")
usersDataAdapter.Fill(gamertagDatSet, "Members_Details")
Dim table As DataTable = gamertagDatSet.Tables("Members_Details")
For i As Integer = 0 To table.Rows.Count - 1
Dim currentUsergt As String = table.Rows(i)("Gamer_Tag").ToString.Trim()
If (currentUsergt = gamertag) Then
gamertagDatSet.Dispose()
Connection.Close()
Return True
End If
Next
gamertagDatSet.Dispose()
Connection.Close()
Return False
End Function
And to prove the column is in the table, here's the table's columns;
I'm new to structuring my code into classes, so I could be 'passing' things around wrongly.
Any help would be much appreciated!
I haven't a clue what the issue is with your code (there's probably something else going on which is not evident in your question).
But I will say that the approach you are taking will not scale well. With each registration, you are downloading the entire table to the program and then scanning through the rows, one by one, looking for an ID.
It would be much easier to write a simple stored procedure to do this for you. You would also get the advantage of using indexes. For example:
CREATE PROCEDURE spGetMembers_ID
#Gamer_Tag varchar(255)
AS
SET NOCOUNT ON;
SELECT Members_ID
FROM Members_Details
WHERE Gamer_Tag = #Gamer_Tag
GO
You can call this stored proc this way:
Dim getMembers_IDCommand As New SqlCommand("TestProcedure", Connection)
getMembers_IDCommand.CommandType = CommandType.StoredProcedure
Dim gamerTagParam As SqlParameter = getMembers_IDCommand.Parameters.Add("#Gamer_Tag", SqlDbType.VarChar, 255)
gamerTagParam.Value = gamertag
SqlDataReader result = getMembers_IDCommand.ExecuteReader();
Dim exists As Boolean = myReader.Read()
result.Close()
Return exists
There, of course, many ways to execute a stored procedure and get the results. Several are documented here. But the point is that a stored procedure is faster, more scalable and generally faster.

UPDATE statement in Oracle

We are building a client program where parameters for storage in a web server with Oracle backend are set in the .Net client program and uploaded as a dataset via webservice.
In the webservice code, data is read from the dataset and added to UPDATE statements on the web server (Oracle backend).
Because the server will run on the customer's LAN behind a firewall and because of the dynamic nature of the parameters involved, no sprocs are being used - SQL strings are built in the logic.
Here is an example string:
UPDATE WorkOrders
SET TravelTimeHours = :TravelTimeHours,
TravelTimeMinutes = :TravelTimeMinutes,
WorkTimeHours = :WorkTimeHours,
WorkTimeMinutes = :WorkTimeMinutes,
CompletedPersonID = :CompletedPersonID,
CompletedPersonName = :CompletedPersonName,
CompleteDate = :CompleteDate
WHERE WorkOrderNumber = :WorkOrderNumber
When debugging code in VS 2010 and stepping into the server code, we receive the following error:
ORA-01036: illegal variable name/number
when executing the SQL command on destination oracle machine, we were prompted to enter the bind
variables for the above statement, and as long as we used the correct date format, the UPDATE statement
worked correctly.
QUESTIONS:
1) is it possible that oracle threw the ORA-01036 error when the month format was wrong?
2) why don't we have to convert the date format from the ASP.net website running on the Oracle machine?
does Oracle have a default conversion routine that excludes the bind variable entry screen?
3) if the date format was not the problem, what precisely does ORA-1036 mean and how do I discover
WHICH variable had an illegal name/number?
This is a snippet of a function that takes the type of the dataset (WOName) and returns the appropriate SQL string.
Many Cases exist but have been removed for readability.
Private Function GetMainSQLString(ByVal WOName As String) As String
Dim Result As String = ""
Select Case WOName
Case "Monthly Site Inspection"
Dim sb As New StringBuilder
sb.Append("UPDATE WorkOrders SET ")
sb.Append("CompletedPersonID = :CompletedPersonID, CompletedPersonName = :CompletedPersonName, CompleteDate = :CompleteDate, ")
sb.Append("SupervisorID = :SupervisorID, SupervisorName = :SupervisorName ")
sb.Append("WHERE WorkOrderNumber = :WorkOrderNumber")
Result = sb.ToString
End Select
Return Result
End Function
This is a snippet of a function that takes the Oracle command object byRef and adds the required parameters to it,
depending upon which of the possible 15 types of dataset(WOName) is received from the client program.
Many Cases exist but have been removed for readability.
The updated Cmd object is then returned to the main program logic, where ExecuteNonQuery() is called.
The test values of params below are as follows:
dr.Item("CompletedPersonID") 21
dr.Item("CompletedPersonName") Pers Name
dr.Item("CompleteDate") #8/16/2010#
dr.Item("SupervisorID") 24
dr.Item("SupervisorName") Sup Name
dr.Item("WorkOrderNumber") 100816101830
Private Function addMainCmdParams(ByVal WOName As String, ByRef cmd As OracleCommand, ByVal dr As DataRow) As OracleCommand
Select Case WOName
Case "Monthly Site Inspection"
cmd.Parameters.Add(":CompletedPersonID", Oracle.DataAccess.Client.OracleDbType.Int32).Value = dr.Item("CompletedPersonID")
cmd.Parameters.Add(":CompletedPersonName", Oracle.DataAccess.Client.OracleDbType.Varchar2).Value = dr.Item("CompletedPersonName")
cmd.Parameters.Add(":CompleteDate", Oracle.DataAccess.Client.OracleDbType.Date).Value = dr.Item("CompleteDate")
cmd.Parameters.Add(":SupervisorID", Oracle.DataAccess.Client.OracleDbType.Int32).Value = dr.Item("SupervisorID")
cmd.Parameters.Add(":SupervisorName", Oracle.DataAccess.Client.OracleDbType.Varchar2).Value = dr.Item("SupervisorName")
cmd.Parameters.Add(":WorkOrderNumber", Oracle.DataAccess.Client.OracleDbType.Varchar2).Value = dr.Item("WorkOrderNumber")
End Select
Return cmd
End Function
While running this today, this precise code WAS successful; but another similar case was not. I still distrust any implicit typecasting performed by Oracle (if any) - and I'm especially suspicious of how Oracle handles any of these parameters that are passed with a dbNull.value - and I know it's going to happen. so if that's the problem I'll have to work around it. There are too many optional parameters and columns that don't always get values passed in for this system to break on nulls.
One Oracle "gotcha" that can cause this error is the fact that, by default, Oracle maps parameters to parameter symbols in the query by sequence, not by name. If the number/type of parameters does not match, you get an error like this one.
The solution is to tell Oracle to bind by name:
cmd.BindByName = true
Without diving into the details of your code, this may or may not be the answer to your specific problem, but this setting should be the default, and should be part of any command setup that uses parameters. It's rather amazing to watch this one statement fix some obscure problems.
EDIT: This assumes that you're using Oracle's data access provider. In .NET, you should be using this, not Microsoft's Oracle provider.
The error has nothing to do with date formats, it means that a variable in the statement was not bound.
Could be as simple as a spelling mistake (would be nice if Oracle included the variable name in the error message).
Can you update your question with the surrounding code that creates, binds, and executes the statement?
This is a snippet of a function that takes the type of the dataset (WOName) and returns the appropriate SQL string.
Many Cases exist but have been removed for readability.
Private Function GetMainSQLString(ByVal WOName As String) As String
Dim Result As String = ""
Select Case WOName
Case "Monthly Site Inspection"
Dim sb As New StringBuilder
sb.Append("UPDATE WorkOrders SET ")
sb.Append("CompletedPersonID = :CompletedPersonID, CompletedPersonName = :CompletedPersonName, CompleteDate = :CompleteDate, ")
sb.Append("SupervisorID = :SupervisorID, SupervisorName = :SupervisorName ")
sb.Append("WHERE WorkOrderNumber = :WorkOrderNumber")
Result = sb.ToString
End Select
Return Result
End Function
This is a snippet of a function that takes the Oracle command object byRef and adds the required parameters to it,
depending upon which of the possible 15 types of dataset(WOName) is received from the client program.
Many Cases exist but have been removed for readability.
The updated Cmd object is then returned to the main program logic, where ExecuteNonQuery() is called.
The test values of params below are as follows:
dr.Item("CompletedPersonID") 21
dr.Item("CompletedPersonName") Pers Name
dr.Item("CompleteDate") #8/16/2010#
dr.Item("SupervisorID") 24
dr.Item("SupervisorName") Sup Name
dr.Item("WorkOrderNumber") 100816101830
Private Function addMainCmdParams(ByVal WOName As String, ByRef cmd As OracleCommand, ByVal dr As DataRow) As OracleCommand
Select Case WOName
Case "Monthly Site Inspection"
cmd.Parameters.Add(":CompletedPersonID", Oracle.DataAccess.Client.OracleDbType.Int32).Value = dr.Item("CompletedPersonID")
cmd.Parameters.Add(":CompletedPersonName", Oracle.DataAccess.Client.OracleDbType.Varchar2).Value = dr.Item("CompletedPersonName")
cmd.Parameters.Add(":CompleteDate", Oracle.DataAccess.Client.OracleDbType.Date).Value = dr.Item("CompleteDate")
cmd.Parameters.Add(":SupervisorID", Oracle.DataAccess.Client.OracleDbType.Int32).Value = dr.Item("SupervisorID")
cmd.Parameters.Add(":SupervisorName", Oracle.DataAccess.Client.OracleDbType.Varchar2).Value = dr.Item("SupervisorName")
cmd.Parameters.Add(":WorkOrderNumber", Oracle.DataAccess.Client.OracleDbType.Varchar2).Value = dr.Item("WorkOrderNumber")
End Select
Return cmd
End Function
While running this today, this precise code WAS successful; but another similar case was not. I still distrust any implicit typecasting performed by Oracle (if any) - and I'm especially suspicious of how Oracle handles any of these parameters that are passed with a dbNull.value - and I know it's going to happen. so if that's the problem I'll have to work around it. There are too many optional parameters and columns that don't always get values passed in for this system to break on nulls.

Creating An Insert Statement -- Windows application Vb.Net

I am doing windows appliction in vb.net. i have customer object contains save method. how do i generate insert query?
I need to save the object in relational database (SQL server). I need to know which is the correct way of doing the insertion ie,. Inside the save method i have written the SQL statement to save the object. Is it the correct way?
Thanks
A simple INSERT statement for SQL takes this basic form:
INSERT INTO [tablename] ( [column1], [column2], ... ) VALUES ( [value1], [value2], ...)
So, we obviously need to know about the database table you are using: what columns it has. We also need to know about the class: what properties it has. Finally, we need to know about the data types for the table columns and class properties, and how the properties will map to the columns. For very simple objects the names and types will just line up. But in other cases your class may itself contain a collection (or several) that would mean inserting data into more than one table.
After all this is determined, we still need two things: connection information for the database (usually distilled down into a single connection string) and whether or not you are concerned that your class instance may have been saved previously, in which case you want to build an UPDATE statement rather than INSERT.
Assuming you can answer all of that in a satisfactory manner, your VB.Net code will look something like this (of course substituting your specific column, property, type, and connection information where appropriate):
Public Class Customer
Public Sub Save()
DAL.SaveCustomer(Me)
End Sub
' ...'
End Class
.
' a VB Module is a C# static class'
Public Module DAL
Private ConnString As String = "Your connection string here"
Public Sub SaveCustomer(ByVal TheCustomer As Customer)
Dim sql As String = "" & _
"INSERT INTO [MyTable] (" & _
"[column1], [column2], ..." & _
") VALUES (" & _
"#Column1, #Column2, ... )"
Using cn As New SqlConnection(ConnString), _
cmd As New SqlCommand(sql, cn)
cmd.Parameters.Add("#column1", SqlDbTypes.VarChar, 50).Value = TheCustomer.Property1
cmd.Parameters.Add("#column2", SqlDbTypes.VarChar, 1000).Value = TheCustomer.Property2
cn.Open()
cmd.ExecuteNonQuery()
End Using
End Sub
End Module
I know you've already heard that separating out your database code is the "right thing to do"tm, but I thought you might also want some more specific reasons why you would want to structure your code this way:
Your connection string is kept in one place, so if your database server moves you only need to make one change. Even better if this is it's own assembly or config file.
If you ever move to a completely different database type you only need to change one file to update the program.
If you have one developer or a DBA who is especially good with sql, you can let him do most of the maintenance on this part of the app.
It makes the code for your "real" objects simpler, and therefore easier to spot when you make a logical design error.
The DAL code might eventually be re-usable if another application wants to talk to the same database.
If you use an ORM tool most of the DAL code is written for you.
There's a few issues here. First, exactly where are you saving this? You say SQL, but is it a SQL Server, an instance of SQL Express, a Local Data Cache (SQL CE 3.5) or saving via a Web Service to talk to your SQL SERVER. These different data sources have different connectivity options/requirements, and in the case of SQL CE there's a few other "gotchas" involved in the SQL itself.
Second, are you sure you want to save data into a relational datastore like SQL Server? Consider, you could use XML, a data file (text, CSV. etc) or even a custom binary file type instead.
Since you're working on a windows application, you have a bunch of options on where and how to save the data. Until you know where you want to put the data, we'd be hard pressed to help you do so.
I agree with Mike Hofer. Keeping your class that does your retrieval and persisting of object separate from your business classes is key to having a flexible and robust design. This is the kind of code you want to be seeing in your GUI or Business layer:
//Populate Customer Objects List with data
IList<Customer> customerList = new List<Customer>()
Customer newCustomer1 = new Customer();
newCustomer.Name = "New Name"
newCustomer.email ="abcd#abcd.com"
customerList.Add(newCustomer1)
//DAL calls
DataAccessClass dalClass = new DataAccessClass ();
dalClass.InsertCustomers(customerList);
Inside your DALClass there should be a method called InsertCustomers(IList customers) and it should have the following code:
Public Function InsertCustomers(ByVal objectList As IList(Of Customer)) As Integer
Dim command As IDbCommand = Nothing
Dim rowsAffected As Integer = 0
Dim connection As IDbConnection = New System.Data.SqlClient.SqlConnection(Me.ConnectionString)
Try
connection.Open
Dim e As IEnumerator = objectList.GetEnumerator
Do While e.MoveNext
command = connection.CreateCommand
command.CommandText = "insert into dbo.Customer(CustomerID,CustomerGUID,RegisterDate,Password,SiteID,Las"& _
"tName,FirstName,Email,Notes,BillingEqualsShipping,BillingLastName) values (#Cust"& _
"omerID,#CustomerGUID,#RegisterDate,#Password,#SiteID,#LastName,#FirstName,#Email"& _
",#Notes,#BillingEqualsShipping,#BillingLastName)"
System.Console.WriteLine("Executing Query: {0}", command.CommandText)
Dim paramCustomerID As IDbDataParameter = command.CreateParameter
paramCustomerID.ParameterName = "#CustomerID"
command.Parameters.Add(paramCustomerID)
Dim paramCustomerGUID As IDbDataParameter = command.CreateParameter
paramCustomerGUID.ParameterName = "#CustomerGUID"
command.Parameters.Add(paramCustomerGUID)
Dim paramRegisterDate As IDbDataParameter = command.CreateParameter
paramRegisterDate.ParameterName = "#RegisterDate"
command.Parameters.Add(paramRegisterDate)
Dim paramPassword As IDbDataParameter = command.CreateParameter
paramPassword.ParameterName = "#Password"
command.Parameters.Add(paramPassword)
Dim paramSiteID As IDbDataParameter = command.CreateParameter
paramSiteID.ParameterName = "#SiteID"
command.Parameters.Add(paramSiteID)
Dim paramLastName As IDbDataParameter = command.CreateParameter
paramLastName.ParameterName = "#LastName"
command.Parameters.Add(paramLastName)
Dim paramFirstName As IDbDataParameter = command.CreateParameter
paramFirstName.ParameterName = "#FirstName"
command.Parameters.Add(paramFirstName)
Dim paramEmail As IDbDataParameter = command.CreateParameter
paramEmail.ParameterName = "#Email"
command.Parameters.Add(paramEmail)
Dim paramNotes As IDbDataParameter = command.CreateParameter
paramNotes.ParameterName = "#Notes"
command.Parameters.Add(paramNotes)
Dim paramBillingEqualsShipping As IDbDataParameter = command.CreateParameter
paramBillingEqualsShipping.ParameterName = "#BillingEqualsShipping"
command.Parameters.Add(paramBillingEqualsShipping)
Dim paramBillingLastName As IDbDataParameter = command.CreateParameter
paramBillingLastName.ParameterName = "#BillingLastName"
command.Parameters.Add(paramBillingLastName)
Dim modelObject As Customer = CType(e.Current,Customer)
paramCustomerID.Value = modelObject.CustomerID
paramCustomerGUID.Value = modelObject.CustomerGUID
paramRegisterDate.Value = modelObject.RegisterDate
If IsNothing(modelObject.Password) Then
paramPassword.Value = System.DBNull.Value
Else
paramPassword.Value = modelObject.Password
End If
paramSiteID.Value = modelObject.SiteID
If IsNothing(modelObject.LastName) Then
paramLastName.Value = System.DBNull.Value
Else
paramLastName.Value = modelObject.LastName
End If
If IsNothing(modelObject.FirstName) Then
paramFirstName.Value = System.DBNull.Value
Else
paramFirstName.Value = modelObject.FirstName
End If
If IsNothing(modelObject.Email) Then
paramEmail.Value = System.DBNull.Value
Else
paramEmail.Value = modelObject.Email
End If
If IsNothing(modelObject.Notes) Then
paramNotes.Value = System.DBNull.Value
Else
paramNotes.Value = modelObject.Notes
End If
paramBillingEqualsShipping.Value = modelObject.BillingEqualsShipping
If IsNothing(modelObject.BillingLastName) Then
paramBillingLastName.Value = System.DBNull.Value
Else
paramBillingLastName.Value = modelObject.BillingLastName
End If
rowsAffected = (rowsAffected + command.ExecuteNonQuery)
Loop
Finally
connection.Close
CType(connection,System.IDisposable).Dispose
End Try
Return rowsAffected
End Function
It is painful to write the DAL code by hand, but you will have full control of your DAL, SQL and Mapping code and changing any of those will be a breeze in the future.
If you don't feel like to write all the DAL Code by hand, you can get a CodeGenerator like Orasis Mapping Studio to generate exactly the same code shown without writing anything. You just need to build your SQL in the tool, map the properties to the paramaters and you are done. It will generate all the rest for you.
Good luck and happy DAL coding!
I'm with Stephen Wrighton. There are a LOT of variables here, and a lot of unanswered questions. If it's SQL, is it even a Microsoft dialect of SQL? Is it Oracle? MySQL? Something else?
In any event, my personal preference is to avoid building SQL in an application if I can, and invoke a stored procedure, even for inserts and updates. Then I pass the arguments for the procedure to the ADO.NET command object. I have this insane idea in my head that SQL belongs in the database. Perhaps that comes from all that time I spent debugging horrifically written ASP code that spliced SQL strings together back in the Dot Com era. (Never again.)
If you feel it's absolutely necessary to do so, meet the System.Text.StringBuilder class. Learn it. Love it. Make it your best friend.
UPDATE:
Seeing your response, I see now that you are working with SQL Server. That makes things much better.
I'd recommend separating your SQL code into a separate class, away from the actual business class. Some might not agree with that, but it will keep the PURPOSE of the classes clear. (See Separation of Concerns.)
You want to have your business object handle the business logic, and a separate class that handles the work of getting data into and out of the database. That way, if you have a problem with the serialization logic, you have a far better idea of where to look, and your chances of hosing the business logic are greatly reduced. It also makes your application much easier to understand.
A little up front effort in writing a few more classes has a HUGE payoff down the road.
But that's just my opinion.
I prefer the idea of Mike Hofer, to have a Stored Proc in the SQL Server side to handle the actual data updates, and having a separate class to wrap calls to those stored procs.
Just my 0.02$
Not quite sure what the OP is asking.
You need to define exactly what you are doing in the "Save" method
If you are creating a new record in the Save method you need to use an INSERT statement.
If you are updating an existing record in the Save method then you need to use an UPDATE statement.
"Save" methods generally imply that both cases are handled by the procedure.
A better method would be to have ("Create" or "Insert") and ("Update" or "Save") methods.
Or perhaps have one procedure which handles both.