ConstraintException when querying SQlite database with C# - c#

I’m hoping somebody will be able to help with my SQLite database problem.
I’m receiving a ConstraintException when querying my SQLite database with C#. The full exception message is “Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.” I originally built this database using access which worked fine, but for various reasons I had to recreate it using SQLite.
To give a bit of background - this is a simple status scheduling program. Each Status has an associated Account and Schedule. I realise Statuses and Schedule is a 1:1 relationship and could be in the same table but to allow the program to develop further I have split them into two tables.
See below for a cut down version of my table script (this is enough to recreate the problem).
PRAGMA foreign_keys = ON;
CREATE TABLE Accounts
(ID INTEGER PRIMARY KEY AUTOINCREMENT,
Name char(100));
CREATE TABLE Statuses
(ID INTEGER PRIMARY KEY AUTOINCREMENT,
AccountId INTEGER REFERENCES Accounts(ID) ON DELETE CASCADE,
Text char(140));
CREATE TABLE Schedule
(ID INTEGER PRIMARY KEY REFERENCES Statuses(ID) ON DELETE CASCADE,
StartDate char(255),
Frequency INT);
I did not have any issues until I created two Statues and associated them to the same Account.
Accounts
ID Name
1 Fred Blogs
Statuses
ID AccountId Text
1 1 “Some text”
2 1 “Some more text”
Schedule
ID StartDate Frequency
1 16/02/2011 1
2 16/02/2011 1
The select statement I’m using which throws the exception is:
SELECT Statuses.Id, Statuses.Text, Accounts.Id, Accounts.Name, Schedule.StartDate, Schedule.Frequency
FROM [Statuses], [Accounts], [Schedule]
WHERE Statuses.AccountId = Accounts.Id AND Statuses.Id = Schedule.Id
If I run the same query, but remove the ‘Accounts.Id’ column the query works fine.
See below for the C# code I’m using but I don’t think this is the problem
public DataTable Query(string commandText)
{
SQLiteConnection sqliteCon = new SQLiteConnection(ConnectionString);
SQLiteCommand sqliteCom = new SQLiteCommand(commandText, sqliteCon);
DataTable sqliteResult = new DataTable("Query Result");
try
{
sqliteCon.Open();
sqliteResult.Load(sqliteCom.ExecuteReader());
}
catch (Exception)
{
throw;
}
finally
{
sqliteCon.Close();
}
return sqliteResult;
}
Any help will be appreciated. Thanks.

the error is occuring due to the ID columns in Statuses table and Schedule table. If they are not important delete the columns from the two tables.

I have found a way round this problem. If I select the AccountId from the Schedule table rather than the Accounts table there is no exception thrown. It seems I was unable to run a SELECT statement that contained two Unique primary key columns.
So instead of
SELECT Statuses.Id, Statuses.Text, Accounts.Id, Accounts.Name, Schedule.StartDate, Schedule.Frequency
FROM [Statuses], [Accounts], [Schedule]
WHERE Statuses.AccountId = Accounts.Id AND Statuses.Id = Schedule.Id
I run
SELECT Statuses.Id, Statuses.Text, Statuses.AccountId, Accounts.Name, Schedule.StartDate, Schedule.Frequency
FROM [Statuses], [Accounts], [Schedule]
WHERE Statuses.AccountId = Accounts.Id AND Statuses.Id = Schedule.Id

I fixed the issue by reading the schema only at first, then cleared the constraints of the datatable and then read again the data.
like this :
DataSet DS = new DataSet();
mytable = new DataTable();
DS.Tables.Add(mytable);
DS.EnforceConstraints = false;
SQLiteCommand command = DBconnection.CreateCommand();
command.CommandText = "select * from V_FullView";
SQLiteDataReader reader = command.ExecuteReader(CommandBehavior.SchemaOnly);
mytable.Load(reader);
mytable.Constraints.Clear();
reader = command.ExecuteReader();
mytable.Load(reader);
reader.Close();
my V_FullView is a view of 4 different tables merged. It seems that the constraints are the ones of the first merged table (name was unique on that one, but replicated a multiple of times in the view)

