How to fix FK Constraint insert Exception - c#

According to my question with weird problem specified here how to fix
System.Data.SqlClient.SqlException: String or binary data would be truncated in table
My problem is, that if I am saving new problem into the database, its ID is always set to 0 (I checked this out in debugging), which then throws
System.Data.SqlClient.SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK__Alert__Problem_I__17F790F9". The conflict occurred in database "SmartOne", table "dbo.Problem", column 'id'
But in SQL Server Management Studio, the ID is set correctly (ID is defined as an Identity column).
Where both I am using is in my question mentioned below. Thanks for any ideas or advice.
Method that saves Problem:
public void Save(Problem element)
{
using (SqlConnection conn = new SqlConnection(DatabaseSingleton.connString))
{
conn.Open();
using (SqlCommand command = new SqlCommand("INSERT INTO Problem VALUES " +
"(#nameOfAlert, #value, #result, #message_ID) ", conn))
{
command.Parameters.Add(new SqlParameter("#nameOfAlert", element.NameOfAlert));
command.Parameters.Add(new SqlParameter("#value", (int)element.Value));
command.Parameters.Add(new SqlParameter("#result", (int)element.Result));
command.Parameters.Add(new SqlParameter("#message_ID", element.Message_Id));
command.ExecuteNonQuery();
command.CommandText = "Select ##Identity";
}
conn.Close();
}
}
Method that saves an Alert:
public void Save(Alert element)
{
using (SqlConnection conn = new SqlConnection(DatabaseSingleton.connString))
{
conn.Open();
using (SqlCommand command = new SqlCommand("INSERT INTO [Alert] VALUES (#message_ID, #date, #email, #AMUser_ID, #Problem_ID) ", conn))
{
command.Parameters.Add(new SqlParameter("#message_ID", element.Id_MimeMessage));
command.Parameters.Add(new SqlParameter("#date", element.Date));
command.Parameters.Add(new SqlParameter("#email", element.Email));
command.Parameters.Add(new SqlParameter("#AMUser_ID", element.User_ID));
command.Parameters.Add(new SqlParameter("#Problem_ID", element.Problem_ID));
command.ExecuteNonQuery();
command.CommandText = "Select ##Identity";
}
conn.Close();
}
}
SQL Scheme
CREATE TABLE [dbo].[Alert](
[id] [int] IDENTITY(1,1) NOT NULL,
[message_ID] [varchar](100) NOT NULL,
[date] [datetime] NOT NULL,
[email] [varchar](50) NOT NULL,
[AMUser_ID] [int] NOT NULL,
[Problem_ID] [int] NOT NULL);
//Where is ID, it means FK ID
CREATE TABLE [dbo].[Problem](
[id] [int] IDENTITY(1,1) NOT NULL,
[nameOfAlert] [varchar](50) NOT NULL,
[Value_ID] [int] NOT NULL,
[Result_ID] [int] NOT NULL,
[message_ID] [varchar](100) NOT NULL);

One problem might be that you're never actually getting back the inserted IDENTITY value from your first insert - thus you aren't using any valid ProblemId value for your second insert.
Try something like this:
public void Save(Problem element)
{
using (SqlConnection conn = new SqlConnection(DatabaseSingleton.connString))
{
conn.Open();
// define INSERT query - I'd *strongly* recommend specifying all
// columns you're inserting into!
// Also: run the "SELECT SCOPE_IDENTITY()" right after the INSERT
string insertQry = "INSERT INTO dbo.Problem(NameOfAlert, Value, Result, MessageId) " +
"VALUES (#nameOfAlert, #value, #result, #message_ID); " +
"SELECT SCOPE_IDENTITY();";
using (SqlCommand command = new SqlCommand(insertQry, conn))
{
// also here: define the *datatype* of the parameter, and use
// .Value = to set the value.
// Since you haven't shown what the table looks like, I'm just
// **guessing** the datatype and max length for the string parameters - adapt as needed!
command.Parameters.Add("#nameOfAlert", SqlDbType.VarChar, 100).Value = element.NameOfAlert;
command.Parameters.Add("#value", SqlDbType.Int).Value = (int)element.Value;
command.Parameters.Add("#result", SqlDbType.Int).Value = (int)element.Result;
command.Parameters.Add("#message_ID", SqlDbType.VarChar, 100).Value = element.Message_Id;
// since your statement now returns the ID value - use "ExecuteScalar"
var returnedValue = command.ExecuteScalar();
if (returnedValue != null)
{
// if a value was returned - convert to INT
int problemId = Convert.ToInt32(returnedValue);
}
}
conn.Close();
}
}
Now, in case the INSERT works, you get back the ProblemId value from the identity column, and you can now use this in your second insert as value for the #ProblemId parameter.

