Doing a lot of input validation in VB.NET - vb.net

I have a form set up where users can enter their booking for a room at my college. I want to validate the user input to avoid SQL injection (my program uses a MS Access database) and also stop numbers and synbols in their name, etc.
I can do the validation fine, but there is to be a lot of validation and then methods executed only if all validation tests come back as true. I did have something like this:
If txtName.Text = "" Then
frmBookErr.SetError(txtName, "Name field cannot be left blank.")
fail = 1
Else
frmBookErr.SetError(txtName, "")
fail = 0
End If
And then check the fail variable, but it obviously gets overridden later in the form if one of the validation tests come back as true.
Can anyone provide some input into this? Thanks.

If you want to avoid SQL injection, use parameterised SQL queries or stored procedures, and do not construct SQL by concatenation.

Set your fail variable at the start of the procedure to 0 then only set it to 1 if something fails...
fail = 0
If txtName.Text = "" Then
frmBookErr.SetError(txtName, "Name field cannot be left blank.")
fail = 1
End If
If txtSomethingElse.Text = String.Empty Then fail = 1
If fail = 0 Then frmBookErr.Clear()

To avoid SQL Injections you need to use something that doesn't directly allow for changes in the SQL Query.
Now this doesn't mean you cant provide values, it means that you strongly specifies what types you want to process to the server.
Example from CodeProject
string commandText = "SELECT * FROM Customers "+
"WHERE Country=#CountryName";
SqlCommand cmd = new SqlCommand(commandText, conn);
cmd.Parameters.Add("#CountryName",countryName);
Instead of providing the countrName as concated string, you actually tell your Sql Command that you provide it as a parameter, which wont allow for any changes in the query itself.

Related

Kofax Transformation VBA Script to update field value