Related

Can't delete row using Npgsql adapter if there is char column in primary key

all.
I have a PostgreSQL table which primary key has a column with type CHAR(20), so value 'X' is stored as 'X[19 spaces]' in the table. I can't delete a row from this table using NpgsqlDataAdapter, I always get an exception "System.Data.DBConcurrencyException: Concurrency violation: the DeleteCommand affected 0 of the expected 1 records.". I guess those spaces somehow are messing with adapter and it can't find a row to delete. I'm using data adapters for all operations with the database, so I can't use some dirty hacks like executing 'delete from' scripts.
Here is my code to reproduce this behaviour:
using (var connection = new NpgsqlConnection(connectionString))
{
connection.Open();
using (var adapter = new NpgsqlDataAdapter("select * from chartest", connection))
using (var builder = new NpgsqlCommandBuilder(adapter))
{
//drop table to make a clean test
var command = connection.CreateCommand();
command.CommandText = "drop table if exists chartest";
command.ExecuteNonQuery();
//create table for testing
command.CommandText = "CREATE TABLE chartest (charcol CHAR(20) NOT NULL, CONSTRAINT pk_chartest PRIMARY KEY (charcol))";
command.ExecuteNonQuery();
//add one row with value 'z' which expands to 'z ' after saving
command.CommandText = "insert into chartest (charcol) values ('z')";
command.ExecuteNonQuery();
//load table from database
var table = new DataTable();
adapter.Fill(table);
//try to delete row...
table.Rows[0].Delete();
adapter.DeleteCommand = builder.GetDeleteCommand();
adapter.Update(table);//...and get an exception System.Data.DBConcurrencyException:
//Concurrency violation: the DeleteCommand affected 0 of the expected 1 records.
}
}
Unfortunately, I can't change the type of this field or make any other changes to the database. I'v tried to:
manually create DeleteCommand with each parameter;
reject row changes, trim value, delete row again: 1) in RowUpdating event
handler 2) before calling Update method
change column's MaxLength to the actual length of a value;
change DbParameter's Size to the actual length of a value;
add trim functions to CommandText of DeleteCommand;
use different Npgsql libraries (2.2.3 and 3.2.1);
use different .NET Framework versions (4.0 and 4.5.1);
study Npgsql source code.
But still no luck. Could you please help me? How can I make the adapter delete such rows? Thanks in advance!

Edit existing data in SQL Server database

