ADO.NET DataAdapters - Ensuring one Table is Update Before Another - c#

I am working on an application that inserts data into two separate, but related tables, when a user hits a submit button.
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_details_report"
However, because I have a foreign key restraint on one table, I have been encountering difficulties. I need the rows for one table (report_summary) to be inserted first, because of foreign key restraints, before even a single row is added to the other table (report_details). However, I would also like them to be handled in a single transaction, there might be some data integrity issues of one insert was to succeed and the other fail. How can I resolve this?
The T-SQL
CREATE TABLE [dbo].[report_summary] (
[report_id] INT NOT NULL,
[inspector] INT NOT NULL,
[employee] INT NOT NULL,
[room] INT NOT NULL,
[date] DATE NOT NULL,
[score] INT NOT NULL,
[locationID] INT NOT NULL,
PRIMARY KEY CLUSTERED ([report_id] ASC),
CONSTRAINT [FK_report_summary_locations] FOREIGN KEY ([locationID]) REFERENCES [dbo].[locations] ([locID])
);
CREATE TABLE [dbo].[report_details] (
[reportID] INT NOT NULL,
[itemID] INT NOT NULL,
[points] INT NOT NULL,
[comments] NTEXT NULL,
PRIMARY KEY CLUSTERED ([itemID] ASC, [reportID] ASC),
CONSTRAINT [FK_details_items] FOREIGN KEY ([itemID]) REFERENCES [dbo].[items] ([itemID]),
CONSTRAINT [FK_details_report] FOREIGN KEY ([reportID]) REFERENCES [dbo].[report_summary] ([report_id])
);
and some of my C#
private void submitData(object sender, RoutedEventArgs e)
{
SqlTransaction tran = con.BeginTransaction();
reportAdapter.InsertCommand.Transaction = tran;
SqlCommand query = new SqlCommand("SELECT report_id FROM dbo.report_summary ORDER by report_id DESC", con);
query.Transaction = tran;
int nextReportID;
if (query.ExecuteScalar() != null)
{
nextReportID = (int)query.ExecuteScalar() + 1;
}
else
{
nextReportID = 1;
}
detailsAdapter.InsertCommand.Transaction = tran;
DataRow reportRow = ds.Tables["Reports"].NewRow();
reportRow["report_id"] = nextReportID;
DataRowView inspectorSelection = (DataRowView)inspectorBox.SelectedItem;
reportRow["inspector"] = Int16.Parse(inspectorSelection["empID"].ToString());
DataRowView empSelection = (DataRowView)employeeBox.SelectedItem;
reportRow["employee"] = Int16.Parse(inspectorSelection["empID"].ToString());
DataRowView locationSelection = (DataRowView)locationComboBox.SelectedItem;
reportRow["locationID"] = Int16.Parse(locationSelection["locID"].ToString());
reportRow["room"] = Int16.Parse(roomTextBox.Text);
reportRow["date"] = DateTime.Now.ToString("yyy-MM-dd");
reportRow["score"] = currentPoints;
ds.Tables["Reports"].Rows.Add(reportRow);
// update report_details dataset
foreach (DataRow row in ds.Tables["Grid"].Rows)
{
DataRow reportDetailsRow = ds.Tables["Details"].NewRow();
reportDetailsRow["reportID"] = nextReportID;
reportDetailsRow["itemID"] = row["ID"];
reportDetailsRow["points"] = row["Current"];
reportDetailsRow["comments"] = row["Comments"];
ds.Tables["Details"].Rows.Add(reportDetailsRow);
}
// update tables as single transaction
try
{
reportAdapter.Update(ds, "Reports");
detailsAdapter.Update(ds, "Details");
tran.Commit();
MessageBox.Show("Data Inserted");
}
catch (SqlException sqlEr)
{
MessageBox.Show(sqlEr.Message);
tran.Rollback();
}
}
I referenced this article by Microsoft (https://msdn.microsoft.com/en-us/library/33y2221y(v=vs.110).aspx), but from my understanding, the Ordering section really applied when it was one table that required updates.
Thanks!

First meet the requirements of the foreign key constaint with an insert. Retain that value and perform a second insert using the foreign key relationship. Wrap these inserts in a transaction.
begin transaction
INSERT INTO TableA (Id) VALUES (1)
INSERT INTO TableB (Id, TableAID) VALUES (newid(), 1)
commit transaction

Related

How should I update existing table in the SQL Server database via C# and Windows forms?

I want to use Windows Forms and C# to implement a Database application which consists of the following tables:
Student table:
CREATE TABLE [dbo].[Student]
(
[Id] INT NOT NULL,
[Name] NVARCHAR(50) NOT NULL,
[MyId] AS ('S' + RIGHT('00' + CONVERT([varchar](5), [Id]), (2))) PERSISTED,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
Class table:
CREATE TABLE [dbo].[Class]
(
[Id] INT NOT NULL,
[Teacher] NVARCHAR(50) NOT NULL,
[Grade] INT NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
StudentClassCombo:
CREATE TABLE [dbo].[StudentClassCombo]
(
[ClassID] INT NOT NULL,
[StudentID] INT NOT NULL,
CONSTRAINT [ClassFK]
FOREIGN KEY ([ClassID]) REFERENCES [dbo].[Class] ([Id]),
CONSTRAINT [StudentFK]
FOREIGN KEY ([StudentID]) REFERENCES [dbo].[Student] ([Id])
);
I have a Windows forms interface through which I can assign students to classes.
I want to ensure that when the a student that has already been assigned to a class is re-assigned to a different class. the previous student-class assignment should be overwritten with the new one. In the case above, if Student ID 1 is already assigned to Class ID 1. But if the user decides to re-assign Student ID 1 to Class ID 2, the existing StudentClassCombo entry of 1-1 should be changed to 1-2.
I have written the following code to perform this update but I am encountering an exception:
string UpdateQuery = #"UPDATE dbo.StudentClassCombo SET"
+ " Class.ID as ClassId, Student.Id as StudentId FROM dbo.Class, dbo.Student" +
" WHERE Class.Grade=#Grade and Student.Name LIKE #StudentName";
using (connection = new SqlConnection(connectionString))
using (SqlCommand Insertcmd = new SqlCommand(UpdateQuery, connection))
{
connection.Open();
Insertcmd.Parameters.Add("#Grade", SqlDbType.Int);
Insertcmd.Parameters.Add("#StudentName", SqlDbType.NVarChar, 50);
foreach (ListViewItem eachItem in StudentsList.CheckedItems)
{
Insertcmd.Parameters["#Grade"].Value = int.Parse(ClassNames.Text);
Insertcmd.Parameters["#StudentName"].Value = eachItem.SubItems[1].Text.ToString();
Insertcmd.ExecuteNonQuery();
}
connection.Close();
}
The exception I am seeing now is as follows:
An unhandled exception of type 'System.Data.SqlClient.SqlException' occurred in System.Data.dll
Additional information: Incorrect syntax near the keyword 'as'.
How should I update the StudentClassCombo entries?
You should first executenonquery this:
UPDATE StudentClassCombo SET ClassId = #ClassId WHERE StudentId =#StudentId
..and capture the return value from ExecuteNonQuery
If the return value is 0, no records were updated (there is no student with that ID), run the following insert instead:
INSERT StudentClassCombo (ClassId,StudentId) VALUES(#ClassId,#StudentId)
You seem to already know how to add parameters to sql commands etc so I'll skip that part
Put a unique index on StudentId
If Class:Student is 1:Many (as you imply) it would be more typical to put ClassId as a column of Student table than have a middleman table, unless that middle table stores other relevant data than just the class and student id

Using SUM(Total). SQL statement works fine in PHP+MySQL

I'm attempting to re-use some mySQL in my Visual Studio 2015 C# project. I'm attempting to retrieve an output from the database which sums up the sales and total sale volume for each salesman. When using the mySQL statement that works great in my PHP project, I'm given the error:
The text, ntext, and image data types cannot be compared or sorted, except when using IS NULL or LIKE operator.
AGENT structure:
CREATE TABLE [dbo].[AGENT] (
[AgentID] INT IDENTITY (1, 1) NOT NULL,
[AgentName] TEXT NOT NULL,
[OfficeKey] INT NOT NULL,
PRIMARY KEY CLUSTERED ([AgentID] ASC)
);
OFFICE structure:
CREATE TABLE [dbo].[OFFICE] (
[OfficeID] INT IDENTITY (1, 1) NOT NULL,
[OfficeLocation] NCHAR (20) NOT NULL,
PRIMARY KEY CLUSTERED ([OfficeID] ASC)
);
SALE structure:
CREATE TABLE [dbo].[SALE] (
[SaleID] INT IDENTITY (1, 1) NOT NULL,
[SaleDate] DATE NOT NULL,
[AgentKey] INT NOT NULL,
[Amount] MONEY NOT NULL,
[DestinationKey] INT NOT NULL,
PRIMARY KEY CLUSTERED ([SaleID] ASC)
);
Here is my source:
namespace Desktop_Campus_Travel
{
public partial class Agent_Bookings : Form
{
public Agent_Bookings()
{
InitializeComponent();
}
private void Agent_Bookings_Load(object sender, EventArgs e)
{
List<AgentBooking> agentList = new List<AgentBooking>();
SqlConnection conn = Database.GetConnection();
string selStmt = #"
SELECT AGENT.AgentName,
OFFICE.OfficeLocation,
COUNT(AGENT.AgentID) AS Sales,
SUM(SALE.Amount) AS Total
FROM AGENT
JOIN OFFICE
ON AGENT.OfficeKey = OFFICE.OfficeID
JOIN SALE
ON SALE.AgentKey = AGENT.AgentID
GROUP BY AGENT.AgentName";
SqlCommand selCmd = new SqlCommand(selStmt, conn);
try
{
conn.Open();
SqlDataReader reader = selCmd.ExecuteReader();
while (reader.Read())
{
AgentBooking agent = new AgentBooking();
agent.AgentName = reader["AgentName"].ToString();
agent.OfficeLocation = reader["OfficeLocation"].ToString();
agent.Sales = reader["Sales"].ToString();
agent.Total = reader["Total"].ToString();
agentList.Add(agent);
}
reader.Close();
}
catch (SqlException ex) { throw ex; }
finally { conn.Close(); }
}
private void button1_Click(object sender, EventArgs e)
{
this.Close();
}
}
}
Step 1
Change your Agent Table to
CREATE TABLE [dbo].[AGENT] (
[AgentID] INT IDENTITY (1, 1) NOT NULL,
[AgentName] NVARCHAR(100) NOT NULL,
[OfficeKey] INT NOT NULL,
PRIMARY KEY CLUSTERED ([AgentID] ASC)
);
SQL Server is not happy to GROUP BY TEXT data type.
Step 2
Change your query to
SELECT
AGENT.AgentName,
OFFICE.OfficeLocation,
SALE2.Sales,
SALE2.Total
FROM
AGENT
JOIN OFFICE ON AGENT.OfficeKey = OFFICE.OfficeID
JOIN
(
SELECT
SALE.AgentKey,
COUNT(SALE.SaleID) AS Sales,
SUM(SALE.Amount) AS Total
FROM SALE
GROUP BY SALE.AgentKey
) SALE2 ON SALE2.AgentKey = AGENT.AgentID
SQL Server is also not happy to select column that is not contained in either an aggregate function or the GROUP BY clause when GROUP BY is used.
You can do only Step 2 without doing Step 1 and it will work, but Text datatype is deprecated (MSDN Reference) so you shouldn't use it.

The INSERT statement conflicted with the FOREIGN KEY constraint

Getting this strange issue,
I have a user defined table:
CREATE TYPE [dbo].[Crates_FruitsType] AS TABLE
(
[FruitID] [int] NOT NULL,
[CrateID] [int] NOT NULL
)
And a stored procedure:
CREATE procedure [dbo].[Crates_InsertRelateMultipleFruits]
(#FruitCrates As Crates_FruitsType READONLY)
AS
BEGIN
DELETE rc
FROM Crates_Fruits rc
WHERE rc.CrateID IN (SELECT DISTINCT tmp.CrateID
FROM #FruitCrates tmp);
INSERT INTO Crates_Fruits (FruitID, CrateID)
SELECT
tmp.FruitID, tmp.CrateID
FROM
#FruitCrates tmp
WHERE
tmp.FruitID <> '-1';
END
And table:
CREATE TABLE [dbo].[Crates_Fruits]
(
[EchoID] [int] NULL,
[FruitID] [int] NOT NULL,
[CrateID] [int] NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Crates_Fruits] WITH CHECK
ADD CONSTRAINT [FK_Crates_CrateID]
FOREIGN KEY([CrateID]) REFERENCES [dbo].[Crates] ([ID])
GO
ALTER TABLE [dbo].[Crates_Fruits] CHECK CONSTRAINT [FK_Crates_CrateID]
GO
ALTER TABLE [dbo].[Crates_Fruits] WITH CHECK
ADD CONSTRAINT [FK_Fruits_FruitID2]
FOREIGN KEY([FruitID]) REFERENCES [dbo].[Fruits] ([ID])
GO
ALTER TABLE [dbo].[Crates_Fruits] CHECK CONSTRAINT [FK_Fruits_FruitID2]
GO
And this C# code:
using (SqlConnection connection = new SqlConnection(GetConnectionString()))
{
connection.Open();
DataTable DT = new DataTable();
DT.Columns.Add("FruitID", typeof(int));
DT.Columns.Add("CrateID", typeof(int));
if (fruitIDs.Count < 1)
DT.Rows.Add(crateID, -1);
else
{
for (int i = 0; i < fruitIDs.Count; i++)
DT.Rows.Add(fruitIDs[i], crateID);
}
using (SqlCommand command = new SqlCommand("Crates_InsertRelateMultipleFruits", connection))
{
command.CommandType = CommandType.StoredProcedure;
var testingparam = command.Parameters.AddWithValue("#FruitCrates", DT);
testingparam.SqlDbType = SqlDbType.Structured;
command.ExecuteNonQuery();
}
}
Error:
System.Data.SqlClient.SqlException (0x80131904): The INSERT statement
conflicted with the FOREIGN KEY constraint "FK_Crates_CrateID". The
conflict occurred in database "FruitFactory", table "dbo.Crates",
column 'ID'. The statement has been terminated.
It works perfectly but gives error when I pass data as
CrateID = 172
FruitID = -1
I am expecting stored procedure to,
if FruitID is passed as "-1" then only delete all records where CrateID is 172
Otherwise delete all records where CrateID is 172 and add new datatable
You have reversed the order of the columns. Your type is defined as (FruitId,CrateId) but your C# code enters the crateID first when there are no fruit IDs:
if (fruitIDs.Count < 1)
DT.Rows.Add(crateID, -1);
It should be
if (fruitIDs.Count < 1)
DT.Rows.Add(-1,crate);
To execute your stored procedure directly, just create a variable using the table type and insert the values you want, eg:
declare #t [Crates_FruitsType]
insert into #t VALUES(-1,172)
exec [Crates_InsertRelateMultipleFruits] #t

Auto Increment Id in stored procedure not working

I am trying to get company id like "Cp-00001". If data exists in table then the id should be "Cp-00001" + 1 = "Cp=00002" and do on...
Here's what I have so far:
CREATE PROCEDURE [dbo].[sp_AutoGenerateCustomerCode]
AS
DECLARE #id VARCHAR(10)
BEGIN
SELECT #id = 'Cp-' + CAST(MAX(CAST(SUBSTRING(CompanyCode,4,5) AS INTEGER))+1 AS VARCHAR) FROM [Beauty Saloon Project].[dbo].[tbl_Company];
IF #id IS NULL
BEGIN
SET #id = 'Cp-00001';
END
RETURN #id;
END
but when i call it here
datatable DT = new datatable
DT = ExecuteSpDataTable("sp_AutoGenerateCustomerCode");
This returns null.
If I don't have data then it should return Cp-00001, but I have one data row in which company code is saloon is it the reason for null ???
EDIT:
public DataTable ExecuteSpDataTable(string SPName)
{
try
{
if (ConnectionOpen())
{
SqlCommand objSqlCommand = new SqlCommand(SPName, objConnection);
objSqlCommand.CommandType = CommandType.StoredProcedure;
objSqlCommand.CommandTimeout = 10000;
SqlDataAdapter objSqlDataAdapter = new SqlDataAdapter();
DataTable objDataTable = new DataTable();
objSqlDataAdapter.SelectCommand = objSqlCommand;
objSqlDataAdapter.Fill(objDataTable);
ConnectionClose();
return objDataTable;
}
return null;
}
catch (Exception ex)
{
objErrorLogs.LogError(ex);
return null;
}
}
One word of advice: DON'T DO THIS! Using this SELECT MAX() + 1 approach is not safe under load, as soon as more than one user will be using your application, you WILL HAVE DUPLICATES - sooner or later.
The only viable solution is to use
an ID INT IDENTITY(1,1) column to get SQL Server to handle the automatic increment of your numeric value
a computed, persisted column to convert that numeric value to the value you need
So try this:
CREATE TABLE dbo.tblCompany
(ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
CompanyID AS 'CP-' + RIGHT('00000' + CAST(ID AS VARCHAR(5)), 5) PERSISTED,
.... your other columns here....
)
Now, every time you insert a row into tblCompany without specifying values for ID or CompanyID:
INSERT INTO dbo.tblCompany(Col1, Col2, ..., ColN)
VALUES (Val1, Val2, ....., ValN)
then SQL Server will automatically and safely increase your ID value, and CompanyID will contain values like CP-00001, CP-00002,...... and so on - automatically, safely, reliably, no duplicates.
Update: if you want to make the CompanyID the primary key, you could use this T-SQL statement:
CREATE TABLE dbo.tblCompany
(ID INT IDENTITY(1,1) NOT NULL,
CompanyID AS 'CP-' + RIGHT('00000' + CAST(ID AS VARCHAR(5)), 5) PERSISTED
CONSTRAINT PK_tblCompany PRIMARY KEY NONCLUSTERED,
.... your other columns here....
)
CREATE CLUSTERED INDEX CIX_Company ON dbo.tblCompany(ID);
I would leave the clustered index on ID and just move the primary key constraint to use CompanyID instead.

Transaction Foreign Key Causes: "INSERT statement conflicted with the FOREIGN KEY constraint"

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

Categories

Resources