handle sql replication issues in c# - c#

I am new to this sql replication. We have 4 subscribers. I have one column in one of the table has unique key. the data type is nvarchar. if subscriberA inserts a row with 'zxc' and subscriber B inserts a row with 'zxc'. One of the insert fails. how can i handle this in the application show the user proper message to the users.
For example I have Two Subscribers:
1.SubA
2.SubB
I have a table.
Table name : Names
I have City column(nvarchar) in my Names table and it is unique key.
User 'A' connects to SubA.
user 'B' connects to SubB.
'A' inserts a row with 'JAKARTA' in to Names table # 10:30 am. It will take 20 min to update the publisher.
'B' inserts a row with 'JAKARTA' in to Names table #10:35 am.
I have the unique constraint on the table so User A's input is updated in the publisher #10:50.
But user B's input caught in the conflicts.
I want the city column should be unique across all the subs.
How could i handle this? How should i display proper message for user B? How should i do validation across all the subscribers?
My application is winforms. I am using textbox for city.
I am validating on button click event. Ideal solution will be if i can able to capture this in click event
and display Message like "One record already exist with this name.try again."
private int Validation( string str)
{
SqlConnection conn = new SqlConnection("ConnectionString");
conn.Open();
SqlCommand cmd = new SqlCommand("SELECT COUNT(*) FROM dbo.Names where city = #str", conn);
cmd.Parameters.Add("#City", SqlDbType.VarChar, 100);
cmd.Parameters["#City"].Value = str;
int count = (Int32) cmd .ExecuteScalar();
conn.Close();
return count;
}
private void button1_Click(object sender, System.EventArgs e)
{
try
{
if(Validation(testbox1.text) == 0)
{
SqlConnection conn = new SqlConnection("ConnectionString");
string sql = "INSERT INTO dbo.Names ( city) VALUES( #City)";
SqlCommand cmd = new SqlCommand(sql, conn);
cmd.Parameters.Add("#City", SqlDbType.VarChar, 100);
cmd.Parameters["#Name"].Value = textbox1.text;
cmd.ExecuteNonQuery();
}
else
{
//display message
}
}
catch(Exception ex)
{
}
}
Let me know if you need more information

This is an application/database design issue. In this case the first subscriber to synchronize their local copy of the Names table with the publication server wins and the second will get an error. The best solution is to avoid collisions altogether and add another column as part of the unique key (subscriber id?) which will make the record unique or just remove the unique constraint altogether and have a server process cull the duplicate city names later if needed.

Related

Getting Data from the existing data #c.net

