Multiple queries on one transaction. C# winforms with SQLite - c#

I'm learning how to use SQLite, and I'm stuck on this problem for more than a week and I didn't find an answer on my web search.
The problem is that I need to register a bill with it's corresponding lines, and update some information of the bought products, all in the same transaction if some query fails.
When I try to register something, I obtain the "Database is locked" exception, but what I found strange is that I kept the program running while I was creating this question and when I saw it again I found that the "continue" button was available, and then the program finally worked.
So I would like to know what I must improve to not have that exception.
Ah, it's a Winforms application, and only one person will use it, so it will not have concurrency problems (or I think so).
First, this is the connectionString (if I need to add something to it):
"Data Source=D:\De disco c\Documents\Visual Studio 2010\Projects\Racion\Racion\bin\Racion.db;Version=3;"
On the Mapper of the bill's class I have this method:
public static void registrarFactura(Factura f)
{
SQLiteConnection conn = null;
SQLiteTransaction trn = null;
try
{
var parametros = new List<SQLiteParameter>();
var cant = new SQLiteParameter();
cant.ParameterName = "#Cliente";
cant.Value = f.cliente;
parametros.Add(cant);
cant = new SQLiteParameter();
cant.ParameterName = "#Fecha";
cant.Value = DateTime.Now;
parametros.Add(cant);
//Here is one query. I need to know the bill's ID to register the lines and make the update.
String consulta = "Insert into Factura(Cliente, Fecha) VALUES (#Cliente, #Fecha); SELECT last_insert_rowid();";
//Open the connection
conn = ObtenerConection();
//begin transaction
using (trn = conn.BeginTransaction())
{
//Here I register the bill and obtain it's id
int codigo = Convert.ToInt32(Mapper.ejecutaScalar(consulta, CommandType.Text, parametros, conn, trn));
//Now I must register the lines of the bill
String consulta2 = "";
foreach (Linea l in f.lineas)
{
parametros = new List<SQLiteParameter>();
cant = new SQLiteParameter();
cant.ParameterName = "#NLinea";
cant.Value = l.numeroLinea;
parametros.Add(cant);
cant = new SQLiteParameter();
cant.ParameterName = "#Cantidad";
cant.Value = l.cantidad;
parametros.Add(cant);
cant = new SQLiteParameter();
cant.ParameterName = "#CodigoProd";
cant.Value = l.producto.Codigo;
parametros.Add(cant);
cant = new SQLiteParameter();
cant.ParameterName = "#PTotal";
cant.Value = l.PrecioTotal;
parametros.Add(cant);
cant = new SQLiteParameter();
cant.ParameterName = "#Codigo";
cant.Value = codigo;
parametros.Add(cant);
//The query to insert the actual line of the foreach
consulta = "Insert into Linea(IdFactura, IdLinea, IdProducto, Cantidad, Total) VALUES (" + codigo + ", #NLinea, #CodigoProd, #Cantidad, #PTotal)";
Mapper.EjecutaNonQuery(consulta, CommandType.Text, parametros, conn, null);
//Update the stock of the product
if (f.cliente == "")
{
consulta2 = "Update Producto Set Cantidad=Cantidad+#Cantidad Where IdProducto=#CodigoProd;";
}
else
{
consulta2 = "Update Producto Set Cantidad=Cantidad-#Cantidad Where IdProducto=#CodigoProd;";
}
Mapper.EjecutaNonQuery(consulta2, CommandType.Text, parametros, conn, null);
}
//The transaction concludes
trn.Commit();
}
}
catch (SqlException ex)
{
//If there is a problem
trn.Rollback();
}
finally
{
//Close the connection
CerrarConexion(conn);
}
}
And on the mapper class, I have these 2 methods that are used in the previous one:
public static object ejecutaScalar(string sentencia, CommandType tipoComando, List<SQLiteParameter> parametros, SQLiteConnection con, SQLiteTransaction trn)
{
object retorno;
using (SQLiteCommand cmd = new SQLiteCommand())
{
cmd.Connection = con;
cmd.CommandText = sentencia;
cmd.CommandType = tipoComando;
cmd.Parameters.AddRange(parametros.ToArray());
if (trn != null)
cmd.Transaction = trn;
retorno = cmd.ExecuteScalar();
}
return retorno;
}
public static int EjecutaNonQuery(string sentencia, CommandType tipoComando, List<SQLiteParameter> parametros, SQLiteConnection con, SQLiteTransaction trn)
{
int afectadas = -1;
using (SQLiteCommand cmd = new SQLiteCommand())
{
cmd.Connection = con;
cmd.CommandText = sentencia;
cmd.CommandType = tipoComando;
cmd.Parameters.AddRange(parametros.ToArray());
if (trn != null)
cmd.Transaction = trn;
afectadas = cmd.ExecuteNonQuery();
}
return afectadas;
}
Thanks, and sorry if I couldn't explain me better, English is not my native language and I have some difficulties with it :P