Im not really used on Kofax technologies and I have a Kofax Transformation form with fields on 2 different tabs. Here is an abstract of this form on which I have to interact on validation process.
Among those fields, I try to update the content of some of them with a validation rule on validation stage. I simply created a multi field validation rule and mapped correctly the fields.
It was proposed a basic script to check if the fields are valid or not. Based on this script I tried some logic. The Objective is to set the content of a field (which is empty and required) based on a basic condition on the second field.
My objective (later) will be to fill / update the fields based on the “Siret” field value with a database call.
My validation rule is the following : I check the “Siret” string length (it should be a 14 chars string). If this is true, I set the Validation to true and set the other field a value.
Private Sub Validation_Validate(ByVal ValItems As CASCADELib.CscXDocValidationItems, ByVal pXDoc As CASCADELib.CscXDocument, ByRef ErrDescription As String, ByRef ValidField As Boolean)
Dim strNAF As String
Dim strSiret As String
strNAF = ValItems.Item("NAF").Text
strSiret = ValItems.Item("Siret").Text
' enter your own validation rule here
If Len(strSiret) <= 14 Then
pXDoc.Fields.ItemByName("NAF").Text = "GOOD JOB"
ValidField = True
Else
ValidField = False
ErrDescription = "Describe the error here"
End If
End Sub
This validation should occurred when I press key enter on the “Siret” input field. It doesn’t seem to work actually. I wonder what is going wrong at this stage.
The best place to have a field's value changed depends on your use case. Kofax Transformation Modules follows a distinct pattern, and you should always try to follow it. For example, when you find yourself putting code in the AfterExtract method, you should reconsider -- there is almost always a better way. For a tl;dr just jump to Where to set values?
Natural Order
When you observe a field in Validation, here's what happens behind the scenes:
A locator detects information (i.e. one or more alternatives).
The locator is assigned to a field. The field will always hold the topmost alternative. By default, your field will be valid if there is only a single confident alternative, or if the second best alternative has a 20% lower confidence.
Your field may or or may not have a formatter associated with it. The formatter can make the field invalid (or even valid, if desired)
You field can be part of one or more validation rules. Validation rules can make fields valid or invalid, but they only fire if formatting was successful.
Example
Here's an example. Imagine you want to detect dates, and you want them in database format (yyyyMMdd). The dates on your documents are in US format (MM/dd/yyyy), and there are two, but the first one has "invoice date" as keyword besides it.
You configure a format locator to pick up dates, along with a keyword. The locator yields two alternatives: 01/20/2020 at 100% and 01/10/2020 at 0%.
Since the second alternative is not considered (below 10% threshold), the field will hold the first one.
Since the first alternative of the field in confident (80%), formatting is applied. The formatter changes the field's value to 20200120, and the field is still valid.
You configure a date validation method, and use said method for the field. You check whether the date is in the future or not, and let's say today's January 19. The field is now invalid since 20200120 is one day in the future.
This brings us back to the original question - where you you change a field's value? Changing it in a validation script is possible, but bear in mind that this breaks the natural order of things. Even worse, if formatting fails in the first place your code never executes.
Where to set values?
Depending on the use case, here are my recommendations:
You need to set a dynamic value, get values from an external system, or anything else that does not depend on another field/value in KTM: create a script locator and link it to your field. In script, create a new alternative and set it to the desired value. You can even make this dependent on other locators (since locators execute in sequence), but not on other fields (since fields are populated AFTER all locators have fired).
You want to normalize values. For example, days of the week should be represented by letters (SUN, MON, TUE, et cetera). You still want your users to enter numeric values (1 = SUN, 2 = MON, 3 = TUE). Create a script formatter and link it to your field. The benefit here is that you don't have to repeat yourself (for example in a script locator and later in validation).
You want to change values based on a user's interaction. You can use any event in validation (such as ButtonClicked, AfterFieldChanged or AfterFieldConfirmed), just keep in mind that some are not supported by Thin Client Validation (such as AfterFieldChanged). Keep in mind that you want to change the pointer and not the field itself since validation methods can be used by any number of fields. In your example, pXDoc.Fields.ItemByName("NAF").Text = "GOOD JOB" would violate this principle (i.e. making validation methods reusable -- your rule is now tied to the NAF field). Change the validation object instead; e.g. ValItems.ItemByName("NAF").Text = "GOOD JOB". Also keep in mind that changing a field at this point will NOT call the formatter associated with your field automatically, so make sure to provide a value that's already formatted. You can however call formatting in script manually.
Your requirement
Now, back to your original requirement:
The Objective is to set the content of a field (which is empty and required) based on a basic condition on the second field.
My objective (later) will be to fill /
update the fields based on the “Siret” field value with a database
call. My validation rule is the following : I check the “Siret” string
length (it should be a 14 chars string). If this is true, I set the
Validation to true and set the other field a value.
This depends on whether you want your users to be able to change the second field during validation. If not, go for a script locator. Note that script locators can have two subfields, and each subfield can be assigned to a different field.
If your users should be able to change it, go for multi-field script validation. Both fields should be part of the validation, and a length check should be the first thing you do. Then, if Siret has more than 14 characters, issue the database call.
A word about DRY and General Design
Not knowing your exact requirements, here are some thoughts about reusability. Let's say that Siret isn't always keyed in manually by users - in fact, a locator might pick up said text. This is where you want to create a specific method for calling the database and returning a result. Note that KTM has native support for relational databases, and you can even access this model in script.
Another alternative is to use local or remote fuzzy databases along with a database locator (again, if Siret is present on your document).
Thanks to Wolfgang and my team, I finally solved my issue.
Here is the code used to manage this :
Private Sub ValidationForm_AfterFieldConfirmed(ByVal pXDoc As CASCADELib.CscXDocument, ByVal pField As CASCADELib.CscXDocField)
Select Case pField.Name
Case "FieldNameInForm"
' simple check if field empty
If(pXDoc.Fields.ItemByName("FieldNameInForm").Text <> "") Then
completeForm(pXDoc, pXDoc.Fields.ItemByName("FieldNameInForm").Text)
End If
End Select
End Sub
Private Sub completeForm(ByVal pXDoc As CASCADELib.CscXDocument, ByVal myString As String)
'define required properties
Dim rs As ADODB.Recordset
Dim cn As ADODB.Connection
Dim sqlRequest As String
Dim dbHostServer As String
Dim dbUsername As String
Dim dbPassword As String
Dim dbName As String
Dim dbConnString As String
'Retrieve information for DB Connection (in ScriptVariables.xml)
dbHostServer = "localhost"
dbUsername = "root"
dbPassword = "root"
dbName = "mydatabase"
'build the connection string and open connection to database
dbConnString = "Provider=MSDASQL;Driver={MySQL ODBC 5.3 Unicode Driver};
dbConnString = dbConnString & "Server=" & dbHostServer & ";"
dbConnString = dbConnString & "UID=" & dbUsername & ";"
dbConnString = dbConnString & "PWD=" & dbPassword & ";"
dbConnString = dbConnString & "database=" & dbName
'Create recordset and set connection
'Prapare the db connection
Set rs = New ADODB.Recordset : : Set cn=New ADODB.Connection
cn.ConnectionString = dbConnString : cn.Open
'build query with concatenation
sqlRequest = "SELECT field1, field2, field3 FROM table"
sqlRequest = sqlRequest & " where fieldN= '" & myString & "'
'Execute the SQL request
Set rs = cn.Execute(sqlRequest)
' if the recordset returns a result
If (rs.EOF Or rs.BOF) Then
rs.MoveFirst
pXDoc.Fields.ItemByName("formField1").Text = CStr(rs.Fields("field1"))
Call ValidStrField("formField1", pXDoc.Fields.ItemByName("field1").Text, pXDoc)
pXDoc.Fields.ItemByName("formField2").Text = CStr(rs.Fields("field2"))
Call ValidStrField("formField2", pXDoc.Fields.ItemByName("field2").Text, pXDoc)
pXDoc.Fields.ItemByName("formField3").Text = CStr(rs.Fields("field3"))
Call ValidStrField("Commune", pXDoc.Fields.ItemByName("field3").Text, pXDoc)
'ifthe recordset do not return a value, we set to Undefined
Else
pXDoc.Fields.ItemByName("formField1").Text = "Undefined"
pXDoc.Fields.ItemByName("formField2").Text = "Undefined"
pXDoc.Fields.ItemByName("formField3").Text = "Undefined"
MsgBox("No result found in database")
End If
' Close connection & recordset
rs.Close : Set rs = Nothing
cn.Close : Set cn=Nothing
End Sub
' methods To validate Fields
Private Sub ValidStrField(ByVal StrItem As String,ByVal StrVal As String,ByVal pXDoc As CASCADELib.CscXDocument)
pXDoc.Fields.ItemByName(StrItem).Text = StrVal
pXDoc.Fields.ItemByName(StrItem).ExtractionConfident = True
pXDoc.Fields.ItemByName(StrItem).Confidence = 100.00
pXDoc.Fields.ItemByName(StrItem).ForcedValid = False
pXDoc.Fields.ItemByName(StrItem).Valid = True
End Sub
Private Sub UnValidStrField(ByVal StrItem As String,ByVal StrVal As String,ByVal pXDoc As CASCADELib.CscXDocument)
pXDoc.Fields.ItemByName(StrItem).Text = StrVal
pXDoc.Fields.ItemByName(StrItem).ExtractionConfident = False
pXDoc.Fields.ItemByName(StrItem).Confidence = 0.00
pXDoc.Fields.ItemByName(StrItem).ForcedValid = True
pXDoc.Fields.ItemByName(StrItem).Valid = False
End Sub

