2
\$\begingroup\$

Say I have the following code. What are your thoughts on questions 1-4 below?

  1. I think it's way too long when I have to list out most of the fields. Is there any better way of doing this?

  2. I build up the DB column name so it can be used later in my select and insert statement.

  3. This is where and how I use the string I build up in Section 2. Does it look good or ugly?

  4. Audit is required after inserting/update a new record, therefore I need to get the old and new value. There should be a better way to do this I guess, but I just have no idea how.

I'm open to any suggestion that can make this code block short and more readable.

Public Function InsertOrUpdateData(ByVal patient As Patient, ByVal sqlMode As SQLMode, ByVal affectedColumn As String, Optional ByVal updateString As String = "") As CreatePatientStatus
 Using conn As New SqlConnection(_myConnectionString)
 Dim cmd As New SqlCommand
 cmd.Connection = conn
 conn.Open()
 Try
 'SECTION 1
 'first I populate all the param before perform any action
 With cmd.Parameters
 .AddWithValue("@PCNO", patient.PCNO)
 .AddWithValue("@PName", patient.PName)
 .AddWithValue("@NRICType", patient.NRICType)
 .AddWithValue("@NRIC", patient.NRIC)
 'Another 80+ fields
 .AddWithValue("@Created_Date", Now)
 .AddWithValue("@Created_By", patient.CreateOrEditBy)
 .AddWithValue("@Edit_Date", Now)
 .AddWithValue("@Edit_By", patient.CreateOrEditBy)
 .AddWithValue("@FirstSeen", Now)
 End With
 'SECTION 2
 'this is list out all the affected DB column (It's FIXED, and always be)
 Dim sbAffectedDBFields As New StringBuilder
 sbAffectedDBFields.Append("PCNO,PName,NRICType,NRIC,")
 sbAffectedDBFields.Append("Anoter 80+ fields")
 sbAffectedDBFields.Append("Created_Date,Created_By,Edit_Date,Edit_By,FirstSeen")
 'oldValue and newValue to be used later part for audit
 Dim oldValue As String = String.Empty
 Dim newValue As String = String.Empty
 Dim dtOldValue As New DataTable
 Dim dtNewValue As New DataTable
 Dim sqlDataAdapter As New SqlDataAdapter(cmd)
 If sqlMode = sqlMode.INSERT Then
 'insert patient
 'SECTION 3
 cmd.CommandText = String.Format("INSERT INTO myPatient({0}) VALUES ({1})", sbAffectedDBFields.ToString, sbAffectedDBFields.Replace(",", ",@").ToString)
 cmd.ExecuteNonQuery()
 ElseIf sqlMode = sqlMode.UPDATE Then
 'get old value before update
 cmd.CommandText = String.Format("SELECT {0} FROM myPatient WHERE PCNO=@PCNO", sbAffectedDBFields.ToString)
 sqlDataAdapter.SelectCommand.Parameters.AddWithValue("@PCNO", patient.PCNO)
 sqlDataAdapter.Fill(dtOldValue)
 'dtOldValue will have only one row always
 'SECTION 4
 For Each dr As DataRow In dtOldValue.Rows
 For Each dc As DataColumn In dtOldValue.Columns
 oldValue &= dr(dc).ToString & ","
 Next
 Next
 oldValue.Remove(oldValue.Length - 1, 1) 'to remove last ","
 'update patient
 'updateString variable in a format of (field1=@field1,field2=@field2,fieldN=@fieldN)
 cmd.CommandText = String.Format("UPDATE myPatient SET {0} WHERE PCNO=@PCNO", updateString)
 cmd.ExecuteNonQuery()
 End If
 'get new value after update
 cmd.CommandText = String.Format("SELECT {0} FROM myPatient WHERE PCNO=@PCNO", sbAffectedDBFields.ToString)
 sqlDataAdapter.SelectCommand.Parameters.AddWithValue("@PCNO", patient.PCNO)
 sqlDataAdapter.Fill(dtNewValue)
 'dtNewValue will have only one row always
 For Each dr As DataRow In dtNewValue.Rows
 For Each dc As DataColumn In dtNewValue.Columns
 newValue &= dr(dc).ToString & ","
 Next
 Next
 newValue.Remove(newValue.Length - 1, 1) 'to remove last ","
 'param tableName,affectedColumn,oldvalue,newvalue
 Audit("myPatient", sbAffectedDBFields.ToString, oldValue, newValue)
 Return True
 Catch ex As Exception
 ErrorLog.Log(ex, ErrorLog.LogLevel.Message, MethodBase.GetCurrentMethod())
 Return False
 End Try
 End Using