For saving the id into other table, you have to complete the insertion first. if the insertion is not completed then you can not get the problem id (if it is the primary key, which is supposed to be returned by saving the datas). Only after saving the data to the table, you are going to have the problem id then you can use it as FK in the same method.
if i say, there is two table and you are going to use the first table primary key in the second table as FK. Then you need to complete the first table row insertion. after excuting the query for the first table, you will get the primary key of that row and you can use easily in the second table as FK.

Related

How to prevent duplicates in database

I have been trying to prevent duplicates and get an error message to show up if a duplicated Email or Username (or both) has been typed, I wonder what's wrong with my code.
I have tried some of the solutions for similar problems, but they didn't seem to work. I am not sure if there is something wrong with my code or another way of doing it. It just runs and executes the code and the results gets added to the database, even if they are duplicated.
sqlCon.Open();
string query = "SELECT COUNT(*) FROM SSUser WHERE username=#username AND password=#password AND email=#email";
SqlCommand c1 = new SqlCommand(query);
SqlCommand c = new SqlCommand("insert into SSUser values(#username, #password, #email)", sqlCon);
c.Parameters.AddWithValue("#username", register_username.Text);
c.Parameters.AddWithValue("#password", register_password.Text);
c.Parameters.AddWithValue("#email", register_mail.Text);
SqlCommand check_username = new SqlCommand("SELECT COUNT(*) FROM SSUser WHERE (username=#username) AND (email = #email)", sqlCon);
check_username.Parameters.AddWithValue("#username", register_username.Text);
check_username.Parameters.AddWithValue("#email", register_mail.Text);
int check = (int)check_username.ExecuteScalar();
if (check > 0)
{
register_error.Visible = true;
}
else
{
if (register_c.Text != register_password.Text)
{
register_error.Visible = true;
}
else
{
c.ExecuteNonQuery();
register_username.Text = "";
register_password.Text = "";
register_c.Text = "";
register_mail.Text = "";
Response.Redirect("Login.aspx");
}
}
Adding to Gordon Linoff's answer:
After you added the unique constraint to the appropriate attributes in your database you should be able to catch a SqlException:
try {
c.ExecuteNonQuery();
}
catch (SqlException ex)
{
}
To prevent duplicates, create unique constraints or indexes in the database:
alter table ssuser add constraint unq_ssuser_username unique (username);
alter table ssuser add constraint unq_ssuer_email unique (email);
Any insert or update that would create a duplicate will return an error. You may want to catch the error when you attempt inserts and updates.
Please consider using your database layer for this; here is a working example of T-SQL that does exactly that:
create table [dbo].[SSUser] (
[UserId] bigint not null identity (0, 1)
, [UserName] nvarchar(255) not null
, [Email] nvarchar(255) not null
, [PasswordHash] varbinary(64) null
, constraint [dbo_SSUser_Pk] primary key clustered ([UserName] asc) with (data_compression = page)
);
go
create type [dbo].[ISSUser] as table (
[UserName] nvarchar(255) not null
, [Email] nvarchar(255) not null
, [PasswordHash] varbinary(64) null
, primary key clustered ([UserName] asc)
);
go
create procedure [dbo].[usp_SSUser_Insert] (
#users [dbo].[ISSUser] readonly
)
as
begin;
set nocount on;
set xact_abort on;
insert into [dbo].[SSUser] (
[UserName]
, [Email]
, [PasswordHash]
)
output [inserted].[UserId]
, [inserted].[Email]
, [inserted].[PasswordHash]
select a.[UserName]
, a.[Email]
, a.[PasswordHash]
from #users as a;
end;
And the accompanying C# code that'll call the procedure and trap any errors:
var connectionString = "Data Source=.;Initial Catalog=Koholint;Trusted_Connection=True;";
var userName = register_username.Text;
var email = register_mail.Text;
var passwordHash = register_password.HashBytes;
try {
var tableMetadata = new[] {
new SqlMetaData("UserName", SqlDbType.NVarChar, 255),
new SqlMetaData("Email", SqlDbType.NVarChar, 255),
new SqlMetaData("PasswordHash", SqlDbType.VarBinary, 64),
};
var tableValues = new SqlDataRecord(tableMetadata);
tableValues.SetValue(0, userName);
tableValues.SetValue(1, email);
tableValues.SetValue(2, passwordHash);
using (var connection = new SqlConnection(connectionString))
using (var command = connection.CreateCommand()) {
command.CommandText = "[dbo].[usp_SSUser_Insert]";
command.CommandTimeout = 15;
command.CommandType = CommandType.StoredProcedure;
var tableParameter = new SqlParameter("users", SqlDbType.Structured);
tableParameter.TypeName = "[dbo].[ISSUser]";
tableParameter.Value = new[] { tableValues };
command.Parameters.Add(tableParameter);
connection.Open();
using (var reader = command.ExecuteReader()) {
do {
while (reader.Read()) {
Console.WriteLine(reader.GetInt64(0)); // TODO: something more useful, or maybe nothing...
}
} while (reader.NextResult());
}
}
}
catch (SqlException e) {
register_error.Visible = true;
if (e.Number == 2627) {
// special handling for primary key/unique constraint violation
}
else {
throw; // rethrow everything else
}
}
register_username.Text = "";
register_password.Text = "";
register_mail.Text = "";
Response.Redirect("Login.aspx");
Why bother with all of this?
Number one, it truly prevents duplicates. It makes absolutely no sense to check for duplicates in client code if you want to prevent them in the database; simply prevent them from ever happening using a primary key or unique constraint.
Another major benefit is that we save at least one trip to the database. The OP's original code has to query the database in order to check for duplicates; not only does this fail in the face of concurrent writers who use the same key value but it is also quite expensive in terms of time.
The table-valued parameter is used in order to encapsulate all inputs into a single "interface" that makes it easier to specify the contract between C# and T-SQL. It also has the side benefit of allowing us to insert many different users in a single transaction instead of making calls in some sort of loop.