How to insert Listbox items in MS Access database in a single row

I have the following code:
Try
Dim queryString As String
queryString = "Insert into ServiceRecords([Personnel]) Values(#Personnels)"
command1 = New OleDbCommand(queryString, connection)
For i As Integer = 0 To Me.ListBox1.Items.Count + 1
command1.Parameters.AddWithValue("Personnels", ListBox1.Items(i))
command1.Parameters.Clear()
command1.ExecuteNonQuery()
Next
Catch ex As Exception
End Try
But I get the error below, and I don't know how to fix it. I think it happens because of my code.
And this is what I get:
Let's review.
First, as muffi suggested, use the Add method instead of .AddWithValue but instead of DBType use OLEDBType. There is not .String type in OleDBType. You will have to check your Access db to get the correct datatype. Probably VarChar. In addition, the parameter name should match the parameter name in your query string. With Access the position is the important thing but with other databases the name matters.
Second, as Charles suggested, change the plus one to minus 1. Most people start counting at one but computers usually start at zero so the upper index of the ListBox is one less than the Count (remember you are starting at zero not one).
Third , as Charles also pointed out it is wrong to clear the parameters before you execute. Then you would have nothing in your parameter. It is not necessary to clear them at all because you are overwriting the Value property with each iteration of your loop and I have set the name and datatype outside the loop because they stay the same. We don't want to reset properties for each iteration when they don't change.
command1.Parameters.Add("#Personnels", OleDbType.VarChar)
For i As Integer = 0 To ListBox1.Count -1
command1.Parameters("#Personnels").Value = ListBox1.Items(i)
command1.ExecuteNonQuery
Next

How to get a return value from a stored procedure in VB.NET

I have a stored procedure in SQL Server for generating transaction numbers.
Can anyone help me with how to call the Stored Procedure from VB.NET and how will i get the value that is returned from the procedure into the front end.
Regards,
George
I think you want something like this:
Public Sub Foo()
Using sql As New SqlClient.SqlConnection("YourConnection")
sql.Open()
Using cmd As New SqlClient.SqlCommand("YourSPName", sql)
cmd.CommandType = CommandType.StoredProcedure
Dim myReturnValue As String = cmd.ExecuteScalar
End Using
End Using
End Sub
Where myReturnValue will be what ever your output param in SQL is.
What kind of value is it you're returning? Will that value in turn result in another database action?
It might be best to return data instead of a single value.
For example if you were verifying the username and password for a potential login, instead of returning a simple true or false you would return the users information. No information returned means failed login.
This method has the advantage of minimising database requests, something which will have a serious effect if it is a common action.
Personally I've never needed to return a single value.

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.