The error message "database is locked" means that there is some other connection that has an active transaction.
To ensure that transactions do not stay active, check that all SQL command and transaction objects are used only with using blocks, or otherwise cleaned up. Furthermore, the entire program should use only a single connection object (unless it has multiple threads); you should not re-open it every time (which just makes everything slower because the page cache gets lost).

Related

How can I allow a SqlParameter to accept any value in SqlClient?

I'm trying to create a generic function, using SqlClient in C#, which will accept a value for any column name and return the valid objects. My approach is to create a stored procedure which accepts values for every column, and where a value has not been provided it will be assume that any value is accepted. My code is as below (code is a little unfinished with regards the final requirement):
public async Task<List<OrganisationInstance>> GetOrganisationInstances(string name, int id)
{
List<OrganisationInstance> responses = new List<OrganisationInstance>();
using (SqlConnection connection = new SqlConnection(connectionString))
{
try
{
await connection.OpenAsync();
using (SqlCommand command = new SqlCommand(OrganisationInstancesSP.READ_ORGANISATION, connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("Id", SqlDbType.BigInt).Value = 2;
command.Parameters.Add("CreationDate", SqlDbType.DateTime).Value = "2021-12-14 23:59:25.837";
command.Parameters.Add("CompanyName", SqlDbType.VarChar).Value = "xxxxxxxxxx";
command.Parameters.Add("Postcode", SqlDbType.VarChar).Value = "xxxxxxxxxxx";
command.Parameters.Add("FormationDate", SqlDbType.DateTime).Value = "2021-12-14 23:59:25.837";
command.Parameters.Add("NodeId", SqlDbType.Int).Value = id;
SqlDataReader reader = await command.ExecuteReaderAsync();
while (reader.Read())
responses.Add(new OrganisationInstance { CompanyName = reader["CompanyName"].ToString(), Id = Convert.ToInt32(reader["Id"]) });
}
}
catch (DbException ex)
{
return null;
}
finally
{
connection.Close();
}
}
return responses;
}
The query returns the expected object, when all values are set to the values of an instance/row in the database. However, I cannot find a way to set the column to allow any value.
In SQL, it would be possible to say WHERE CreationDate = CreationDate , which would translate to:
command.Parameters.Add("CreationDate", SqlDbType.DateTime).Value = "CreationDate";
However, this doesn't work in SqlClient. Can anyone suggest how I might achieve this?

If the SELECT SQL Server value is null, the query takes 5 minutes C #

I have a very silly problem. I am doing a select, and I want that when the value comes null, return an empty string. When there is value in sql query, the query occurs all ok, but if there is nothing in the query, I have to give a sqlCommand.CommandTimeout greater than 300, and yet sometimes gives timeout. Have a solution for this?
public string TesteMetodo(string codPess)
{
var vp = new Classe.validaPessoa();
string _connection = vp.conString();
string query = String.Format("SELECT COUNT(*) FROM teste cliente WHERE cod_pess = {0}", codPess);
try
{
using (var conn = new SqlConnection(_connection))
{
conn.Open();
using (var cmd = new SqlCommand(query, conn))
{
SqlDataReader dr = cmd.ExecuteReader();
if(dr.HasRows)
return "";
return codPess;
}
}
}
You should probably validate in the UI and pass an integer.
You can combine the usings to a single block. A bit easier to read with fewer indents.
Always use parameters to make the query easier to write and avoid Sql Injection. I had to guess at the SqlDbType so, check your database for the actual type.
Don't open the connection until directly before the .Execute. Since you are only retrieving a single value you can use .ExecuteScalar. .ExecuteScalar returns an Object so must be converted to int.
public string TesteMetodo(string codPess)
{
int codPessNum = 0;
if (!Int32.TryParse(codPess, out codPessNum))
return "codPess is not a number";
var vp = new Classe.validaPessoa();
try
{
using (var conn = new SqlConnection(vp.conString))
using (var cmd = new SqlCommand("SELECT COUNT(*) FROM teste cliente WHERE cod_pess = #cod_pess", conn))
{
cmd.Parameters.Add("#cod_pess", SqlDbType.Int).Value = codPessNum;
conn.Open();
int count = (int)cmd.ExecuteScalar();
if (count > 0)
return "";
return codPess;
}
}
catch (Exception ex)
{
return ex.Message;
}
}

Is there a way to validate the connection string if the server is closed or down?

I have problem regarding inserting the loop process. So when the connection string determine that the server is down, the inserting process stop looping. My question, is there way to determine whether this connection string is down or not? I have research they answer is to make if condition sqlconn.State == ConnectionState.Open I will show you guys the sample error that I encounter.
string connetionString = null;
MySqlConnection cnn;
connetionString = "server=localhost;database=sample_db_xx;uid=root;pwd=;";
cnn = new MySqlConnection(connetionString);
try
{
var arpp_pro = new List<string>();
cnn.Open();
MySqlCommand command = new MySqlCommand("SELECT store_id,CONCAT(boh,'\\\\sqlexpress') as boh FROM db_cua.stores WHERE " +
"is_active = 1 AND boh != '' ", cnn);
using (MySqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine(reader[0].ToString());
arpp_pro.Add(reader[1].ToString());
}
}
cnn.Close();
foreach (var arpp_pro_data in arpp_pro)
{
string connetionString_SQL = #"Server=" + arpp_pro_data + " \sqlexpress;Database=Site5;User ID=sa;Password=dospos";
//#"Server=" + arpp_pro_data + ";Database=Site5;User ID=sa;Password=dospos";
var date_minus_one_day = DateTime.Today.AddDays(-1);
var formatted_date_minus_one_day = date_minus_one_day.ToString("yyyy-MM-dd");
var year = DateTime.Now.ToString("yyyy");
var month = DateTime.Now.ToString("MM");
var date = DateTime.Today.AddDays(-1);
var date_formatted = date.ToString("dd");
string get_sos_orders_details = #"SELECT
Convert(nvarchar(50),dbo.SOS_ORDERS.OrderId)+ '-'+ Convert(nvarchar(50),dbo.SOS_ORDERS.TransTime) + Convert(nvarchar(50),dbo.Sales.TransactionId)+ Convert(nvarchar(50),dbo.Sales.TotalDeptName) as result,
dbo.Sales.StoreId,
Convert(nvarchar(50),dbo.SOS_ORDERS.TransTime) as TransTime,
dbo.Transactions.OperatorName as Cashier,
dbo.Sales.TotalDeptName as Transaction_Type,
dbo.Sales.TransactionId,
(dbo.SOS_ORDERS.DTOT + dbo.SOS_ORDERS.ASSM) as Cashier_Time,
(dbo.SOS_ORDERS.KIT) as Preparation_Time,
(dbo.SOS_ORDERS.KIT + dbo.SOS_ORDERS.DTOT + dbo.SOS_ORDERS.ASSM) as Total_Time
FROM dbo.SOS_ORDERS INNER JOIN
dbo.Sales ON dbo.SOS_ORDERS.OrderId = dbo.Sales.StoredOrderIndex INNER JOIN
dbo.Transactions ON dbo.Sales.Sequence = dbo.Transactions.Sequence
where dbo.Sales.businessdate= #date_minus_one_day
OR(DATEPART(yy, dbo.SOS_ORDERS.TransTime) = #year
AND DATEPART(mm, dbo.SOS_ORDERS.TransTime) = #month
AND DATEPART(dd, dbo.SOS_ORDERS.TransTime) = #date_today )
AND(dbo.Sales.TotalDeptName in ('01 SALLE MANGER', '02 EMPORTER')
or dbo.Sales.TotalDeptName in ('01 DINE IN', '02 TAKE OUT'))
GROUP BY dbo.SOS_ORDERS.OrderId, dbo.Sales.StoreId, dbo.SOS_ORDERS.TransTime, dbo.SOS_ORDERS.DTOT, dbo.SOS_ORDERS.LINE, dbo.SOS_ORDERS.WIND, dbo.SOS_ORDERS.SERV, dbo.SOS_ORDERS.HOLD,
dbo.SOS_ORDERS.TOTL, dbo.SOS_ORDERS.ASSM, dbo.SOS_ORDERS.CASH, dbo.SOS_ORDERS.FTOT, dbo.SOS_ORDERS.PAY, dbo.SOS_ORDERS.KIT, dbo.Sales.TransactionId,
dbo.Transactions.OperatorName, dbo.Sales.TotalDeptName order by dbo.SOS_ORDERS.TransTime DESC";
using (SqlConnection sqlconn = new SqlConnection(connetionString_SQL))
{
sqlconn.Open();
if (sqlconn.State == ConnectionState.Open)
{
SqlCommand cmd = new SqlCommand(get_sos_orders_details, sqlconn);
cmd.Parameters.AddWithValue("#date_minus_one_day", formatted_date_minus_one_day);
cmd.Parameters.AddWithValue("#year", year);
cmd.Parameters.AddWithValue("#month", month);
cmd.Parameters.AddWithValue("#date_today", date_formatted);
SqlDataReader rs = cmd.ExecuteReader();
while (rs.Read())
{
// access your record colums by using reader
Console.WriteLine(rs["StoreId"]);
cnn.Open();
MySqlCommand comm = cnn.CreateCommand();
comm.CommandText = #"INSERT INTO master_data.so_v2 (StoreId,TransTime,Cashier,Transaction_Type,TransactionId,Cashier_Time,Preparation_Time)
VALUES(#Storeid, #TransTime, #Cashier, #Transaction_Type, #TransactionId, #Cashier_Time, #Preparation_Time)";
comm.Parameters.AddWithValue("#Storeid", rs["StoreId"]);
comm.Parameters.AddWithValue("#TransTime", rs["TransTime"]);
comm.Parameters.AddWithValue("#Cashier", rs["Cashier"]);
comm.Parameters.AddWithValue("#Transaction_Type", rs["Transaction_Type"]);
comm.Parameters.AddWithValue("#TransactionId", rs["TransactionId"]);
comm.Parameters.AddWithValue("#Cashier_Time", rs["Cashier_Time"]);
comm.Parameters.AddWithValue("#Preparation_Time", rs["Preparation_Time"]);
comm.ExecuteNonQuery();
cnn.Close();
}
}
sqlconn.Close();
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
Thank you.
I suppose if all you wanted was a way to test and make sure a connection string is valid and that you can connect to the server and to the database, you could use a method like this:
public bool IsConnectionStringValid(string cs)
{
try
{
using (MySqlConnection conn = new MySqlConnection(cs))
{
conn.Open();
return true;
}
}
catch
{
return false;
}
}
Although I have to admit, in all my years of developing in C#, I've never used anything like this. Normally, as people have said in the comments, you already know before runtime that your connection string is valid and working.

Cannot Use DbContext.Query inside a transaction

I am using EF6 to query a backend database. User can customize a temporary table and query the data from the temporary table. I am using
DataTable result = context.Query(queryStatement);
to get the result and it has been working fine.
Now the query is needed among a serious of other sqlcommand and a transaction is needed. So I have
public static DataTable GetData()
{
using (MyDbContext context = new MyDbContext())
using (DbContextTransaction tran = context.Database.BeginTransaction())
{
try
{
int rowAffected = context.Database.ExecuteSqlCommand(
"UPDATE [MyDb].dbo.[TableLocks] SET RefCount = RefCount + 1 WHERE TableName = 'TESTTABLE1'");
if (rowAffected != 1)
throw new Exception("Cannot find 'TestTable1'");
//The following line will raise an exception
DataTable result = context.Query("SELECT TOP 100 * FROM [MyDb].dbo.[TestTable1]");
//This line will work if I change it to
//context.Database.ExecuteSqlCommand("SELECT TOP 100 * FROM [MyDb].dbo.[TestTable1]");
//but I don't know how to get the result out of it.
context.Database.ExecuteSqlCommand(
"UPDATE [MyDb].dbo.[TableLocks] SET RefCount = RefCount - 1 WHERE TableName = 'TestTable1'");
tran.Commit();
return result;
}
catch (Exception ex)
{
tran.Rollback();
throw (ex);
}
}
}
But this throws an exception while executing context.Query
ExecuteReader requires the command to have a transaction when the connection
assigned to the command is in a pending local transaction. The Transaction
property of the command has not been initialized.
And when I read this article: https://learn.microsoft.com/en-us/ef/ef6/saving/transactions
It says:
Entity Framework does not wrap queries in a transaction.
Is it the reason cause this issue?
How can I use context.Query() inside a transaction?
What else I can use?
I tried all other method, none of them work - because the return datatype cannot be predicted before hand.
I just realized that, the Query method is defined in MyDbContext!
public DataTable Query(string sqlQuery)
{
DbProviderFactory dbFactory = DbProviderFactories.GetFactory(Database.Connection);
using (var cmd = dbFactory.CreateCommand())
{
cmd.Connection = Database.Connection;
cmd.CommandType = CommandType.Text;
cmd.CommandText = sqlQuery;
using (DbDataAdapter adapter = dbFactory.CreateDataAdapter())
{
adapter.SelectCommand = cmd;
DataTable dt = new DataTable();
adapter.Fill(dt);
return dt;
}
}
}
May be you are missing this section -
you are free to execute database operations either directly on the
SqlConnection itself, or on the DbContext. All such operations are
executed within one transaction. You take responsibility for
committing or rolling back the transaction and for calling Dispose()
on it, as well as for closing and disposing the database connection
And then this codebase -
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var sqlTxn =
conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
{
try
{
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.Transaction = sqlTxn;
sqlCommand.CommandText =
#"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
context.Database.UseTransaction(sqlTxn);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
sqlTxn.Commit();
}
catch (Exception)
{
sqlTxn.Rollback();
}
}
}
Specially this one -
context.Database.UseTransaction(sqlTxn);
Sorry guys, as mentioned above, I thought the Query method is from EF, but I examined the code and found it is actually coded by another developer, defined in class MyDbContext. Since this class is generated by EF, and I never think somebody have added a method.
It is
public DataTable Query(string sqlQuery)
{
DbProviderFactory dbFactory = DbProviderFactories.GetFactory(Database.Connection);
using (var cmd = dbFactory.CreateCommand())
{
cmd.Connection = Database.Connection;
cmd.CommandType = CommandType.Text;
cmd.CommandText = sqlQuery;
//And I added this line, then problem solved.
if (Database.CurrentTransaction != null)
cmd.Transaction = Database.CurrentTransaction.UnderlyingTransaction;
using (DbDataAdapter adapter = dbFactory.CreateDataAdapter())
{
adapter.SelectCommand = cmd;
DataTable dt = new DataTable();
adapter.Fill(dt);
return dt;
}
}
}

C# ASP.Net Processing Issue - Reading Table While Deleting Rows

Simply, I have an application that has one page that deletes and then re-adds/refreshes the records into a table every 30 seconds. I have another page that runs every 45 seconds that reads the table data and builds a chart.
The problem is, in the read/view page, every once in a while I get a 0 value (from a max count) and the chart shows nothing. I have a feeling that this is happening because the read is being done at the exact same time the delete page has deleted all the records in the table but has not yet refreshed/re-added them.
Is there a way in my application I can hold off on the read when the table is being refreshed?
Best Regards,
Andy
C#
ASP.Net 4.5
SQL Server 2012
My code below is run in an ASP.Net 4.5 built Windows service. It deletes all records in the ActualPlot table and then refreshes/adds new records from a text file every 30 seconds. I basically need to block (lock?) any user from reading the ActualPlot table while the records are being deleted and refreshed. Can you PLEASE help me change my code to do this?
private void timer1_Tick(object sender, ElapsedEventArgs e)
{
// Open the SAP text files, clear the data in the tables and repopulate the new SAP data into the tables.
var cnnString = ConfigurationManager.ConnectionStrings["TaktBoardsConnectionString"].ConnectionString;
SqlConnection conn = new SqlConnection(cnnString);
SqlConnection conndetail = new SqlConnection(cnnString);
SqlConnection connEdit = new SqlConnection(cnnString);
SqlCommand cmdGetProductFile = new SqlCommand();
SqlDataReader reader;
string sql;
// Delete all the records from the ActualPlot and the ActualPlotPreload tables. We are going to repopulate them with the data from the text file.
sql = "DELETE FROM ActualPlotPreload";
try
{
conn.Open();
SqlCommand cmd = new SqlCommand(sql, conn);
cmd.ExecuteNonQuery();
}
catch (System.Data.SqlClient.SqlException ex)
{
string msg = "Delete Error:";
msg += ex.Message;
Library.WriteErrorLog(msg);
}
finally
{
conn.Close();
}
sql = "DELETE FROM ActualPlot";
try
{
conn.Open();
SqlCommand cmd = new SqlCommand(sql, conn);
cmd.ExecuteNonQuery();
}
catch (System.Data.SqlClient.SqlException ex)
{
string msg = "Delete Error:";
msg += ex.Message;
Library.WriteErrorLog(msg);
}
finally
{
conn.Close();
}
// Read the SAP text file and load the data into the ActualPlotPreload table
sql = "SELECT DISTINCT [BoardName], [ProductFile], [ProductFileIdent] FROM [TaktBoards].[dbo].[TaktBoard] ";
sql = sql + "JOIN [TaktBoards].[dbo].[Product] ON [Product].[ProductID] = [TaktBoard].[ProductID]";
cmdGetProductFile.CommandText = sql;
cmdGetProductFile.CommandType = CommandType.Text;
cmdGetProductFile.Connection = conn;
conn.Open();
reader = cmdGetProductFile.ExecuteReader();
string DBProductFile = "";
string DBTischID = "";
string filepath = "";
string[] cellvalues;
DateTime dt, DateCheckNotMidnightShift;
DateTime ldSAPFileLastMod = DateTime.Now;
string MyDateString;
int FileRecordCount = 1;
while (reader.Read())
{
DBProductFile = (string)reader["ProductFile"];
DBTischID = (string)reader["ProductFileIdent"];
filepath = "c:\\inetpub\\wwwroot\\WebApps\\TaktBoard\\FilesFromSAP\\" + DBProductFile;
FileInfo fileInfo = new FileInfo(filepath); // Open file
ldSAPFileLastMod = fileInfo.LastWriteTime; // Get last time modified
try
{
StreamReader sr = new StreamReader(filepath);
FileRecordCount = 1;
// Populate the AcutalPlotPreload table from with the dates from the SAP text file.
sql = "INSERT into ActualPlotPreload (ActualDate, TischID) values (#ActualDate, #TischID)";
while (!sr.EndOfStream)
{
cellvalues = sr.ReadLine().Split(';');
if (FileRecordCount > 1 & cellvalues[7] != "")
{
MyDateString = cellvalues[7];
DateTime ldDateCheck = DateTime.ParseExact(MyDateString, "M/dd/yyyy", null);
DateTime dateNow = DateTime.Now;
string lsDateString = dateNow.Month + "/" + dateNow.Day.ToString("d2") + "/" + dateNow.Year;
DateTime ldCurrentDate = DateTime.ParseExact(lsDateString, "M/dd/yyyy", null);
string lsTischID = cellvalues[119];
if (ldDateCheck == ldCurrentDate)
{
try
{
conndetail.Open();
SqlCommand cmd = new SqlCommand(sql, conndetail);
cmd.Parameters.Add("#ActualDate", SqlDbType.DateTime);
cmd.Parameters.Add("#TischID", SqlDbType.VarChar);
cmd.Parameters["#TischID"].Value = cellvalues[119];
MyDateString = cellvalues[7] + " " + cellvalues[55];
dt = DateTime.ParseExact(MyDateString, "M/dd/yyyy H:mm:ss", null);
cmd.Parameters["#ActualDate"].Value = dt;
// Ignore any midnight shift (12am to 3/4am) units built.
DateCheckNotMidnightShift = DateTime.ParseExact(cellvalues[7] + " 6:00:00", "M/dd/yyyy H:mm:ss", null);
if (dt >= DateCheckNotMidnightShift)
{
cmd.ExecuteNonQuery();
}
}
catch (System.Data.SqlClient.SqlException ex)
{
string msg = "Insert Error:";
msg += ex.Message;
Library.WriteErrorLog(msg);
}
finally
{
conndetail.Close();
}
}
}
FileRecordCount++;
}
sr.Close();
}
catch
{ }
finally
{ }
}
conn.Close();
// Get the unique TischID's and ActualDate from the ActualPlotPreload table. Then loop through each one, adding the ActualUnits
// AcutalDate and TischID to the ActualPlot table. For each unique TischID we make sure that we reset the liTargetUnits to 1 and
// count up as we insert.
SqlCommand cmdGetTischID = new SqlCommand();
SqlDataReader readerTischID;
int liTargetUnits = 0;
string sqlInsert = "INSERT into ActualPlot (ActualUnits, ActualDate, TischID) values (#ActualUnits, #ActualDate, #TischID)";
sql = "SELECT DISTINCT [ActualDate], [TischID] FROM [TaktBoards].[dbo].[ActualPlotPreload] ORDER BY [TischID], [ActualDate] ASC ";
cmdGetTischID.CommandText = sql;
cmdGetTischID.CommandType = CommandType.Text;
cmdGetTischID.Connection = conn;
conn.Open();
readerTischID = cmdGetTischID.ExecuteReader();
DBTischID = "";
DateTime DBActualDate;
string DBTischIDInitial = "";
while (readerTischID.Read())
{
DBTischID = (string)readerTischID["TischID"];
DBActualDate = (DateTime)readerTischID["ActualDate"];
if (DBTischIDInitial != DBTischID)
{
liTargetUnits = 1;
DBTischIDInitial = DBTischID;
}
else
{
liTargetUnits++;
}
try
{
conndetail.Open();
SqlCommand cmd = new SqlCommand(sqlInsert, conndetail);
cmd.Parameters.Add("#ActualUnits", SqlDbType.Real);
cmd.Parameters.Add("#ActualDate", SqlDbType.DateTime);
cmd.Parameters.Add("#TischID", SqlDbType.VarChar);
cmd.Parameters["#TischID"].Value = DBTischID;
cmd.Parameters["#ActualDate"].Value = DBActualDate;
cmd.Parameters["#ActualUnits"].Value = liTargetUnits;
cmd.ExecuteNonQuery();
cmd.Parameters.Clear();
}
catch (System.Data.SqlClient.SqlException ex)
{
string msg = "Insert Error:";
msg += ex.Message;
Library.WriteErrorLog(msg);
}
finally
{
conndetail.Close();
}
}
conn.Close();
Library.WriteErrorLog("SAP text file data has been imported.");
}
If the data is being re-added right back after the delete (basically you know what to re-add before emptying the table), you could have both operation within the same SQL transaction, so that the data will be available to the other page only when it has been re-added.
I mean something like that :
public bool DeleteAndAddData(string connString)
{
using (OleDbConnection conn = new OleDbConnection(connString))
{
OleDbTransaction tran = null;
try
{
conn.Open();
tran = conn.BeginTransaction();
OleDbCommand deleteComm = new OleDbCommand("DELETE FROM Table", conn);
deleteComm.ExecuteNonQuery();
OleDbCommand reAddComm = new OleDbCommand("INSERT INTO Table VALUES(1, 'blabla', 'etc.'", conn);
reAddComm.ExecuteNonQuery();
tran.Commit();
}
catch (Exception ex)
{
tran.Rollback();
return false;
}
}
return true;
}
If your queries don't take too long to execute, you can start the two with a difference of 7.5 seconds, as there is a collision at every 90 seconds when the read/write finishes 3 cycles, and read/view finishes 2 cycles.
That being said, it's not a fool-proof solution, just a trick based on assumptions, in case you wan't to be completely sure that read/view never happens when read/write cycle is happening, try considering having a Read Lock. I would recommend reading Understanding how SQL Server executes a query and Locking in the Database Engine
Hope that helps.
I would try a couple of things:
Make sure your DELETE + INSERT operation is occurring within a single transaction:
BEGIN TRAN
DELETE FROM ...
INSERT INTO ...
COMMIT
If this isn't a busy table, try locking hints your SELECT statement. For example:
SELECT ...
FROM Table
WITH (UPDLOCK, HOLDLOCK)
In the case where the update transactions starts while your SELECT statement is running, this will cause that transaction to wait until the SELECT is finished. Unfortunately it will block other SELECT statements too, but you don't risk reading dirty data.
I was not able to figure this out but I changed my code so the program was not deleting all the rows in the ActualPlot table but checking to see if the row was there and if not adding the new row from the text file.

Categories