I have used this code to display the employee's first name and last name from the provided database when an employee id number is typed into the text box and the “Find” button is clicked. But I will also need Previous and Next Button to display previous and next records respectively in textbox as well. Is there a method like MovePrevious and MoveNext sth to get it?
Here is my code :
private void button1_Click(object sender, EventArgs e)
{
string constr = #"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\\CCEMPLOYEE.mdb";
OleDbConnection con = new OleDbConnection(constr);
{
using (OleDbCommand cmd = new OleDbCommand("SELECT emp_fname, emp_lname, emp_mi FROM Employee WHERE emp_id =#ID "))
{
if (idText.Text != "")
{
cmd.Parameters.AddWithValue("#ID", int.Parse(idText.Text));
cmd.CommandType = CommandType.Text;
cmd.Connection = con;
con.Open();
using (OleDbDataReader sdr = cmd.ExecuteReader())
{
try
{
sdr.Read();
fnameText.Text = sdr["emp_fname"].ToString();
lnameText.Text = sdr["emp_lname"].ToString();
miText.Text = sdr["emp_mi"].ToString();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
else
{
MessageBox.Show("You did not enter any ID", "Enter the ID ",
MessageBoxButtons.OK);
}
con.Close();
}
}
}
You won't be able to go Next or Previous in your provided code because you only have one record in your recordset.
You will also have the problem that you haven't specified the basis for what "Previous" and "Next" mean, the framework has no idea that you mean next by emp_id (or do you mean next by alphabetical surname?)
I would suggest your Prev and Next buttons need to be aware of what "this" record is, and use that as a parameter to run a similar piece of code when they are clicked. You could add an int property to your class, have the Find button store the emp_id into that property, and then have the Next and Prev buttons call almost identical code except with the SQL adjusted to something like "SELECT TOP 1 emp_fname, emp_lname, emp_mi FROM Employee WHERE emp_id > #ID ORDER BY emp_id ASC" (note I don't have anything on me to test this at the moment, you might need to sort DESC).
The reason I've suggested TOP 1, > and ORDER By is I don't know if you have a guarantee that you have sequential emp_id - obviously if you know they're sequential and can guarantee that always you could just go "SELECT... WHERE emp_id = #ID-1" for Prev...
This answer is academic, not practical, I don't suggest this is a good solution to a production scenario - it's an answer to your question.

How can I make it so only the logged in user view his own grade?

This is the student info table where the student info comes from //This is the login code for the user and it get the user log in info from the studentinfo table.
private void btnlogin_Click(object sender, EventArgs e)
{
i = 0;
con.Open();
MySqlCommand cmd = con.CreateCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = "select * from stdinfotable WHERE Username='" + textuser.Text + "' and Password= '" + textpass.Text + "'";
cmd.ExecuteNonQuery();
DataTable dt = new DataTable();
MySqlDataAdapter dta = new MySqlDataAdapter(cmd);
dta.Fill(dt);
i = Convert.ToInt32(dt.Rows.Count.ToString());
if(i==0)
{
MessageBox.Show("Error");
}
else
{
this.Hide();
StudentPage f = new StudentPage();
f.Show();
MySqlDataAdapter data = new MySqlDataAdapter("ViewAllGrades", con);
data.SelectCommand.CommandType = CommandType.StoredProcedure;
DataTable dtTable = new DataTable();
data.Fill(dtTable);
dtTable.Select("Last_Name");
f.stdGrade.DataSource = dtTable;
f.stdGrade.Columns[0].Visible = false;
}
con.Close();
}
This is the ViewlAllGrades stored procedure where the grade info is returned from
CREATE DEFINER=`root`#`localhost` PROCEDURE `ViewAllGrades`()
BEGIN
SELECT *
FROM Grades;
END
I am trying to make it so only the logged in user can view his own grade rather than viewing every user grade. So I am wondering should I try to do within the stored procedure or in Visual Studio and how would I achieve such thing? Also my primary keys which are ID are from both table are auto incremented so I cant not necessarily use those
Right now your stored procedure is selecting all of the grades. First thing to do would be to parameterize the query, accepting the user as the input, and using the WHERE clause to find only the grades for that student. Without seeing your tables, I cannot tell you exactly what this would look like, but as an example: SELECT * FROM Grades WHERE StudentId = #StudentId;
The second thing you need to figure out is how to get the current user's student id and then pass it to the stored procedure. Again, without seeing your tables, I can hardly guess. It appears you already have the username of the current user. Are you able to run a query to find the studentId from the username? If so, then you first need to run that procedure with username as the parameter to get the studentId.
A third concern is with your first query. Since it has not been parameterized, it is susceptible to SQL injection attacks. This poses a MAJOR security risk. If someone maliciously enters a username or password, they can escape the SQL you intended to run and drop all of your tables.
Hopefully that helps!

I cannot save data to my SQL database using C#

I am new to C#. I am trying to save the numbers into a SQL Server database table (locally) but I get an error:
Cannot insert the value NULL into column
My code:
private void SaveBtn_Click(object sender, EventArgs e)
{
try
{
SqlConnection conn = new SqlConnection(#"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=C:\Users\fn1965\Desktop\Work\TESTDB\NumDB.mdf;Integrated Security=True;Connect Timeout=30");
conn.Open();
string insert_query = "INSERT into [NumericTable] (Num1, Num2, Total) VALUES (#Num1, #Num2, #Total)";
SqlCommand cmd = new SqlCommand(insert_query, conn);
cmd.Parameters.AddWithValue("#Num1", textBox1.Text);
cmd.Parameters.AddWithValue("#Num2", textBox2.Text);
cmd.Parameters.AddWithValue("#Total", textBox3.Text);
cmd.ExecuteNonQuery();
MessageBox.Show("Record saved");
conn.Close();
}
catch (Exception ex)
{
MessageBox.Show("EROR:"+ ex.ToString());
}
}
Table schema
You can see in the image that the column Id is the only one that does not support null values. Since the column is not identity and as you are not providing a value on your insert, then the INSERT fail with the given exception. This code will work (only if there isn't a record with Id = 1 already):
string insert_query = "INSERT into [NumericTable] (Num1,Num2,Total, Id) Values (#Num1,#Num2,#Total, #id)";
SqlCommand cmd = new SqlCommand(insert_query, conn);
cmd.Parameters.AddWithValue("#Num1", textBox1.Text);
cmd.Parameters.AddWithValue("#Num2", textBox2.Text);
cmd.Parameters.AddWithValue("#Total", textBox3.Text);
cmd.Parameters.AddWithValue("#Id", 1);
cmd.ExecuteNonQuery();
I assume that this is obviously not the desired fuctionality. What you should do is either set the Id column to identity = true or set a value on the insert.
I also encourage you to not use AddWithValue method since it can lead you to some undesired problems. You can read more here: https://blogs.msmvps.com/jcoehoorn/blog/2014/05/12/can-we-stop-using-addwithvalue-already/
That screenshot you took of your table columns design; get back to that, then click the id column, look in the Properties grid for Identity Specification (might need to expand it) and set it to Yes. Set other properties relevant to your needs and save the table.
Borrowed from another SO question:
There are ways to do this from script but they're generally longer/more awkward than using the UI in management studio.
This will (should) change th column so it auto inserts an incrementing number into itself when you insert values for other rows. Someone else has posted an answer as to how to insert values for it yourself but my recommendation to you as a learner is to use auto increment to save the additional needless complication of providing your own primary key values

Datarow id string not matching with object id string even though they are identical strings after checking debug

I am trying to search through a list of ID strings in my database where an ID string is equal to the one of the object i am trying to create. The id being created is in a factory design pattern where a train type "express" is made with an ID of "1E45". After this ID is created, it increments the number section after the letter and then that can be used for the next train added.
When searching through the list with a foreach it returns the id from the database that is similar to the one trying to be created.
But when I try to match these two after using toString to change them both and match in an IF. The match returns false even though when I check the debug it is exactly the same?
It then just continues on to try and add a new object with that ID that already exists and crashes.
What am I doing wrong? it doesn't make sense after checking the values being matched and it saying false.
Here is the code I have set up:
//Create sql command variables to create new commands
SqlCommand insert = new SqlCommand();
SqlCommand checkID = new SqlCommand();
//Set the command type to text
insert.CommandType = CommandType.Text;
checkID.CommandType = CommandType.Text;
//Searches for an ID in the database that matches one that is trying to be created
checkID.CommandText = "SELECT id FROM Train WHERE id = #trainID";
//Parameters for checking ID in database
checkID.Parameters.AddWithValue("#trainID", train.TrainID);
//Set the connection for the command for the checkID sql connection
checkID.Connection = con;
//Start the connection
con.Open();
DataTable dt = new DataTable();
SqlDataAdapter adapter = new SqlDataAdapter(checkID);
adapter.Fill(dt);
dt.Load(checkID.ExecuteReader());
//Item last = Module.
foreach (DataRow i in dt.Rows)
{
if (i.ToString() == train.TrainID.ToString())
{
MessageBox.Show("This ID already exists! " + train.TrainID);
return;
}
}
//Close the connection
con.Close();
//Set the text for the command to insert data to the database connected to
insert.CommandText = "INSERT Train (id, departure, destination, type, intermediate, departure_time, departure_date, sleeperBerth, firstClass) " +
"VALUES ( #trainID , #departure, #destination, #type, #intermediate, #dep_time, #dep_date, #sleep, #first)";
//Parameters for adding values from the train object to the database
insert.Parameters.AddWithValue("#trainID", train.TrainID);
insert.Parameters.AddWithValue("#departure", train.Departure);
insert.Parameters.AddWithValue("#destination", train.Destination);
insert.Parameters.AddWithValue("#type", train.Type);
insert.Parameters.AddWithValue("#intermediate", intStops);
insert.Parameters.AddWithValue("#dep_time", train.DepartureTime);
insert.Parameters.AddWithValue("#dep_date", train.DepartureDay);
insert.Parameters.AddWithValue("#sleep", train.SleeperBerth);
insert.Parameters.AddWithValue("#first", train.FirstClass);
//Set the connection for the command for the insert sql connection
insert.Connection = con;
//Start the connection
con.Open();
//Execute the command specified
insert.ExecuteNonQuery();
//Close the connection
con.Close();
Sounds like you need to change your column id in the table train to an IDENTITY column, and let the database handle the ID assignment:
ALTER TABLE dbo.Train ALTER COLUMN id int IDENTITY(1,1); --data type guessed
Then, in your application, you don't need to generate a value for ID, nor do declare it in your INSERT statement (either in the list of columns to INSERT into or in your VALUES clause).

C# locking records multiuser

I am looking for suggestions to handle multi-user accessing a C#-Sql server application. C# program selects Top 5 rows from a table where date is null and then updates those records based on input from user. If more than one person is using the app, how can I make sure, data is saved consistently? I am using a grid control to show the data & a button which calls the SaveToDataBase procedure. Here's the part code
protected void Page_Load(object sender, EventArgs e)
{
string sqlSel = #" SELECT TOP 5 r.[keyid], name
FROM db1.Table1 r where date is null
GROUP BY r.keyid, name; ";
if (!IsPostBack)
{
using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["SQLConnectionString"].ConnectionString))
{
connection.Open();
SqlCommand cmdSel = new SqlCommand(sqlSel, connection);
SqlDataReader reader1 = cmdSel.ExecuteReader();
while (reader1.Read())
{
DataSet ds = GetData(sqlSel);
if (ds.Tables.Count > 0)
{
GridView1.DataSource = ds;
GridView1.DataBind();
}
else
{
Response.Write("Unable to connect to the database.");
}
}
connection.Close();
}
}
protected void SaveToDatabase()
{
string datenow = DateTime.Now.ToString(#"MM\/dd\/yyyy h\:mm tt");
string sqlUpd = #"UPDATE [db1].[Table1] set DateVerified=#datenow where KeyID=#keyID and name=#name";
try
{
using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["SQLConnectionString"].ConnectionString))
{
connection.Open();
SqlCommand cmdUpd = new SqlCommand(sqlUpd, connection);
cmdUpd.Parameters.Add("#datenow", SqlDbType.DateTime);
cmdUpd.Parameters["#datenow"].Value = datenow;
Int32 rowsAffected = 0;
rowsAffected = cmdUpd.ExecuteNonQuery();
connection.Close();
}
private DataSet GetData(string cmdSel)
{
String strConnString = System.Configuration.ConfigurationManager.ConnectionStrings["SQLConnectionString"].ConnectionString;
DataSet ds = new DataSet();
try
{
SqlConnection con = new SqlConnection(strConnString);
SqlDataAdapter sda = new SqlDataAdapter(cmdSel, con);
sda.Fill(ds);
Thanks
Rashmi
You could use something like optimistic concurrency with a Version Id that should be updated every time someone changes the row, for example:
// Table User
create table User(
Id int primary key,
Name varchar(300) not null,
Version long not null default 0
);
// the select code
select Id, Name, Version
from User
where Id = ?
// the update code
update User
set
Name = ?
Version = Version + 1
where
Id = ?
and Version = ?
Imagine two users go to a screen where you can update the name of the user. Imagine the following order of your code:
UserA: select Id, Name, Version from User where Id = 1; // (1, John Doe, 0)
UserB: select Id, Name, Version from User where Id = 1; // (1, John Doe, 0)
UserA: update User set Name = 'Jane Doe' Version = Version + 1 where Id = 1 and Version = 0; // 1 row updated
UserA: commit;
UserB: update User set Name = 'Mr John Doe' Version = Version + 1 where Id = 1 and Version = 0; // 0 row updated which means someone updated the row
UserB: rollback; // you should rollback and send an info to the user that someone changed the information he was seeing (refresh the screen)
With this approach you prevent the need for locking the rows. Every time you update o delete something and the number of affected rows are different than the ones you expected, optimistic concurrency should be applied.
Most ORM frameworks already implement this approach by using a Version or a Timestamp, but the logic is the same. Keep in mind that the update of the Version field should always be performed.
You should get an idea how to implement with this pratical example about how an ORM (Entity Framework in this case) implement this logic here: http://www.asp.net/mvc/tutorials/getting-started-with-ef-5-using-mvc-4/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application
There are several different approaches:
You can add the WHERE-condition to your update statement. First user updating the records will get affected-rows = 5, the next one affect-rows=0 so you know his data cannot be save but must be reloaded (and eventually merged).
Or you can set the records as assigned to the user when fetching the data so the next one gets different records (WHERE (AssignedUser <> #MYUSER)).
If you really want to lock - which means another app cannot read the top 5 records until the first user saved - you could do reading and writing within one transaction with a very restrictive IsolationLevel.
To protect from updating a value with date assigned
update table set value = 1, date = getdate()
where id = 1 and date is null
I think you will get 0 rows updated if date is null
So you could provide user feedback
But 5 at a time is going to be a problem as that is going to increase the chance of collision (date is not null)
I would have some type of locking
Maybe assign a date of 1/1/1900 prior to update
update table set value = 1, date = getdate()
where id = 1 and (date is null or date = '1/1/1900')
Then have some mechanism to set 1/1/1900 to null periodically for orphaned

Categories

Resources