End Function
Alex L
5,7832 gold badges26 silver badges69 bronze badges
asked Jul 17, 2014 at 10:04
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

I think it's way too long when I have to list out most of the fields. Is there any better way of doing this?

Extract the creation of the parameters in a method passing in the Patient object and the SqlParameterCollection of the SqlCommand object

Private Sub FillPatientParameterCollection(patient As Patient, parameters As SqlClient.SqlParameterCollection)
 Dim currentDate As Date = Now
 With parameters
 .AddWithValue("@PCNO", patient.PCNO)
 .AddWithValue("@PName", patient.PName)
 .AddWithValue("@NRICType", patient.NRICType)
 .AddWithValue("@NRIC", patient.NRIC)
 'Another 80+ fields
 .AddWithValue("@Created_Date", currentDate)
 .AddWithValue("@Created_By", patient.CreateOrEditBy)
 .AddWithValue("@Edit_Date", currentDate)
 .AddWithValue("@Edit_By", patient.CreateOrEditBy)
 .AddWithValue("@FirstSeen", currentDate)
 End With
End Sub

Also see the currentDatevariable. This ensures that the values for Created_Date,Edit_Date... are the same.

call the method like

FillPatientParameterCollection (patient, cmd.Parameters)

I build up the DB column name so it can be used later in my select and insert statement.

This should also be extracted in a own method but made accessible by a property. In this way it needs to be filled only once. This method should return a List(Of String) as this is easier to read and to maintain.

Private _AffectedDBFields As List(Of String)
Private ReadOnly Property AffectedDBFields As List(Of String)
 Get
 If (_AffectedDBFields.Count = 0) Then
 _AffectedDBFields = GetAffectedDBFields()
 End If
 Return _AffectedDBFields
 End Get
End Property
Private Function GetAffectedDBFields() As List(Of String)
 Dim affectedFields As New List(Of String)()
 affectedFields.Add("PCNO")
 affectedFields.Add("PName")
 affectedFields.Add("NRICType")
 affectedFields.Add("NRIC")
 affectedFields.Add("Anoter 80+ fields")
 affectedFields.Add("Created_Date")
 Return affectedFields
End Function 

This is where and how I use the string I build up in Section 2. Does it look good or ugly?

As you don't have a StringBuilderobject anymore you need to use the String.Join() method.

So this

cmd.CommandText = String.Format("INSERT INTO myPatient({0}) VALUES ({1})", sbAffectedDBFields.ToString, sbAffectedDBFields.Replace(",", ",@").ToString)

will become this

cmd.CommandText = String.Format("INSERT INTO myPatient({0}) VALUES ({1})", String.Join(",", AffectedDBFields), String.Join(",@", AffectedDBFields))

Audit is required after inserting/update a new record, therefore I need to get the old and new value. There should be a better way to do this I guess, but I just have no idea how.

You should extract the loop over the dtOldValues.Rows and also dtNewValues.Rows to a method, which again can return a List(Of String) instead of a String

Private Function GetDataRowValues(row As DataRow) As List(Of String)
 Dim values As New List(Of String)()
 For Each dc As DataColumn In row.Table.Columns
 Dim value As String = String.Empty
 If (Not IsDBNull(row(dc))) Then
 value = row(dc).ToString()
 End If
 values.Add(value)
 Next
 Return values
End Function

You then call this method with a guard condition like this

If dtOldValue.Rows.Count > 0 then
 oldValue = String.Join(",",GetDataRowValues(dtOldValues.Rows(0)))
End If

and

If dtNewValue.Rows.Count > 0 then
 newValue = String.Join(",",GetDataRowValues(dtNewValues.Rows(0)))
End If

At the end you need to call your Audit() like

Audit("myPatient", String.Join(",", AffectedDBFields), oldValue, newValue)

Additional remarks

I have integrated a check for DbNull values in the GetDataRowValues() method. You should always check for DbNull values.

answered Jul 18, 2014 at 6:05
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.