I have an application written in C#. The app writes records to a table in a SQL database. The table looks like this:
CREATE TABLE [dbo].[Products]
(
[Id] [UNIQUEIDENTIFIER] NOT NULL,
[ReferenceNumber] [INT] IDENTITY(0,1) NOT NULL,
[Name] [UNIQUEIDENTIFIER] NOT NULL,
[OnHand] [INT] NOT NULL,
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
Please note that the Id field is a Guid that is sent from the application. The ReferenceNumber is an auto-incremented field generated by the database. I'm writing records to the table via the stored procedure.
CREATE PROCEDURE [dbo].[ProductSave]
(#id UNIQUEIDENTIFIER,
#name NVARCHAR(MAX),
#onHand INT)
AS
DECLARE #referenceNumber INT
IF EXISTS (SELECT 1 FROM Products WHERE [Id] = #id)
BEGIN
UPDATE [Products]
SET [Name] = #name,
[OnHand] = #onHand
WHERE [Id] = #id
END
ELSE BEGIN
INSERT INTO [Products] ([Id], [Name], [OnHand])
VALUES (#id, #name, #onHand)
END
SET #referenceNumber = SCOPE_IDENTITY()
RETURN #referenceNumber
GO
The stored procedure returns the ReferenceNumber of the product that was inserted or updated. I need that ReferenceNumber in my C# code. At the same time, I need to send insert / update a batch of records at once.
At this time, my C# code looks like this:
public static void SaveProducts(List<Product> products)
{
var connString = ConfigurationManager.ConnectionStrings["productDb"].ConnectionString;
using (var conn = new SqlConnection(connString)
{
using (var cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[dbo].[ProductSave]";
cmd.Parameters.Add(new SqlParameter("#id", SqlDbType.UniqueIdentifier));
cmd.Parameters.Add(new SqlParameter("#name", SqlDbType.NVarChar));
cmd.Parameters.Add(new SqlParameter("#onHand", SqlDbType.Int));
foreach (var product in products)
{
cmd.Parameters[0].Value = product.Id;
cmd.Parameters[1].Value = product.Name;
cmd.Parameters[2].Value = product.OnHand;
}
cmd.ExecuteNonQuery();
// How do I assign the ReferenceNumber to each Product instance?
}
}
}
When I execute this, there's some weird behavior. It doesn't seem all products get saved. Why would that be? At the same time, and I believe related, is how do I get the ReferenceNumbers from the database and assign them to their products in code?
I really need to save the products in a batch. I can't save them one-at-a-time. At the same time, I do need to get the ReferenceNumber back from the database and assign it to the Product for use in code. What am I missing / doing wrong?
Related
I have a database table which also has a column of type image. The definition of the table is:
CREATE TABLE [dbo].[table_battery]
(
[id] [int] IDENTITY(1,1) NOT NULL,
[capacity] [int] NOT NULL,
[description] [varchar](100) NOT NULL,
[image] [image] NOT NULL, -- -----------<HERE IS IMAGE>------------
[price] [float] NOT NULL,
CONSTRAINT [PK_table_battery]
PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
The insert statement is:
SET IDENTITY_INSERT [dbo].[table_battery] OFF
INSERT INTO [dbo].[table_battery] ([capacity], [description], [image], [price])
VALUES ('Value1', 'Value2',
(SELECT BulkColumn
FROM OPENROWSET(BULK N'C:\Users\Juan\Desktop\Ingeniería Informática\2 año\2º Cuatrimestre\Programación Visual Avanzada\ProyectoFinal\AJMobile\AJMobile\scr\images\bateria.jpg', SINGLE_BLOB) AS CategoryImage),
'Value3')
And as you can see, here is full path. I want to do something like:
(SELECT BulkColumn
FROM OPENROWSET(BULK N'.\..\..\scr\images\bateria.jpg', SINGLE_BLOB) AS image)
But I do not know how can I achieve this.
Also, I have read that if you stores in database an image with relative path, It is sure you will get trouble when you will try to recover it from the datatable... So I would like to know how can I get it into my C# application for using it without problems.
Thank you.
it seems you would like to have SQLServer to load the file.
This can be quite a difficult scenario to handle: sql server cannot use relative path as you like.
moreover, it can happen that you sqlserver is not able to access the file you are trying to load: what if sqlserver is running on a different pc? what about file access right? (check this for some further info about this problem)
Since it seems you are calling the query from a c# application, it's much easier to have your application load the file, and pass its content as parameter to the query;
Something like this:
void Insert()
{
string query = #"
INSERT INTO [dbo].[table_battery]
([capacity], [description], [image], [price])
VALUES
(#capacity, #description, #fileContent, #price)";
// setup values you want to use for the query
int capacity = ... ;
string descr = ... ;
float price = ...;
string path = ... // you can use relative or absolute path here
byte[] fileContent = File.ReadAllBytes(path);
SqlConnection conn = null;// get connection some how;
using (conn)
{
using (SqlCommand command = new SqlCommand(query, conn))
{
command.Parameters.Add("#capacity", SqlDbType.Int).Value = capacity;
command.Parameters.Add("#description", SqlDbType.NVarChar).Value = descr;
command.Parameters.Add("#fileContent", SqlDbType.VarBinary).Value = fileContent;
command.Parameters.Add("#price", SqlDbType.Float).Value = price;
command.ExecuteNonQuery();
}
}
}
(disclaimer: not tested and not compiled, may require some edit)
How can I prevent the below error when executing a SQL Server transaction?
I'm trying to add a SupplierOrder and a VehicleRecord into a set of two database tables. I'm using the following:
Table SQL Structure:
CREATE TABLE VSI_VehicleRecords
(
VehicleRecordID INT IDENTITY(1,1) PRIMARY KEY,
StockNumber INT NOT NULL,
Status INT NOT NULL,
Make VARCHAR(50) NOT NULL,
Model VARCHAR(50) NOT NULL,
Colour VARCHAR(50) NOT NULL,
Spefication VARCHAR(255) NOT NULL
)
CREATE TABLE VSI_SupplierOrders
(
SupplierOrderID INT IDENTITY(1,1) PRIMARY KEY,
VehicleRecordID INT FOREIGN KEY REFERENCES VSI_VehicleRecords(VehicleRecordID) UNIQUE,
Timestamp
)
I've written a utility method which runs a set of Sql queries as a transaction:
C# Execution of a transaction:
SqlTransaction _Transaction;
OpenConnection();
_Transaction = __Connection.BeginTransaction();
try
{
for (int i = 0; i < Commands.Length; i++)
{
Commands[i].Connection = __Connection;
Commands[i].Transaction = _Transaction;
Commands[i].ExecuteNonQuery();
}
_Transaction.Commit();
return true;
}
catch (SqlException e)
{
_Transaction.Rollback();
}
SQL commands to be executed by the above function:
SqlCommand[] _Commands = new SqlCommand[2];
string _InsertVehicleQuery = "INSERT INTO VSI_VehicleRecords(StockNumber,Status,Make,Model,Colour,Spefication) VALUES (#StockNumber, #Status, #Make, #Model, #Colour, #Specification);";
SqlCommand _InsertVehicleCommand = new SqlCommand(_InsertVehicleQuery);
_InsertVehicleCommand.Parameters.AddWithValue("#StockNumber", __StockNumber);
_InsertVehicleCommand.Parameters.AddWithValue("#Status", __Status);
_InsertVehicleCommand.Parameters.AddWithValue("#Make", Make);
_InsertVehicleCommand.Parameters.AddWithValue("#Model", Model);
_InsertVehicleCommand.Parameters.AddWithValue("#Colour", Colour);
_InsertVehicleCommand.Parameters.AddWithValue("#Specification", Specification);
_Commands[0] = _InsertVehicleCommand;
string _InsertSupplierOrderQuery = "INSERT INTO VSI_SupplierOrders(VehicleRecordID) VALUES (#VehicleRecordID);";
SqlCommand _InsertSupplierOrderCommand = new SqlCommand(_InsertSupplierOrderQuery);
_InsertSupplierOrderCommand.Parameters.AddWithValue("#VehicleRecordID", _VehicleRecordID);
_Commands[1] = _InsertSupplierOrderCommand;
DataUtility.NonQueryTransaction(_Commands);
However I get the following error:
*The INSERT statement conflicted with the FOREIGN KEY constraint "FK__VSI_Suppl_Vehic_5165187F". The conflict occurred in database
"jack_test", table "dbo.VSI_VehicleRecords", column 'VehicleRecordID'.*
You need to get the VehicleRecordID from your first query - You currently aren't setting _VehicleRecordID to any value
To do that you need to append ;SELECT Scope_Identity() after your insert SQL and execute the command via ExecuteScalar
However, it may be easier and neater to create a stored procedure that takes all the parameters for both queries and does the work on the SQL Server
eg
create proc CreateRecordAndSupplier
(
#Stocknumber int,
... (etc)
)
as
begin
declare #VR int
INSERT INTO VSI_VehicleRecords(StockNumber,Status,Make,Model,Colour,Spefication)
VALUES (#StockNumber, #Status, #Make, #Model, #Colour, #Specification);
select #VR = Scope_Identity();
INSERT INTO VSI_SupplierOrders(VehicleRecordID) VALUES (#VR)
end
I am trying to insert Data into my Sql-Server database though C#. I'm Calling a stored procedure and then would like it to add. I'm not sure what changes to make, but ultimately i would like it done in the stored procedure.
My Stored procedure now:
CREATE PROCEDURE [dbo].[InsertTagProcdure]
#TagID int,
#Value nvarchar(200),
#TagCount nvarchar(200)
AS
IF NOT EXISTS (SELECT NULL FROM Tag
WHERE #TagID = #TagID)
BEGIN
INSERT INTO
Tag
(TagID,Value,TagCount)
VALUES
(#TagID,#Value,#TagCount)
END
And my C# Code:
int TagID = int.Parse(txtTagID.Text); //This should fall away so auto increment.
String Value = txtValue.Text;
int TagCount = int.Parse(txtCount.Text);
using (var conn = new SqlConnection(Properties.Settings.Default.DBConnectionString))
using (var cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = "InsertTagProcdure";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#TagID", TagID);
cmd.Parameters.AddWithValue("#Value", Value);
cmd.Parameters.AddWithValue("#TagCount", TagCount);
cmd.ExecuteNonQuery();
}
The Table Create i used: //Cant change this its what the boss gave me.
CREATE TABLE [dbo].[Tag](
[TagID] [int] IDENTITY(1,1) NOT NULL,
[Value] [varchar](200) NOT NULL,
[TagCount] [varchar](200) NULL,
CONSTRAINT [PK_Tag] PRIMARY KEY CLUSTERED
(
[TagID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Ideally you would just make TagID an identity field by changing the table definition. If you can't do that, next best would be:
CREATE PROCEDURE [dbo].[InsertTagProcdure]
#Value nvarchar(200),
#TagCount nvarchar(200)
AS
BEGIN
BEGIN TRANSACTION
DECLARE #TagID int;
SELECT #TagID = coalesce((select max(TagID) + 1 from Tag), 1)
COMMIT
INSERT INTO
Tag
(TagID,Value,TagCount)
VALUES
(#TagID,#Value,#TagCount)
END
The transaction ensures that you don't end up with unique TagIDs and the coalesce handles the special case where the table is empty and gives an initial value of 1.
EDIT:
Based on the change to your original question, the table already has an identity column so your stored procedure should be:
CREATE PROCEDURE [dbo].[InsertTagProcdure]
#Value nvarchar(200),
#TagCount nvarchar(200)
AS
BEGIN
INSERT INTO Tag (Value,TagCount) VALUES (#Value,#TagCount)
END
and your C# code should be
int TagID = int.Parse(txtTagID.Text); //This should fall away so auto increment.
String Value = txtValue.Text;
int TagCount = int.Parse(txtCount.Text);
using (var conn = new SqlConnection(Properties.Settings.Default.DBConnectionString))
using (var cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = "InsertTagProcdure";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#Value", Value);
cmd.Parameters.AddWithValue("#TagCount", TagCount);
cmd.ExecuteNonQuery();
}
You want to set the tag table up so that it uses the identity property. See here: http://msdn.microsoft.com/en-us/library/aa933196(v=sql.80).aspx.
Then you can drop TagId from the procedure, the insert statement in the procedure and the c# code.
It then becomes something like this:
CREATE PROCEDURE [dbo].[InsertTagProcdure]
#Value nvarchar(200),
#TagCount nvarchar(200)
AS
BEGIN
INSERT INTO
Tag
(Value,TagCount)
VALUES
(#Value,#TagCount)
END
C# Code:
String Value = txtValue.Text;
int TagCount = int.Parse(txtCount.Text);
using (var conn = new SqlConnection(Properties.Settings.Default.DBConnectionString))
using (var cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = "InsertTagProcdure";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#Value", Value);
cmd.Parameters.AddWithValue("#TagCount", TagCount);
cmd.ExecuteNonQuery();
}
the statement WHERE #TagID = #TagID will always be true, because your comparing the same values.
I think your looking for this (assuming TagID is your AUTO-ID field);
CREATE PROCEDURE [dbo].[InsertTagProcdure]
#TagID int,
#Value nvarchar(200),
#TagCount nvarchar(200)
AS
BEGIN
IF NOT EXISTS (SELECT TagID FROM Tag WHERE TagID = #TagID)
BEGIN
INSERT INTO
Tag
(Value,TagCount)
VALUES
(#Value,#TagCount)
SET #TagID = ##IDENTITY
END
ELSE
BEGIN
UPDATE Tag
SET Value=#Value,
TagCount=#TagCount
WHERE TagID = #TagID
END
RETURN #TagID
END
You have to turn on 'Identity specification' in SQL Server on the appropriate column. Go to table design mode, select the column and in the bottom properties screen there is a property called 'Identity specification'.
After this, you can omit the TagId in your stored procedure. Sql Server will automatically assign an id to a new record.
If you want to know what that newly inserted id is (which I assume is your next question), you can use the function SCOPE_IDENTITY() in your stored procedure, which returns the id of the newly inserted record.
I have stored procedure which takes xml as an input .
The C# code to execute the SP is
using (SqlConnection conn = new SqlConnection(connString))
{
try
{
conn.Open();
SqlCommand command = new SqlCommand("sp_Configure_Users", conn);
command.CommandType = CommandType.StoredProcedure;
var xmlData = new SqlParameter(parameterName: "#XMLDATA", value: GenerateXML(_userDetails));
command.Parameters.Add(xmlData);
count = command.ExecuteNonQuery();
if (count > 0)
{
return count;
}
else
{
return 0;
}
}
catch (SqlException )
{
}
}
The method GenerateXML returns a string which is in the form of xml data .In my stored proc i have removed SET NOCOUNT ON to return me the actual no of rows when inserted ,deleted or updated .If i run my query with the sample xml data :-
<UserCollection>
<InsertList><Users User_Id="438" First_Name="Praveen" Middle_Name="" Last_Name="Kumar" Designation_Id="1" Email="" Contact_Number="96533" Updated_By="pkumar" />
</InsertList>
<UpdateList></UpdateList>
<DeleteList></DeleteList>
</UserCollection>
The value returned is 3 instead of 1 as in my collection im passing only a single row which needs to be inserted .Since there are no rows which are updated or deleted .How can the SP return 3 when it has to return only 1 ??
The stored Proc is
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER PROCEDURE [dbo].[sp_Configure_Users]
#XMLDATA xml
AS
DECLARE #Users_Staging Table(
[User_Id] [int] NOT NULL,
[First_Name] [varchar](200) NOT NULL,
[Middle_Name] [varchar](200) NULL,
[Last_Name] [varchar](200) NOT NULL,
[Designation_Id] [int] NOT NULL,
[Email] [varchar](250) NOT NULL,
[Contact_Number] [varchar](50) NOT NULL,
[Updated_By] [varchar](255) NOT NULL
)
BEGIN
Set nocount off
Go
BEGIN TRY
BEGIN TRANSACTION
DECLARE #CurrDate datetime;
SELECT #CurrDate = GETDATE();
-- Insert statements for procedure
INSERT INTO Users
([User_Id]
,[First_Name]
,[Middle_Name]
,[Last_Name]
,[Designation_Id]
,[Email]
,[Contact_Number]
,[Updated_By]
,[Updated_Date]
)
Select
XMLDATA.item.value('#User_Id[1]', 'int') AS User_Id,
XMLDATA.item.value('#First_Name[1]', 'varchar(200)') AS First_Name,
XMLDATA.item.value('#Middle_Name[1]', 'varchar(200)') AS Middle_Name,
XMLDATA.item.value('#Last_Name[1]', 'varchar(200)') AS Last_Name,
XMLDATA.item.value('#Designation_Id[1]', 'int') AS Designation_Id,
XMLDATA.item.value('#Email[1]', 'varchar(250)') AS Email,
XMLDATA.item.value('#Contact_Number[1]', 'varchar(50)') AS Contact_Number,
XMLDATA.item.value('#Updated_By[1]', 'varchar(255)') AS Updated_By,
#CurrDate
FROM #XMLDATA.nodes('//UserCollection/InsertList/Users') AS XMLDATA(item)
-- Update statements for procedure
INSERT INTO #Users_Staging
([User_Id]
,[First_Name]
,[Middle_Name]
,[Last_Name]
,[Designation_Id]
,[Email]
,[Contact_Number]
,[Updated_By])
Select
XMLDATA.item.value('#User_Id[1]', 'int') AS User_Id,
XMLDATA.item.value('#First_Name[1]', 'varchar(200)') AS First_Name,
XMLDATA.item.value('#Middle_Name[1]', 'varchar(200)') AS Middle_Name,
XMLDATA.item.value('#Last_Name[1]', 'varchar(200)') AS Last_Name,
XMLDATA.item.value('#Designation_Id[1]', 'int') AS Designation_Id,
XMLDATA.item.value('#Email[1]', 'varchar(250)') AS Email,
XMLDATA.item.value('#Contact_Number[1]', 'varchar(50)') AS Contact_Number,
XMLDATA.item.value('#Updated_By[1]', 'varchar(255)') AS Updated_By
FROM #XMLDATA.nodes('//UserCollection/InsertList/Users') AS XMLDATA(item)
UPDATE Users
SET First_Name=stgusr.First_Name,
Middle_Name=stgusr.Middle_Name,
Last_Name=stgusr.Last_Name,
Email=stgusr.Email,
Contact_Number=stgusr.Contact_Number,
Updated_By=stgusr.Updated_By,
Updated_Date=#CurrDate
FROM Users usr INNER JOIN
#Users_Staging stgusr
ON usr.User_Id=stgusr.User_Id
-- Delete statements for procedure
DELETE Users
WHERE User_Id
IN (Select
XMLDATA.item.value('#User_Id[1]', 'int') AS User_Id
FROM #XMLDATA.nodes('//UserCollection/DeleteList/Users') AS XMLDATA(item))
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
--EXEC sp_rethrow_error;
ROLLBACK TRANSACTION;
END CATCH;
END
If you have triggers on the tables updated/inserted by your stored procedure, they will affect the affected row count:
For UPDATE, INSERT, and DELETE statements, the return value is the
number of rows affected by the command. When a trigger exists on a
table being inserted or updated, the return value includes the number
of rows affected by both the insert or update operation and the number
of rows affected by the trigger or triggers. For all other types of
statements, the return value is -1. If a rollback occurs, the return
value is also -1.
Ref.
Suggest you post the stored procedure, and schema/triggers for the tables involved.
Why doesn't this work? I get an error saying the number cannot be infinity. However, I had to take this away from an insert statement so that it doesn't post entries twice.
Where do I have to incorporate this piece of code to get it to allow my code to loop as a new ID?
cmd = new SqlCommand(#"SELECT CAST(scope_identity() as int)", con);
int aID = Convert.ToInt32(cmd.ExecuteScalar());
In general you can have a stored procedure to do the INSERT and return the last inserted identity with an out parameter, as you can see in an example here: http://www.objectreference.net/post/SCOPE_IDENTITY()-return-the-id-from-the-database-on-insert.aspx
CREATE PROCEDURE [dbo].[Customer_Insert]
#Name VARCHAR(255),
#Email VARCHAR(255),
#Phone VARCHAR(255),
#CustomerID INT OUTPUT
AS
BEGIN
INSERT INTO dbo.Customer ([Name], Email, Phone)
VALUES (#Name,#Email,#Phone)
SET #CustomerID = CAST(SCOPE_IDENTITY() AS INT)
END