Thanks William, that was the ticket. Had to assign the name property on both ends [DataTable.TableName].
On a side note here: There appears to be some school of thought (no offense Marc) that the following statement is always true:
"Everything in the world can and should be made into an object."
It is, simply, not always true. There are cases where you cannot cram your 'object' into any cookie-cutter or class no matter how you try to customize it. For me to objectize this beast, I'd have to create roughly 4,000 objects. I don't have time to do that, and yet this project must run as a service. Frankly I think the developers at MickeySoft need to get out more into the real world, and see for themselves that although theory is great it does not represent true-life challenges. I am all for working with objects for the obvious benefits, but there is a reality that universals do not exist. In my trade, even 'most of the time' rarely happens.
Before they push out a new technology set, and cease support of an old one, they need to make sure that the new technology has the same capabilities the old one had.
For the record: The people who believe the above statement to be true are also the same people who would refuse to take the project I'm currently working on.
Just the same -- thank you both for your time, efforts and opinions!
I'm trying to create a WCF function that will return a table to my console testing app. I am a total noob. The data is 2-dimensional and looks like this:
23 Letter
42 Another Letter
43 Document
...
Here's what I'm trying to do:
<ServiceContract()> _
Public Interface ILetterWriter
<OperationContract()> _
Function GetLetter(ByVal LetterID As String, ByVal StateID As String, ByVal CompID As String, ByVal tblVar As DataTable) As String
<OperationContract()> _
Function GetLetterNames(ByVal DepartmentType As Integer) As DataTable
End Interface
Public Function GetLetterNames(ByVal DepartmentType As Integer) As DataTable Implements ILetterWriter.GetLetterNames
Dim SQLCon As New SqlClient.SqlConnection
Dim SQLCmd As New SqlClient.SqlCommand
'Connect to the database
SQLCon.ConnectionString = "Data Source=VMSQL08-SRV1;Initial Catalog=DotNetDev;User ID=aiis_pgmr;Password=ag58102;"
SQLCon.Open()
'Grab the stored procedure, which returns the letter names
SQLCmd.CommandText = "sp_GetLetters"
SQLCmd.CommandType = CommandType.StoredProcedure
SQLCmd.Connection = SQLCon
'Pass the parameters
SQLCmd.Parameters.AddWithValue("#LetterType", DepartmentType)
'Execute the stored procedure, fill the datatable from a data adapter
GetLetterNames = New DataTable
GetLetterNames.Load(SQLCmd.ExecuteReader)
'Shut it down
SQLCmd.Dispose()
SQLCon.Close()
SQLCon.Dispose()
End Function
...Of course, it won't work. I just need to get the WCF to pass a basic table to my console application. The execute SQL seems to work just fine, I just can't get the data back to my application.
Any help would be greatly appreciated.
Thanks,
Jason
I agree with the other poster.
However, if you are returning a DataTable, you have to set the "Name" property of the DataTable if you want to return it from a WCF Service.
Related
I haven't done any serious programming in years, and I don't have much experience manipulating SQL data indirectly anyway, but I'm trying to create a program for my employer and running into confusion. Many of the answers here and college books i have are helpful, but just when I think I understand what's going on, I get lost again, often because people use variables and column names that are identical, leaving it hard to figure out what is going where.
So let's say I have a database called Attendance on SQL server localhost. Inside is a table called employees which consists of the columns: employee_id, name_last, name_first, and points. The first three are varchar and the last is a decimal(2,1). Using Visual Studio for Visual Basic, I've created a program which contains several textboxes. The user enters the employee id which becomes var_empid when they hit the Load button.
How would I proceed so that the program executes an SQL query which pulls the name_last and name_first from the table where the employee_id matches the var_empid as input by the user and puts that data into the var_last_name and var_first_name variables?
Secondly, if the user entered into other textboxes information that became var_empid, var_last_name, var_first_name and then clicked the Add Employee button, how I would i proceed so that the information added by the user is written to the SQL table?
For clarification, moving data between textboxes and variables isn't the problem. I can do that all day. It's moving the variables between the VB and SQL that is causing me problems. I realize this is basic stuff, but any help would be great.
To answer your second request, try to execute the following query:
Dim Query = "INSERT INTO Attendance (name_first, name_last, points) VALUES(var_first_name, var_last_name, var_points)"
Note that i did not insert the var_empid because if you have created your table correctly, this id should be an auto-generated primary key that increments itself automatically.
It's not easy to start because at first you see so many different names and technologies and they all seem to promise, more or less, the same thing. Especially if you want to build a database-connected application, and start doing basic stuff like CRUD operations (inserts, updates, deletes...), it's easy to get confused.
Start reading here about ADO.NET Architecture. You hopefully will understand something more about DataSet, Linq To SQL, Entity Framework, but probably not much. This is why I strongly suggest to take a few days and slowly watch the tutorial videos by Beth Massi, on VB.Net and Data.
On this page, How Do I Videos for Visual Basic, you will find a lot of useful information to start building simple but very effective applications and database.
Be sure to watch Forms over Data Video Series, Visual Basic 2008 Forms over Data Videos (using DataSets) and then Language Integrated Query (LINQ) Series (using LINQ To SQL where you'll understand why in your vb.net application your object variables have the same name as your database columns). FInally you can take a look at the Entity Framework tutorial (that you will find very similar to Linq To SQL).
After these basic tutorials you'll be able to choose your path and start programming. Once you grasp the basic concepts it's a lot easier to search and understand what you find on the internet.
This is more than what you asked for, because I'm trying to push you into some good practices at the same time:
Public Class Employee
Public Property ID As String
Public Property Points As Double 'why is this a decimal(2,1)?
Public Property LastName As String
Public Property FirstName As String
Public Shared Function FromDataRow(ByVal data As IDataRecord) As Employee
Dim result As New Employee()
result.ID = CDbl(data("ID"))
result.LastName = CStr(data("LastName"))
result.FirstName = CStr(data("FirstName"))
Return result
End Function
End Class
Public Module DataLayer
'Check www.connectionstring.com for more info on connection strings
Private Property ConnectionString As String = "database connection string here"
Private Iterator Function GetRecords(ByVal sql As String, ByVal addParams As Action(Of SqlParameterCollection)) As IEnumerable(Of IDataRecord)
Using cn As New SqlConnection(ConnectionString), _
cmd As New SqlCommand(sql, cn)
addParams(cmd.Parameters)
cn.Open()
Using rdr As SqlDataReader = cmd.ExecuteReader()
While rdr.Read)
Yield Return rdr
End While
End Using
End Using
End Function
Private Function GetRecords(Of T)(ByVal sql As String, ByVal addParams As Action(Of SqlParameterCollection), ByVal translate As Function(Of IDataRecord, T)) As IEnumerable(Of T)
Return GetRecords(sql, addParams).Select(translate)
End Function
Public Function GetEmployeeData(ByVal EmployeeID As String) As Employee
Dim sql As String = _
"SELECT employee_id, name_last, name_first " & _
"FROM employees " & _
"WHERE employee_id= #ID"
Return GetRecords(sql, _
Sub(p) p.Add("#ID", SqlDbType.NVarChar, 10).Value = EmployeeID, _
Employee.FromDataRow).First()
End Function
End Class
Please see the code below:
Public Function ExecuteDynamicQuery(ByVal strSQL As String, ByVal list As List(Of clsType), ByVal tyType As clsType) As List(Of clsType) Implements IGenie.ExecuteDynamicQuery
Dim objParameterValues As New clsParameterValues
Dim iConnectionBLL As iConnectionBLL
Dim objCon As DbConnection
Dim objDR As DbDataReader
Dim paramValues() As DbParameter
objParameterValues = New clsParameterValues
iConnectionBLL = New clsConnectionBLL()
objCon = iConnectionBLL.getDatabaseTypeByDescription("Genie2")
Using objCon
paramValues = objParameterValues.getParameterValues
objDR = clsDatabaseHelper.ExecuteReader(objCon, CommandType.Text, strSQL, paramValues)
Do While objDR.Read
Dim tyType2 As clsType = tyType
tyType.PopulateDataReader(objDR)
list.Add(tyType2)
Loop
objDR.Close()
Return list
End Using
End Function
An SQL statement is passed to the function along with clsType (the base type). A list of types is returned e.g. a list of Persons. For example, in this case strSQL = "SELECT * FROM Persons". A list of 500 persons is returned but they are all the same person (the last person added to the list). I realise this is because the list is referncing the same object for each entry. How do I change this?
This is a situation where making the method generic would be useful. For instance:
Public Function MyGenericMethod(Of T As New)() As List(Of T)
Dim results As New List(Of T)()
For i As Integer = 0 To 9
Dim item As New T()
' Populate item ...
results.Add(item)
Next
Return results
End Function
For what it's worth, though, I see people trying do this kind of thing often, and it never sits well with me. I'm always the first one in line to suggest that common code should be encapsulated and not duplicated all over the place, but, I've never been convinced that creating some sort of data access layer that encapsulates the calls to ADO, but doesn't also encapsulate the SQL, is a good idea.
Consider for a moment that ADO, is in-and-of-itself an encapsulation of that part of the data-access layer. Sure, it can take a few more lines of code than you might like to execute a simple SQL command, but that extra complexity is there for a reason. It's necessary in order to support all of the features of the data source. If you try to simplify it, inevitably, you will one day need to use some other feature of the data source, but it won't be supported by your simplified interface. In my opinion, each data access method should use all of the necessary ADO objects directly rather than trying to some how create some common methods to do that. Yes, that does mean that many of your data access methods will be very similar in structure, but I think you'll be happier in the long run.
I've reduced your original code. The following sample is functionally equivalent to what you posted. Without knowing more about your types, it will hard to give you anything more than this, but maybe the reduction will make the code clear enough for you to spot a solution:
Public Function ExecuteDynamicQuery(ByVal sql As String, ByVal list As List(Of clsType), ByVal type As clsType) As List(Of clsType) Implements IGenie.ExecuteDynamicQuery
Dim paramValues() As DbParameter = New clsParameterValues().getParameterValues()
Using conn As DbConnection = iConnectionBLL.getDatabaseTypeByDescription("Genie2"), _
rdr As DbDataReader = clsDatabaseHelper.ExecuteReader(conn, CommandType.Text, sql, paramValues)
While rdr.Read()
type.PopulateDataReader(rdr)
list.Add(type)
End While
Return list
End Using
End Function
There are a few additional bits of advice I can give you:
You must have some way to accept parameter information for your query that is separate from the query itself. The ExecuteReader() method that you call supports this, but you only ever pass it an empty array. Fix this, or you will get hacked.
A implementation that uses Generics (as posted in another answer) would be much simpler and cleaner. The Genie interface you're relying doesn't seem to be adding much value. You'll likely do better starting over with a system that understands generics.
The problem of re-using the same object over and over can be fixed by creating a new object inside the loop. As written, the only way to do that is with a New clsType (and it seems you may have Option Strict Off, such that this could blow up on you at run time), through some messy reflection code, a switch to using generics as suggested in #2, or a by accepting a Func(Of clsType) delegate that can build the new object for you.
I was wondering what is the most basic way to avoid the following.
con.ConnectionString = connection_String
con.Open()
cmd.Connection = con
'database interaction here
cmd.Close()
I keep making those lines all over in my project, but I figure there has to be a better way to save on typing this over and over. It makes the code look even more sloppy than it already is!
Ended up with this, works well for me. Thanks for the help :)
Public Sub connectionState()
If con.State = 0 Then
con.ConnectionString = connection_String
con.Open()
cmd.Connection = con
Else
con.Close()
End If
End Sub
This is where a lot of programmers are tempted to create a "database layer" with a variations on method signatures that look like this:
Public DataSet ExecuteSQL(ByVal sql As String) As DataSet
That allows you to isolate all that boilerplate connection code in one place. An sql command string goes in, and data comes out. Easy.
Don't do it!
This is headed in the right direction, but has one very big flaw: it forces you to use string manipulation to substitute parameter values into your sql queries. That leads to horrible sql injection security vulnerabilities.
Instead, make sure you include some mechanism in your methods to prompt for the sql parameters separately. This usually comes in the form of an additional argument to the function, and could be as simple as an array of KeyValuePairs. If you're comfortable with lambdas, my preferred pattern looks like this:
Public Iterator Function GetData(Of T)(ByVal sql As String, ByVal addParameters As Action(Of SqlParameterCollection), ByVal translate As Func(Of IDatarecord, T)) As IEnumerable(Of T)
Using cn As New SqlConnection("connection string"), _
cmd As New SqlCommand(sql, cn)
addParameters(cmd.Parameters)
cn.Open()
Using rdr As SqlDataReader = cmd.ExecuteReader()
While rdr.Read()
Yield(translate(rdr))
End While
End Using
End Using
End Function
To call that function, you would do something like this:
Dim bigCustomers = GetData("SELECT * FROM Customers WHERE SalesTotal > #MinSalesTotal", _
Sub(p) p.Add("#MinSalesTotal", SqlDbType.Decimal, 1000000), _
MyCustomerClass.FromIDataRecord)
You can try creating a class ( a singleton class ), and write the database connection syntax code and exceptions in that class, then call one object to the main class to create the database connection, that's the best way in performance and keep writing the same code on and on...
You can use just using block, using execute dispose on non managed object in the end of treatment.
Link : http://msdn.microsoft.com/en-us/library/htd05whh(v=vs.80).aspx
I'm trying to retrieve the ##IDENTITY value from an access database after I add a row to it. However, instead of using hard coded connections, I'm using the DataSet wizard. Unfortunately, due to the Jet engine, I can't do more than one command at a time. I tried to select the ##IDENTITY in a separate command but unfortunately, I guess it counts as a different command as it returns a 0 each time.
My question is, is there a way I can use the GUI/IDE to retrieve the ##IDENTITY or do I have hard code the connection, command, query values and obtain the value that way.
Thanks.
You asked an interesting questions which I did not know the answer to. After some research I found this and it seems promising. However I have never used it and cannot substantiate if and how well it works.
I also don't know the DataSet wizard that well, but vaguely recalls that it generates an OleDbAdapter object, hopefully it exposes a RowUpdated event which you can hook this code to.
I have pasted the interesting MSDN code parts here:
(Link to full documentation)
' Create the INSERT command for the new category.
adapter.InsertCommand = New OleDbCommand( _
"INSERT INTO Categories (CategoryName) Values(?)", connection)
adapter.InsertCommand.CommandType = CommandType.Text
Then hook-up and event listener to RowUpdated
' Include an event to fill in the Autonumber value.
AddHandler adapter.RowUpdated, _
New OleDbRowUpdatedEventHandler(AddressOf OnRowUpdated)
Obtain the ##Identity with the same connection.
Private Shared Sub OnRowUpdated( _
ByVal sender As Object, ByVal e As OleDbRowUpdatedEventArgs)
' Conditionally execute this code block on inserts only.
If e.StatementType = StatementType.Insert Then
' Retrieve the Autonumber and store it in the CategoryID column.
Dim cmdNewID As New OleDbCommand("SELECT ##IDENTITY", _
connection)
e.Row("CategoryID") = CInt(cmdNewID.ExecuteScalar)
e.Status = UpdateStatus.SkipCurrentRow
End If
End Sub
Hopefully this helps you out or least someone will set me straight :)
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.