I have a table SupplierMaster in a SQL Server database with a column SUPPLIERNAME.
I want to edit saved supplier name using stored procedure with below query
ALTER PROCEDURE [dbo].[usp_SupplierMasterUpdateDetails]
(
#SUPPLIERNAME NVARCHAR(50)
)
AS
BEGIN
UPDATE [dbo].[SupplierMaster]
SET [SUPPLIERNAME] = #SUPPLIERNAME
WHERE [SUPPLIERNAME] = #SUPPLIERNAME
END
and I run the BELOW code through "UPDATE BUTTON" to update the data.
string connString = ConfigurationManager.ConnectionStrings["dbx"].ConnectionString;
using (SqlConnection conn = new SqlConnection(connString))
{
using (SqlCommand cmd = new SqlCommand("usp_SupplierMasterUpdateDetails", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
// Parameter
cmd.Parameters.AddWithValue("SUPPLIERNAME", AddSupplierTextBox.Text);
// Open Connection
conn.Open();
// ExecuteReader (Select Statement)
// ExecuteScalar (Select Statement)
// ExecuteNonQuery (Insert, Update or Delete)
cmd.ExecuteNonQuery();
MessageBox.Show("SUCCESSFULLY UPDATED", "Successful", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
But its not updating the selected data.
Please advice and assist me to correct the code for proper work.
You have multiple issues there.
First you need to fix your update query just as Thomas Levesque suggested.
a SQL Server table needs a primary key to be able to uniquely identify a record, for updates for example.
The easiest thing you could do is set that primary key to be identity of type int and make it self generating. Your supplier table could look like this :
SupplierID int, Primary Key, identity
SupplierName nvarchar(100)
Now, when you do an update, you would do it like this:
Update SupplierMaster
Set SupplierName = #supplierName
Where SupplierID = #suplierID
Such a SQL statement will return an int value. This return value will tell you how many SQL rows this update statement has changed. If it says 0 then it means that the SQL statement could not find that id you passed through and nothing changed. If it says 1, then the record was found and updated, if you get more than 1 you have an issue with the SQL statement and multiple rows were updated.
In your code check for this return value and that's how you determine if your update statement was successful or not.

Loop to insert data in OleDb breaks when I try to execute more than 1 query

I'm working on a small offline C# application with an Access 2002 database (.mdb) and OleDb.
I have 2 tables where I need to insert data at the same time, one holding a foreign key of the other. So, to simplify let's say one table has 2 attributes: "idTable1" (auto-increment integer) and "number", and the other has 2 attributes: "idTable2" (auto-increment integer) and "fkTable1" (foreign key containing an integer value that matches an "idTable1" from table 1).
A foreach loop iterates over a collection and inserts each element in Table1. Then the idea is to use a SELECT ##Identity query on Table1 to get the auto-incrementing id field of the last record that was inserted, and insert that in Table2 as a foreign key.
I'm just trying the first part before I attempt to insert the foreign key: loop over a collection, insert each item in Table1 and get the idTable1 of the last inserted record. But whenever I try to execute SELECT ##Identity I get only 1 record in my database, even when the loop correctly iterates over all the collection items.
My code looks like this:
string queryInsertTable1 = "INSERT INTO Table1 (numero) VALUES (?)";
string queryGetLastId = "Select ##Identity";
using (OleDbConnection dbConnection = new OleDbConnection(strDeConexion))
{
using (OleDbCommand commandStatement = new OleDbCommand(queryInsertTable1, dbConnection))
{
dbConnection.Open();
foreach (int c in Collection)
{
commandStatement.Parameters.AddWithValue("", c);
commandStatement.ExecuteNonQuery();
commandStatement.CommandText = queryGetLastId;
LastInsertedId = (int)commandStatement.ExecuteScalar();
}
}
}
If I comment out the last 3 lines:
commandStatement.CommandText = queryGetLastId;
LastInsertedId = (int)commandStatement.ExecuteScalar();
Then all records from Collection are correctly inserted in the BD. But as soon as I un-comment those, I get just 1 record inserted, while the value stored in "c" is the last element in the collection (so the loop worked fine).
I also tried calling commandStatement.Parameters.Clear() right after the commandStatement.ExecuteNonQuery() sentence, but that makes no difference (and it shouldn't, but I still tried).
I don't want to make things complicated by using transactions and such, if I can avoid them, since this is a very simple, single-computer, offline and small application. So if anyone knows what I could do to make that code work, I'd be very grateful :)
Here: commandStatement.CommandText = queryGetLastId; you actually changing your command from inserting to selecting identity.
Thus, on the next iteration it will not insert anything, but again select for identity, that's why you're having only one record inserted into DB.
I think it's better to have two separate commands for inserting and for selecting identity.
Also note - you're trying to add new parameter into commandStatement on each iteration, so on iteration, say, N it will be N parameters. Either clear parameters before adding new one, or add parameter outside of loop and in the loop change its value only.
using (OleDbConnection dbConnection = new OleDbConnection(strDeConexion))
{
using (OleDbCommand commandStatement = new OleDbCommand(queryInsertTable1, dbConnection))
using (OleDbCommand commandIdentity = new OleDbCommand(queryGetLastId , dbConnection))
{
commandStatement.Parameters.Add(new OleDbParameter());
dbConnection.Open();
foreach (int c in Collection)
{
commandStatement.Parameters[0].Value = c;
commandStatement.ExecuteNonQuery();
LastInsertedId = (int)commandIdentity.ExecuteScalar();
}
}
}
Once you changed the commandtext the first query is gone from it so it wont insert any data to the first table again. reassing it and try

Application of Primary Key from Parent Table to Child Table

I have the following tables in my SQL Server CE database:
ORDERS
OrderID (handled my DBMS)
CustomerID
OrderDate
ORDER_DETAILS
OrderID (from the ORDERS table)
ProductID
OrderQTY
I currently use 2 insert queries to add new orders to the database. The first one to inserts the order into the ORDERS table and allow the DBMS to create the OrderID, and the second one that uses that OrderID provided to insert to the ORDER_DETAILS table.
The approach I have used below seems very klunky and potentially vulnerable to concurrency issues. Is there a way to have the DBMS handle the creation of the correct OrderID for the ORDER_DETAILS table when a new record is inserted into the ORDERS table?
This is the C# I use to run the insert queries:
public int InsertOrder(Order order)
{
DBConnection connection = DBConnection.getInstance();
connection.conn.Open();
using (SqlCeCommand query = new SqlCeCommand(OrderCommandList.cmdInsertOrderHeader, connection.conn))
{
query.Parameters.AddWithValue("#CustomerID", order.CustomerID);
query.Parameters.AddWithValue("#OrderDate", order.OrderDate);
query.ExecuteNonQuery();
}
//retrieves the PK for the recently inserted record
int newOrderPK = 0;
using(SqlCeCommand cmdGetIdentity = new SqlCeCommand("SELECT ##IDENTITY", connection.conn))
{
newOrderPK = Convert.ToInt32(cmdGetIdentity.ExecuteScalar());
}
connection.conn.Close();
InsertOrderDetails(order, newOrderPK);
return newOrderPK;
}
//inserts all the order details associated with the Order object
private void InsertOrderDetails(Order order, int orderForeignKey)
{
foreach (OrderDetail od in order.OrderLineItems)
{
DBConnection connection = DBConnection.getInstance();
connection.conn.Open();
using (SqlCeCommand query = new SqlCeCommand(OrderCommandList.cmdInsertOrderDetails, connection.conn))
{
query.Parameters.AddWithValue("#OrderID", orderForeignKey);
query.Parameters.AddWithValue("#ProductID", od.ProductID);
query.Parameters.AddWithValue("#OrderQty", od.QtyOrdered);
query.ExecuteNonQuery();
}
connection.conn.Close();
}
}
Change your stored procedure to select the generated identity value, and have it use SELECT SCOPE_IDENTITY(); rather than SELECT ##IDENTITY;. Actually, preferable would be to use an OUTPUT parameter, but for now I'll assume you're going to keep using SELECT. e.g.
INSERT dbo.Orders(CustomerID,OrderDate) SELECT #CustomerID,#OrderDate;
SELECT ID = SCOPE_IDENTITY();
Then, instead of ExecuteNonQuery() for the insert, then a separate command for the SELECT ##IDENTITY, you just need one ExecuteScalar to perform both.
You can do even better than this using TVPs, where you could send the order and all of the detail rows with a single stored procedure. However, CE is a little passé - not sure if it supports TVPs. You should consider using Express / LocalDB.

Different ways to generate the latest int type primary foreign key in code

I am new to sql. I have added 2 new tables in database. The primary key of first is a foreign key in the other. The type of the keys is integer. Now I want to generate the keys in the code and assign it to new data so that the association between different rows of the tables is right. How do I ensure uniqueness of keys and also get the latest key from the db so that there are no errors while saving.
If I had used guids then I would have assigned a new guid to the primary key and then assigned the same to the foreign key in the other table. Also there are multiple clients and one server which is saving the data.
The data to be inserted in both the tables is decided in the c# code and is not derived from the row inserted in the primary table. Even if get the id in db then also the relation between the rows should be stored in some form from the code because after that it is lost.
The only viable way to do this is to use INT IDENTITY that the SQL Server database offers. Trust me on this one - you don't want to try to do this on your own!
Just use
CREATE TABLE dbo.YourTableOne(ID INT IDENTITY(1,1), ...other columns...)
and be done with it.
Once you insert a row into your first table, you can retrieve the value of the identity column like this:
-- do the insert into the first table
INSERT INTO dbo.YourTableOne(Col1, Col2, ...., ColN)
VALUES (Val1, Val2, ...., ValN)
DECLARE #NewID INT
-- get the newly inserted ID for future use
SELECT #NewID = SCOPE_IDENTITY()
-- insert into the second table, use first table's new ID for your FK column
INSERT INTO dbo.YourTableTwo (FKColumn, ......) VALUES(#NewID, ......)
Update: if you need to insert multiple rows into the first table and capture multiple generated ID values, use the OUTPUT clause:
-- declare a table variable to hold the data
DECLARE #InsertedData TABLE (NewID INT, ...some other columns as needed......)
-- do the insert into the first table
INSERT INTO dbo.YourTableOne(Col1, Col2, ...., ColN)
OUTPUT Inserted.ID, Inserted.Col1, ..., Inserted.ColN INTO #InsertedData(NewID, Col1, ..., ColN)
VALUES (Val1, Val2, ...., ValN)
and then go from there. You can get any values from the newly inserted rows into the temporary table variable, which will then allow you to decide which new ID values to use for which rows for your second table
As #marc_s said using Database managed keys is more viable. But in cases there is no much load on the database, for example because there are few users who work simultanously, I will use another easier method. That's I get the last id, I try to add new record, and if I encountered error for duplicate, I will try again. I limited this to 3 trials for my application and there's a 300 ms timeout between each trial. Dont forget that this approach has serious limitations. In my application, there are very few users, the work load is very low, and the connection is a local one so this will do job well. Perhaps in other applications you need to adjust the delay, and in some cases, the approach might completely fail. Here's the code,
I have two tables, Invoices and Invoices_Items the column which relates them is invoice_id:
byte attempts = 0;
tryagain: //Find last invoice no
OleDbCommand command = new OleDbCommand("SELECT MAX(invoice_id) FROM Invoices"
, myconnection);
int last_invoice_id = 0;
try
{
last_invoice_id = (int)command.ExecuteScalar();
}
catch (InvalidCastException) { };
// text_invoice_number.Text = Convert.ToString(last_invoice_id + 1);
try
{
command = new OleDbCommand(#"INSERT INTO Invoices
(invoice_id,patient_id,visit_id,issue_date,invoice_to,doctor_id,assistant_id)
VALUES(?,?,?,?,?,?,?)",myconnection);
// We use last_invoice_id+1 as primary key
command.Parameters.AddWithValue("#invoice_id",last_invoice_id+1);
// I will add other parameters here (with the exact order in query)
command.ExecuteNonQuery();
}
catch (Exception ex){
attempts++;
if (attempts <= 3) // 3 attempts
{
System.Threading.Thread.Sleep(300); // 300 ms second delay
goto tryagain;
}
else
{
MessageBox.Show("Can not add invoice to database, " + ex.Message, "Unexpected error!"
, MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
for (int i = 0; i <= listInvoiceItems.Count-1; i++)
{
command = new OleDbCommand(#"INSERT INTO Invoices_Items
(invoice_id,quantity,product,price,amount,item_type)
VALUES(?,?,?,?,?,?)",myconnection);
// Now we use our stored last_invoice_id+1 as foreign key
command.Parameters.AddWithValue("#invoice_id",last_invoice_id+1);
// Add other Invoice Items parameters here (with the exact order in query)
command.ExecuteNonQuery();
}

Categories