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
Related
The application I am developing is meant to be a quick and easy tool to import data to our main app. So the user loads in a CSV, meddles with the data a little and pushes it up to the database.
Before the data is pushed to the database, I have a verification check going on which basically says, "Does a customer exist in the database with the same name, account and sort codes? If so, put their guid (which is known already) into a list."
The problem is, the result variable is always 0; this is despite the fact that there is duplicate test data already in my database which should show a result. Added to that, using SQL Profiler, I can't see a query actually being executed against the database.
I'm sure that the ExecuteScalar() is what I should be doing, so my attention comes to the Parameters I'm adding to the SqlCommand... but I'll be blowed if I can figure it... any thoughts?
string connectionString = Generic.GetConnectionString("myDatabase");
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand check = new SqlCommand("select COUNT(*) from Customers C1 INNER JOIN CustomerBank B1 ON C1.Id = B1.CustomerId WHERE C1.Name = #Name AND B1.SortCode = #SortCode AND B1.AccountNumber = #AccountNumber", conn);
foreach (DataRow row in importedData.Rows)
{
check.Parameters.Clear();
check.Parameters.Add("#Name", SqlDbType.NVarChar).Value = row["Name"].ToString();
check.Parameters.Add("#SortCode", SqlDbType.NVarChar).Value = row["SortCode"].ToString();
check.Parameters.Add("#AccountNumber", SqlDbType.NVarChar).Value = row["AccountNumber"].ToString();
Object result = check.ExecuteScalar();
int count = (Int32)result;
if (count > 0)
{
DuplicateData.Add((Guid)row["BureauCustomerId"]);
}
}
}
Clarification: importedData is a DataTable of the user's data held in this C# application. During the ForEach loop, each row has various columns, a few of those being Name, SortCode and AccountNumber. The values seem to get set in the parameters (but will verify now).
I am updating a row in SQL DB using the code below. The loop works and it updates the row but the problem is that each time it goes through the loop, it only updates one value and the other values are overwritten. So at the end, it has updated but instead of multiple values being inputted to the table for the respective Project ID, it only puts one value for the respective Project ID. I am not receiving any errors for this. Any help is much appreciated.
for (int i = 0; i < cbAvailableEntities.Items.Count - 1; i++)
{
SqlConnection connection = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand("UpdateProjectEntity", connection);
using (connection)
{
connection.Open();
using (cmd)
{
if (cbAvailableEntities.Items[i].Selected)
{
cmd.CommandType = CommandType.StoredProcedure;
//the following is the Project ID for the row being updated.
SqlParameter paramPID = new SqlParameter("#ProjectID", nr.ProjectID);
cmd.Parameters.Add(paramPID);
nr.Entities = cbAvailableEntities.Items[i].Value;
cmd.Parameters.AddWithValue("#CorpID", nr.Entities);
cmd.ExecuteNonQuery();
}
}
}
}
Here is the SQL query for the Stored Procedure "UpdateProjectEntity"
ALTER PROCEDURE [dbo].[UpdateProjectEntity]
#ProjectID int,
#CorpID int
AS
BEGIN
UPDATE [dbo].[ProjectEntity]
SET
[CorpID] = #CorpID
WHERE
ProjectID = #ProjectID
END
Here are screenshots of inputs and results when I run the program.
These are the checkboxes I am saving to the DB
This is the result after I have saved to the DB
I changed the date to show that everything else works in this program.
I can see you save your enity INT, maybe you should save it as a Comma Separated String.
So instead of save 1, you can save 1,2,3
Of course you will have to add some logic before the save building and concat the string. And also need to do some parsing when you read from db doing the split by ,
The other aproach is creating a relation table to indicate with are the options selected.
But this is also have problem when you remove a selection and add new ones.
ProjectID CorpID
1 1
1 2
1 3
The way I resolved this without making any changes to the DB is I used a DELETE Statement to delete the rows with the ProjectID and then I used an insert stored procedure that I have used before. It was a lot faster than creating another table among all that is already in place. So the code looks like this
for (int i = 0; i < cbAvailableEntities.Items.Count - 1; i++) {
SqlConnection connection = new SqlConnection(connString);
SqlCommand com = new SqlCommand("InsertProjectEntity", connection);
SqlCommand dcm = new SqlCommand();
using(connection) {
//First time going through the loop, i = 0 is true.
if (i == 0) {
connection.Open();
using(com) {
//This will remove anything in the DB related to the ProjectID being edited.
dcm.Connection = connection;
dcm.CommandText = "DELETE FROM [dbo].[ProjectEntity] WHERE ProjectID = " + _pID;
dcm.ExecuteNonQuery();
//This will insert all items checked in the checkboxlist but will not insert the unchecked.
if (cbAvailableEntities.Items[i].Selected) {
com.CommandType = CommandType.StoredProcedure;
SqlParameter paramPID = new SqlParameter("#ProjectID", nr.ProjectID);
com.Parameters.Add(paramPID);
nr.Entities = cbAvailableEntities.Items[i].Value;
com.Parameters.AddWithValue("#CorpID", nr.Entities);
com.ExecuteNonQuery();
}
}
} else {
connection.Open();
using(com) {
//This will insert all items checked in the checkboxlist but will not insert the unchecked.
if (cbAvailableEntities.Items[i].Selected) {
com.CommandType = CommandType.StoredProcedure;
SqlParameter paramPID = new SqlParameter("#ProjectID", nr.ProjectID);
com.Parameters.Add(paramPID);
nr.Entities = cbAvailableEntities.Items[i].Value;
com.Parameters.AddWithValue("#CorpID", nr.Entities);
com.ExecuteNonQuery();
}
}
}
}
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.
I am creating a winform application in c#.and using sql database.
I have one table, employee_master, which has columns like Id, name, address and phone no. Id is auto increment and all other datatypes are varchar.
I am using this code to get the next auto increment value:
string s = "select max(id) as Id from Employee_Master";
SqlCommand cmd = new SqlCommand(s, obj.con);
SqlDataReader dr = cmd.ExecuteReader();
dr.Read();
int i = Convert.ToInt16(dr["Id"].ToString());
txtId.Text = (i + 1).ToString();
I am displaying on a textBox.
But when last row from table is deleted, still I get that value which is recently deleted in textbox
How should I get the next autoincrement value?
To get the next auto-increment value from SQLServer :
This will fetch the present auto-increment value.
SELECT IDENT_CURRENT('table_name');
Next auto-increment value.
SELECT IDENT_CURRENT('table_name')+1;
------> This will work even if you add a row and then delete it because IDENT_CURRENT returns the last identity value generated for a specific table in any session and any scope.
try this:
SELECT IDENT_CURRENT('tbl_name') + IDENT_INCR('tbl_name');
If you are using Microsoft SQL Server. Use this statement to get current identity value of table. Then add your seed value which you have specified at time of designing table if you want to get next id.
SELECT IDENT_CURRENT(<TableName>)
As for me, the best answer is:
dbcc checkident(table_name)
You will see two values (probably same)
current identity value , current column value
When you delete a row from the table the next number will stay the same as it doesnt decrement in any way.
So if you have 100 rows and you deleted row 100. You would have 99 rows but the next number is still going to be 101.
select isnull((max(AddressID)+1),1) from AddressDetails
the max(id) will get you maximum number in the list pf employee_master
e.g. id = 10, 20, 100 so max will get you 100
But when you delete the record it must have been not 100
So you still get 100 back
One important reason for me to say this might be the issue because you are not using order by id in your query
For MS SQL 2005 and greater:
Select Cast(IsNULL(last_value,seed_value) As Int) + Cast(increment_value As Int) As NextID
From sys.identity_columns
WHERE NAME = <Table_Name>
Just a thought, if what you wanted was the last auto-number that you inserted on an already open connection try using:
SELECT ##IDENTITY FROM...
from that connection. That's the best way to keep track of what has just happened on a given connection and avoids race conditions w/ other connections. Getting the maximum identity is not generally feasible.
SqlConnection con = new SqlConnection("Data Source=.\SQLEXPRESS;Initial Catalog=databasename;User ID=sa;Password=123");
con.Open();
SqlCommand cmd = new SqlCommand("SELECT TOP(1) UID FROM InvoiceDetails ORDER BY 1 DESC", con);
SqlDataReader reader = cmd.ExecuteReader();
//won't need a while since it will only retrieve one row
while (reader.Read())
{
string data = reader["UID"].ToString();
//txtuniqueno.Text = data;
//here is your data
//cal();
//txtuniqueno.Text = data.ToString();
int i = Int32.Parse(data);
i++;
txtuid.Text = i.ToString();
}
I have this SQL Table:
TABLE: Info
COLUMNS:| Name | Value
--------|----------|------------------
ROW: | Server | 255.255.255.255
ROW: | Host | 212.212.212.212
ROW: | User | Admin
I'm selecting this table like that: SELECT * FROM Info
Now after I got all in this table.
I want to get the value Where Name = 'Server' and put it into the Server variable.
What is the best method to do it in C#?
DataSet? DataReader? And how can I accomplish this?
If you didn't understand what I need here is another good explanation thx to Tim:
I'm trying to get a specified column's value based on the value in another column of the same row
Based on your latest comments, it appears that you want to get all the rows in the table, and then be able to select a given row based on the Name column.
A DataTable would be best if your program is going to need to access the different rows at different times - as long as the DataTable is in memory/cached, you can pull the value for any name at any time.
If you just need to do it once, a SqlDataReader would probably be faster, but its forward-only.
DataTable example:
Assuming you've already filled the DataTable (name info in the example), you can use the Select method:
DataRow[] selectedRows = info.Select("Name = 'Server'");
string serverIP = selectedRows[0]["Value"].ToString();
DateReader example:
Based upon #Kobe's code, simply check the Name each time you advance to the next record, and then pull the Value out:
bool valueFound = false;
while (reader.Read() && !valueFound)
{
if (reader["Name"].ToString() == "Server")
{
serverIP = reader["Value"].ToString();
valueFound = true;
}
}
There are some caveats to be aware of. First, the Select method of the DataTable returns an array of DataRow, so if more than one record has "Server" in the Name column, you'll get multiple results. If that's by design, that's fine - just loop through the array of DataRows.
Second, if there are a lot of rows in the table, or there is the potential down the road, the reader may be slower depending on where the record of interest is in the table. And if you're dealing with the possibility of multiple records in the table matching the Name criteria, it's probably easier all around to just stick with a DataTable.
Is this what you are looking for , if so let me know , i will refine the code with USING key word and remove the sql inline and post you one more answer soon
one more example from google ,
// instantiate and open connection
conn = new
SqlConnection("Server=(local);DataBase=Northwind;Integrated Security=SSPI");
conn.Open();
// don't ever do this!
// SqlCommand cmd = new SqlCommand(
// "select * from Customers where city = '" + inputCity + "'";
// 1. declare command object with parameter
SqlCommand cmd = new SqlCommand(
"select * from Customers where city = #City", conn);
// 2. define parameters used in command object
SqlParameter param = new SqlParameter();
param.ParameterName = "#City";
param.Value = inputCity;
// 3. add new parameter to command object
cmd.Parameters.Add(param);
// get data stream
reader = cmd.ExecuteReader();
// write each record
while(reader.Read())
{
Console.WriteLine("{0}, {1}",
reader["CompanyName"],
reader["ContactName"]);
}
}
finally
{
// close reader
if (reader != null)
{
reader.Close();
}
// close connection
if (conn != null)
{
conn.Close();
}
}
other example...
SqlCommand sqlComm = new SqlCommand("SELECT * FROM Info where name='+server+'", sqlConn);
SqlDataReader r = sqlComm.ExecuteReader();
while ( r.Read() ) {
string name = (string)r["Name"];
Debug.WriteLine(username + "(" + userID + ")");
}
r.Close();