SQLCommand, create table

I want to create table in my database after button click. In Button_Click function I have a code
SqlConnection conn = new SqlConnection(#"MyConnectionString");
conn.Open();
SqlCommand cmd = new SqlCommand("CREATE TABLE '" + tableName+ "' (IdPy INT IDENTITY(1,1), Question NVARCHAR (MAX) NOT NULL, IsChecked BIT NOT NULL, CONSTRAINTPK_'" + tableName+ "' PRIMARY KEY(Id) )", conn);
cmd.ExecuteNonQuery();
conn.Close();
tableName is my String variable (its value 2018-04-18 asd - yes, I want the table with such a name). And I have an error after button click:
System.Data.SqlClient.SqlException: 'Incorrect syntax near '2018-04-18 asd'.'
I think that the problem is in my SqlCommand. I would be gratefull if you could help me solve that problem.
It looks like the tableName variable is 2018-04-18 asd. If that really is the correct table name, you need to escape it (and the constraint) in square brackets:
SqlCommand cmd = new SqlCommand("CREATE TABLE [" + tableName + "] (IdPy INT IDENTITY(1,1), Question NVARCHAR (MAX) NOT NULL, IsChecked BIT NOT NULL, CONSTRAINT [CONSTRAINTPK_" + tableName+ "] PRIMARY KEY(Id) )", conn);
You should escape ([...] in case of MS SQL) table and constraint names:
//DONE: wrap IDisposable into using
using(SqlConnection conn = new SqlConnection(#"MyConnectionString")) {
conn.Open();
//DONE: Make sql readable. Can you see that you've skipped CONSTRAINT keyword?
string sql =
$#"CREATE TABLE [{tableName}] (
-- Fields
IdPy INT IDENTITY(1,1),
Question NVARCHAR (MAX) NOT NULL,
IsChecked BIT NOT NULL,
-- Constraints
--DONE: Constraint key word (optional in some RDBMS) added
CONSTRAINT [CONSTRAINTPK_{tableName}] PRIMARY KEY(Id)
)";
//DONE: wrap IDisposable into using
using (qlCommand cmd = new SqlCommand(sql, conn)) {
cmd.ExecuteNonQuery();
}
}
It might be easier to identify issues with your SQLCommand by using a string variable and parameterised string formatting. An example:
string query = "CREATE TABLE #tablename (IdPy INT IDENTITY(1,1),
Question NVARCHAR (MAX) NOT NULL, IsChecked BIT NOT NULL,
CONSTRAINTPK_#tablename PRIMARY KEY(Id) )";
string param = new {#tablename = txttable.txt(example)};
SqlCommand cmd = new SqlCommand(query, param, conn);
This might help step through to make sure that the variable you have to inspect more concise.

Index was outside the bounds of the array SqlDataReader.GetDecimal

I want to sum up my Amount column, but I receive this error message
Additional information: Index was outside the bounds of the array.
Here is the definition of the Balance table from SQL database:
CREATE TABLE [dbo].[Balance]
(
[BalanceID] [int] IDENTITY(1,1) NOT NULL,
[Sn] [nvarchar](50) NULL,
[Description] [nvarchar](1000) NULL,
[Date] [date] NULL,
[Amount] [decimal](8, 0) NULL
)
Here is the code C# that accesses the table via SQL inline
cn.Open();
SqlCommand cmd = cn.CreateCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = "Select sum(Amount) from Balance where Sn=#sn and Date Between #SD and #ED";
cmd.Parameters.Add(new SqlParameter("#sn", txtSn.Text));
cmd.Parameters.Add(new SqlParameter("#SD", DTPStart.Text));
cmd.Parameters.Add(new SqlParameter("#ED", DTPEnd.Text));
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
Debt = reader.GetDecimal(4);
}
reader.Close();
cn.Close();
Any help will be much appreciated!
Method GetDecimal gets the value of column having zero-based index provided as argument of method call.
You're returning only one column in your query Select sum(Amount) from Balance, but trying to achieve fifth column in reader.GetDecimal(4)
So change your code to
Debt = reader.GetDecimal(0);
Update: As it was noted in comments - in your particular case you don't need ExecuteReader at all. Since you're returning single value from server - you can use ExecuteScalar instead, which executes the query, and returns the first column of the first row in the result set returned by the query.

Operand type clash: nvarchar is incompatible with user-defined table type

I'm new here and I'm facing a trouble currently, my scenario is that I wanted to insert and update data from Excel into a SQL Server table.
For the insert part it works perfectly but when it comes to update I have no idea how should I do that. I have search for few methods and I found this is the most comfortable for me by using stored procedure.
Here is my code that I'm using now. When I try it gave me this error:
Operand type clash: nvarchar is incompatible with user-defined table type
--- Stored procedure ---
CREATE PROCEDURE [dbo].[chkUpdate]
#Operator IC_CHK READONLY
AS
BEGIN
set nocount on;
MERGE INTO tb_Operator c1
USING #Operator c2 ON c1.IC = c2.IC
WHEN MATCHED THEN
UPDATE SET
c1.Name = c2.Name,
--c1.IC = c2.IC,
c1.Email = c2.Email,
c1.Status = c2.Status,
c1.Datetime = c2.Datetime
WHEN NOT MATCHED THEN
INSERT VALUES(c2.Name, c2.IC, c2.Email, c2.[Status], c2.[Datetime]);
end
--- User-defined table type ---
CREATE TYPE [dbo].[IC_CHK] as table
(
[Id] [int] NULL,
[Name] [nvarchar](50) NULL,
[IC] [bigint] NULL,
[Email] [nvarchar](MAX) NULL,
[Status] [nvarchar](50) NULL,
[Datetime] [datetime] NULL
)
VS 2010 code:
protected void btnImport_Click1(object sender, EventArgs e)
{
int i = 0;
try
{
string path = string.Concat(Server.MapPath("~/Excel/" + UploadExcel.FileName));
UploadExcel.SaveAs(path);
String strCon = string.Format("Provider=Microsoft.Ace.OLEDB.12.0;Data Source={0}; Extended Properties=Excel 12.0;",path);
OleDbDataAdapter myda = new OleDbDataAdapter("SELECT * FROM [sheet1$]", strCon);
DataTable myds = new DataTable();
myda.Fill(myds);
for (i = 0; i <= myds.Rows.Count - 1; i++)
{
String constr = ConfigurationManager.ConnectionStrings["conn"].ConnectionString;
using (SqlConnection con = new SqlConnection(constr))
using (SqlCommand cmd = new SqlCommand("chkUpdate"))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = con;
cmd.Parameters.AddWithValue("#Operator", path);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
}
MsgBox1.alert("Import success");
View.Visible = true;
vBinds();
}
catch (Exception ex)
{
MsgBox1.alert(ex.Message);
}
}
Do check for me and I'm appreciate it. Thank you
P/S: I double confirm that my user-defined table type has the same data type with my table.
In the INSERT in your MERGE statement, I would recommend to explicitly define the columns you're inserting into. Most likely, that's the cause of the error - you're inserting your columns - but you're not specifying which target columns those should be inserted into.
Since you're not specifying that, you must supply values for each column in the table, in the exact order in which they are defined - is that really the case?? E.g. what do you insert into your ID column in the table??
Assuming the ID column on your actual database table is an IDENTITY column, I would use (otherwise, you'd have to list ID in the list of columns to insert into as well and provide a value in the VALUES list of values):
WHEN NOT MATCHED THEN
INSERT(Name, IC, Email, [Status], [DateTime])
VALUES(c2.Name, c2.IC, c2.Email, c2.[Status], c2.[Datetime]);
and I would also recommend not to use T-SQL reserved keywords like status or datetime as your column names - you're just asking for trouble doing so. Use more expressive names - something that really relates to your business domain - not just datetime.....

C# database Column name or number of supplied values does not match table definition

Have just started working with C# and sql and have been trying to use a database to store information, but not 100% on the syntax of it all and have been piecing it together, but have not been able to get past this error, any help would be appreciated, it is probably only something simple i have looked over.
here is the C# code i am using to try and access the database
SqlConnection myConnection = new SqlConnection(#"Data Source=(LocalDB)\v11.0;AttachDbFilename=""F:\Bar admin\Bar admin\Database.mdf"";Integrated Security=True");
SqlCommand DatabaseNew = new SqlCommand("insert into Events Values(#Name, #Date, #Price, #Tickets, #Descrip)");
myConnection.Open();
// adds the event information to the database
DatabaseNew.Parameters.AddWithValue("#Name", TxtName.Text);
DatabaseNew.Parameters.AddWithValue("#Date", dateTimePicker1.Value);
DatabaseNew.Parameters.AddWithValue("#Price", TxtName.Text);
DatabaseNew.Parameters.AddWithValue("#Tickets", Convert.ToInt16(TxtTicketNum.Text));
DatabaseNew.Parameters.AddWithValue("#Descrip", TxtDesc.Text);
DatabaseNew.Connection = myConnection;
int n = DatabaseNew.ExecuteNonQuery();
if (n>0)
{
MessageBox.Show("Event" + TxtName.Text + "Added");
}
myConnection.Close();
and the sql code
CREATE TABLE [dbo].[Events] (
[Id] INT NOT NULL,
[Name] NCHAR (10) NULL,
[Date] DATETIME NULL,
[Price] NCHAR(10) NULL,
[Tickets] INT NULL,
[TicketsSold] INT NULL,
[Descrip] NVARCHAR(50) NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
Again any help would be much apreaciated, thank you.
It is expecting all fields with exact order. So Id and TicketsSold are missing and causing error. You should change to:
SqlCommand DatabaseNew = new SqlCommand("insert into Events
(Name,Date,Price,Tickets,Decrip) Values(#Name, #Date, #Price, #Tickets, #Descrip)");
You are not passing ID and it doesn't appear that your ID is set to Auto increment.

Categories