The INSERT statement conflicted with the FOREIGN KEY constraint - c#

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

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

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

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

How to fast reduce a datatable according to existing entries in a DB

I'm currently trying to bulkinsert a datatable into a database. It works fine and fast. The only problem occurs
if there are any rows that are already in the database (duplicate key).
To counter this I have modified my program so that I first check for each new entry if it already exists in the database or not.
Which is.......slow (In the current cases I don't have many entries but later on its over 200k entries that I need to check and that a few times).
Thus I need to make it faster as it is now (if possible).
The datatable is structured this way:
DataTable transactionTable.Columns.Add("DeviceId", typeof(Int32));
transactionTable.Columns.Add("LogDate", typeof(DateTime));
transactionTable.Columns.Add("LogType", typeof(Int32));
transactionTable.Columns.Add("LogText", typeof(String));
transactionTable.PrimaryKey = new DataColumn[3] {
transactionTable.Columns[0],
transactionTable.Columns[1],
transactionTable.Columns[2]
};
What I have so far is the following:
DataTable insertTable = transactionTable.Copy();
insertTable.Clear();
using (SqlConnection sqlcon = new SqlConnection(this.GetConnString()))
{
sqlcon.Open();
foreach (var entry in transactionTable.AsEnumerable())
{
using (SqlCommand sqlCom = sqlCon.CreateCommand())
{
sqlCom.Parameters.Clear();
sqlCom.CommandText = "SELECT 1 FROM myTable WHERE"
+ " DeviceId = #DeviceId AND LogDate = #LogDate"
+ " AND LogType = #LogType"
sqlCom.Parameters.AddWithValue("#DeviceId", entry.Field<Int32>("DeviceId"));
sqlCom.Parameters.AddWithValue("#LogDate", entry.Field<DateTime>("LogDate"));
sqlCom.Parameters.AddWithValue("#LogType", entry.Field<Int32>("LogType"));
using (SqlDataREader myRead = sqlCon.ExecuteReader()
{
myRead.Read();
if (myRead.HasRows == false)
{
insertTable.Rows.Add(entry.ItemArray);
}
}
}
}
}
// And afterwards the bulkinsert which I think is out of scope for the question itself
// (I use the insertTable there)
Now my question is: Is there any way to do this faster in order to not get the key violation problem?
In this case I would use some staging table. Here is some steps:
Bulk insert into staging table(using SqlBulkCopy)
Inserting into base table using stored proc with left join to eliminate existing rows
Truncate staging table
So you will need to delete foreach statement in your code, add stored proc for inserting to base table, add stored proc for truncating. Or you can combine last 2 steps in one.
I have a similar set up.
I'm using a stored procedure with a Table-Valued parameter and MERGE statement. See also Table-Valued Parameters for example how to use them in .NET.
I would shift the focus of the problem from simple bulk insert to merging a batch of rows into a table with existing data.
Destination table
CREATE TABLE [dbo].[MyTable](
[DeviceId] [int] NOT NULL,
[LogDate] [datetime] NOT NULL,
[LogType] [int] NOT NULL,
[LogText] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_MyTable] PRIMARY KEY CLUSTERED
(
[DeviceId] ASC,
[LogDate] ASC,
[LogType] ASC
))
Create user-defined table type
CREATE TYPE [dbo].[MyTableType] AS TABLE(
[DeviceId] [int] NOT NULL,
[LogDate] [datetime] NOT NULL,
[LogType] [int] NOT NULL,
[LogText] [nvarchar](50) NOT NULL,
PRIMARY KEY CLUSTERED
(
[DeviceId] ASC,
[LogDate] ASC,
[LogType] ASC
))
Test and measure whether specifying PRIMARY KEY for the TYPE makes overall process faster or slower.
Stored procedure with TVP
CREATE PROCEDURE [dbo].[MergeMyTable]
#ParamRows dbo.MyTableType READONLY
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
BEGIN TRANSACTION;
BEGIN TRY
MERGE INTO dbo.MyTable as Dest
USING
(
SELECT
TT.[DeviceId],
TT.[LogDate],
TT.[LogType],
TT.[LogText]
FROM
#ParamRows AS TT
) AS Src
ON
(Dest.[DeviceId] = Src.[DeviceId]) AND
(Dest.[LogDate] = Src.[LogDate]) AND
(Dest.[LogType] = Src.[LogType])
WHEN MATCHED THEN
UPDATE SET
Dest.[LogText] = Src.[LogText]
WHEN NOT MATCHED BY TARGET THEN
INSERT
([DeviceId]
,[LogDate]
,[LogType]
,[LogText])
VALUES
(Src.[DeviceId],
Src.[LogDate],
Src.[LogType],
Src.[LogText]);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH;
END
Call this stored procedure passing it a batch of rows to merge. Test and measure how performance changes with the size of the batch. Try batches with 1K, 10K, 100K rows.
If you never want to update existing rows with new values, remove the WHEN MATCHED THEN part of the MERGE, it will work faster.
You can drop and recreate your index with the IGNORE_DUP_KEY set to ON. Something like this:
ALTER TABLE datatable
ADD CONSTRAINT PK_datatable
PRIMARY KEY CLUSTERED (DeviceId,LogDate,LogType,LogText)
WITH (IGNORE_DUP_KEY = ON)
What this option does is report a duplicate key error with a different severity and message when any duplicate inserts for the index are attempted. It will not allow duplicates to be entered, but it will continue to insert all the records that are not duplicates and only give a warning message if duplicates were found and ignored.
More info at this link: Creating Unique Indexes.

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