We are using Microsoft Enterprise library to access the sql server database.We are
having some doubts about how to close the DB Connection.Code is given below.
DatabaseFactory.SetDatabaseProviderFactory(new DatabaseProviderFactory(), false);
Database db = new DatabaseProviderFactory().Create("Dataconnectionstring");
string sqlCommand = "";
DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand);
dbCommand.CommandTimeout = 0;
try
{
success = Convert.ToInt32(db.ExecuteScalar(dbCommand));
return success;
}
so how can we close database connection.
One way to do this is use using statements.
All Objects that are implementing IDisposable can be used in such way, when code reaches the closing bracket it calls Dispose() that is supposed to get rid of the object.
DatabaseFactory.SetDatabaseProviderFactory(new DatabaseProviderFactory(), false);
int success = 0;
using (var db = new DatabaseProviderFactory().Create("Dataconnectionstring"))
{
string sqlCommand = "";
using (var dbCommand = db.GetStoredProcCommand(sqlCommand))
{
dbCommand.CommandTimeout = 0;
try
{
success = Convert.ToInt32(db.ExecuteScalar(dbCommand));
}
}
}
return success;
Related
Synopsis
I have a small local SQLite DB with a set records that are used to populate a ComboBoxList. The list is populated, and the DB connection released. No DB locking issues observed.
Then when a record is selected from the list, that record is retrieved and used to populate a LocalDBInstallRecord object. However, after populating the object, the DB connection seems to remain open and prevents any further access.
The Code
public LocalDBInstallRecord GetRecord(string recordID)
{
LocalDBInstallRecord lir = new LocalDBInstallRecord();
string query = "SELECT * FROM InstallationRecords WHERE RecordID = " + recordID;
using (SQLiteConnection localDBConnection = new SQLiteConnection(ConnectionStr))
{
localDBConnection.Open();
using (SQLiteCommand cmd = new SQLiteCommand(query, localDBConnection))
{
using (SQLiteDataReader rdr = cmd.ExecuteReader(CommandBehavior.KeyInfo))
{
while (rdr.Read())
{
lir.RecordID = (uint)rdr.GetInt32(rdr.GetOrdinal("RecordID"));
lir.DateCreated = rdr.GetDateTime(rdr.GetOrdinal("DateCreated"));
lir.Model = CheckDBNull(rdr, "Model");
lir.SCFirmwareVer = CheckDBNull(rdr, "SCFirmwareVer");
lir.DispFirmwareVer = CheckDBNull(rdr, "DispFirmwareVer");
lir.UCCFirmwareVer = CheckDBNull(rdr, "UCCFirmwareVer");
lir.Title = CheckDBNull(rdr, "Title");
lir.FOC = rdr.GetBoolean(rdr.GetOrdinal("FOC"));
lir.MCManufacturer = CheckDBNull(rdr, "MCManufacturer");
lir.MCType = CheckDBNull(rdr, "MCType");
lir.RailConvertor = CheckDBNull(rdr, "RailConvertor");
lir.PlantID = CheckDBNull(rdr, "PlantID");
lir.PlantOwner = CheckDBNull(rdr, "PlantOwner");
lir.PlantOwnerID = CheckDBNull(rdr, "PlantOwnerID");
lir.BoomLength = rdr.GetFloat(rdr.GetOrdinal("BoomLength"));
lir.ArticLength = rdr.GetFloat(rdr.GetOrdinal("ArticLength"));
lir.DipperLength = rdr.GetFloat(rdr.GetOrdinal("DipperLength"));
lir.MachineRecord = ReadDBBlob(rdr, "MachineRecord");
lir.DutyRecord = ReadDBBlob(rdr, "DutyRecord");
lir.CalibRecord = ReadDBBlob(rdr, "CalibRecord");
}
}
}
}
return lir;
}
Called Functions...
CheckDBNull
private static string CheckDBNull(SQLiteDataReader rdr, string col)
{
string str = null;
if (!rdr.IsDBNull(rdr.GetOrdinal(col)))
str = rdr.GetString(rdr.GetOrdinal(col));
return str;
}
ReadDBBlob
private static byte[] ReadDBBlob(SQLiteDataReader rdr, string col)
{
byte[] data = null;
if (!rdr.IsDBNull(rdr.GetOrdinal(col)))
{
SQLiteBlob blob = rdr.GetBlob(rdr.GetOrdinal(col), true);
data = new byte[blob.GetCount()];
blob.Read(data, blob.GetCount(), 0);
}
return data;
}
My Thoughts
I'm sure there's easier ways to load the object, but that's not the issue...
I think the DB connection is being held open because the SQLiteDataReader can't be disposed of cleanly. I suspect that something in this function (in the LocalDBInstallRecord object) has got hold of an object by reference. I just can't see what.
I've tried not accessing the DateTime object in the record, but that's not it.
Should I be using a different method to access the DB in this case?
Any suggestions greatly appreciated, I think it needs a fresh set of eyes. Thanks.
Further Research...
I've found that if I try and us the SQLite DB with another tool, whilst the GetRecord() function has hold of the reader, I'm able to read the DB, but can't write. I get a database locked (RELEASE 'RESTOREPOINT';) error message from "DB Browser SQLite" application. But I think that's just because it's waiting for the reader to release.
The problem came in the function ReadDBBlob. The SQLiteBlob is IDisposable, and it's use wasn't encapsulated in a using clause. As a result the SQLiteBlob wasn't being diposed of, and this held the reader and hence kept the connection open...
New ReadDBBlob
private static byte[] ReadDBBlob(SQLiteDataReader rdr, string col)
{
byte[] data = null;
if (!rdr.IsDBNull(rdr.GetOrdinal(col)))
{
using (SQLiteBlob blob = rdr.GetBlob(rdr.GetOrdinal(col), true))
{
data = new byte[blob.GetCount()];
blob.Read(data, blob.GetCount(), 0);
}
}
return data;
}
I hope this saves someone some time.
I am trying to capture the entity framework queries via SQL Server trace using C# that are getting executed during my automation test run. When I run the below code ,I am getting the exception
Issue
The queries that are getting executed in the background are not getting captured. Any other thing that I can try to get the select /insert/ update queries that are running in the background
Code
class Program
{
static void Main(string[] args)
{
using (SqlConnection conn = new SqlConnection(#"Data Source = XXXXXX\XXXXX; Initial Catalog = Test; Integrated Security = SSPI"))
{
XEStore store = new XEStore(new SqlStoreConnection(conn));
string sessionName = "abc";
if (store.Sessions[sessionName] != null)
{
Console.WriteLine("dropping existing session");
store.Sessions[sessionName].Drop();
}
Session s = store.CreateSession(sessionName);
s.MaxMemory = 4096;
s.MaxDispatchLatency = 30;
s.EventRetentionMode = Session.EventRetentionModeEnum.AllowMultipleEventLoss;
Event rpc = s.AddEvent("rpc_completed");
rpc.AddAction("username");
rpc.AddAction("database_name");
rpc.AddAction("sql_text");
rpc.PredicateExpression = #"sqlserver.username NOT LIKE '%testuser'";
s.Create();
s.Start();
int i = 0;
while (i < 15000)
{
s.Refresh();
foreach (var prop in rpc.Actions)
{
Console.WriteLine(prop.Description);
Console.WriteLine(prop.KeyChain);
Console.WriteLine(prop.IdentityKey);
Console.WriteLine(prop.Metadata);
Console.WriteLine(prop.ModuleID);
Console.WriteLine(prop.Name);
Console.WriteLine(prop.PackageName);
Console.WriteLine(prop.Parent);
Console.WriteLine(prop.Properties);
Console.WriteLine(prop.State);
Console.WriteLine(prop.Urn);
}
Thread.Sleep(1000);
}
}
}
}
So this is a little bit code-ceptionlike.
I have a function that is checking the last ID in a table, this function is called within another function. At the end of that function, I have another function that's opening another datareader.
Error:
There is already an open Datareader associated with this connection which must be closed first.
getLastIdfromDB()
public string getLastIdFromDB()
{
int lastIndex;
string lastID ="";
var dbCon = DB_connect.Instance();
if (dbCon.IsConnect())
{
MySqlCommand cmd2 = new MySqlCommand("SELECT ID FROM `competitor`", dbCon.Connection);
try
{
MySqlDataReader reader = cmd2.ExecuteReader();
while (reader.Read())
{
string item = reader2["ID"].ToString();
lastIndex = int.Parse(item);
lastIndex++;
lastID = lastIndex.ToString();
}
}
catch (Exception ex)
{
MessageBox.Show("Error:" + ex.Message);
}
}
return lastID;
}
This function is later-on used in this function:
private void addPlayerBtn_Click(object sender, EventArgs e)
{
ListViewItem lvi = new ListViewItem(getLastIdFromDB());
.........................................^
... HERE
...
... irrelevant code removed
.........................................
var dbCon = DB_connect.Instance();
if (dbCon.IsConnect())
{
MySqlCommand cmd = new MySqlCommand("INSERT INTO `competitor`(`ID`, `Name`, `Age`) VALUES(#idSql,#NameSql,#AgeSql)", dbCon.Connection);
cmd.Parameters.AddWithValue("#idSql", getLastIdFromDB());
cmd.Parameters.AddWithValue("#NameSql", playerName.Text);
cmd.Parameters.AddWithValue("#AgeSql", playerAge.Text);
try
{
cmd.ExecuteNonQuery();
listView1.Items.Clear();
}
catch (Exception ex)
{
MessageBox.Show("Error:" + ex.Message);
dbCon.Connection.Close();
}
finally
{
updateListView();
}
}
}
What would be the best way for me to solve this problem and in the future be sure to close my connections properly?
UPDATE: (per request, included DB_connect)
class DB_connect
{
private DB_connect()
{
}
private string databaseName = "simhopp";
public string DatabaseName
{
get { return databaseName; }
set { databaseName = value; }
}
public string Password { get; set; }
private MySqlConnection connection = null;
public MySqlConnection Connection
{
get { return connection; }
}
private static DB_connect _instance = null;
public static DB_connect Instance()
{
if (_instance == null)
_instance = new DB_connect();
return _instance;
}
public bool IsConnect()
{
bool result = true;
try
{
if (Connection == null)
{
if (String.IsNullOrEmpty(databaseName))
result = false;
string connstring = string.Format("Server=localhost; database={0}; UID=root;", databaseName);
connection = new MySqlConnection(connstring);
connection.Open();
result = true;
}
}
catch (Exception ex)
{
Console.Write("Error: " + ex.Message);
}
return result;
}
public void Close()
{
connection.Close();
}
}
}
You are trying to have multiple open readers on the same connection. This is commonly called "MARS" (multiple active result sets). MySql seems to have no support for it.
You will have to either limit yourself to one open reader at a time, or use more than one connection, so you can have one connection for each reader.
My suggestion would be to throw away that singleton-like thingy and instead use connection pooling and proper using blocks.
As suggested by Pikoh in the comments, using the using clause indeed solved it for me.
Working code-snippet:
getLastIdFromDB
using (MySqlDataReader reader2 = cmd2.ExecuteReader()) {
while (reader2.Read())
{
string item = reader2["ID"].ToString();
lastIndex = int.Parse(item);
lastIndex++;
lastID = lastIndex.ToString();
}
}
Your connection handling here is not good. You need to ditch the DB_connect. No need to maintain a single connection - just open and close the connection each time you need it. Under the covers, ADO.NET will "pool" the connection for you, so that you don't actually have to wait to reconnect.
For any object that implements IDisposable you need to either call .Dispose() on it in a finally block, or wrap it in a using statement. That ensures your resources are properly disposed of. I recommend the using statement, because it helps keep the scope clear.
Your naming conventions should conform to C# standards. Methods that return a boolean should be like IsConnected, not IsConnect. addPlayerBtn_Click should be AddPlayerButton_Click. getLastIdFromDB should be GetlastIdFromDb or getLastIdFromDatabase.
public string GetLastIdFromDatabase()
{
int lastIndex;
string lastID ="";
using (var connection = new MySqlConnection(Configuration.ConnectionString))
using (var command = new MySqlCommand("query", connection))
{
connection.Open();
MySqlDataReader reader = cmd2.ExecuteReader();
while (reader.Read())
{
string item = reader2["ID"].ToString();
lastIndex = int.Parse(item);
lastIndex++;
lastID = lastIndex.ToString();
}
}
return lastID;
}
Note, your query is bad too. I suspect you're using a string data type instead of a number, even though your ID's are number based. You should switch your column to a number data type, then select the max() number. Or use an autoincrementing column or sequence to get the next ID. Reading every single row to determine the next ID and incrementing a counter not good.
I need to create an application for monitoring SQL Server 2000 Agent Job status and info when Job occur same as show on Windows application event log. Now I connect to the database already via a connection string, but I don't know how to get the status and info from Job.
I need to show status and info on Textbox.
What do you suggestion how to do.
Developer tools :
MS SQL Sever 2000 SP4
MS Visual Studio 2008 (C#)
I am a rookie programmer.
i can do this already...
i select form table "Sysjobserver" in database "msdb" for read status, date, time of job that i want.
use this code
public void GetJobsAndStatus()
{
string sqlJobQuery = "select j.job_id, j.name, j.enabled, jh.run_status," +
" js.last_outcome_message, jh.run_date, jh.step_name, jh.run_time" +
" from sysjobs j left join sysjobhistory jh on (j.job_id = jh.job_id)" +
" left join sysjobservers js on (j.job_id = js.job_id)" +
" where jh.run_date = (select Max(run_date) from sysjobhistory)" +
" and jh.run_time = (select Max(run_time) from sysjobhistory)";
// create SQL connection and set up SQL Command for query
using (SqlConnection _con = new SqlConnection("server=10.15.13.70;database=msdb;user id=sa;pwd="))
using (SqlCommand _cmd = new SqlCommand(sqlJobQuery, _con))
{
try
{
// open connection
_con.Open();
SqlConnection.ClearPool(_con);
// create SQL Data Reader and grab data
using (SqlDataReader rdr = _cmd.ExecuteReader())
{
// as long as we get information from the reader
while (rdr.Read())
{
Guid jobID = rdr.GetGuid(0); // read Job_id
string jobName = rdr.GetString(1); // read Job name
byte jobEnabled = rdr.GetByte(2); // read Job enabled flag
int jobStatus = rdr.GetInt32(3); // read last_run_outcome from sysjobserver
string jobMessage = rdr.GetString(4); // read Message from sysjobserver
int jobRunDate = rdr.GetInt32(5); // read run_date from sysjobhistory
string jobStepName = rdr.GetString(6); // read StepName from sysjobhistory
int jobRunTime = rdr.GetInt32(7); // read run_time from sysjobhistory
String[] lviData = new String[] // ตัวแปรอะเรย์ชื่อ lviData
{
jobID.ToString(),
jobName.ToString(),
jobStepName.ToString(),
jobMessage.ToString(),
jobStatus.ToString(),
jobRunDate.ToString(),
jobRunTime.ToString(),
//jobEnabled.ToString(),
};
newData = lviData;
DisplayList(); // for display data on datagridview
}
rdr.Close();
}
}
thank you for everybody help very much. :-D
SQL stored procedures of queries don't give you any system data unless you have db_owner rights on the msdb system database, at lease in SQL Server 2008. Therefore mentioned methods normally don't work for applications where you want to show or manage jobs. However SMO namespace provides you with managed code solution for many SQL Server management features, including the SQL Server Agent functions that only require SQLServerAgent* permissions that you normally could get sorted for your application user. A good intro of using SMO classes to work with jobs is given here:
http://www.codeproject.com/Tips/367470/Manage-SQL-Server-Agent-Jobs-using-Csharp
I work on a similar task now and whilst SQL queries give me access denied, with C# code and Microsoft.SqlServer.Management.Smo.Agent namespace I just listed all jobs with this code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo.Agent;
namespace SmoTest
{
class Program
{
static readonly string SqlServer = #"SQL01\SQL01";
static void Main(string[] args)
{
ServerConnection conn = new ServerConnection(SqlServer);
Server server = new Server(conn);
JobCollection jobs = server.JobServer.Jobs;
foreach (Job job in jobs)
{
Console.WriteLine(job.Name);
}
}
}
}
This should be a good starting point to find out how to find your SQL Agent jobs using T-SQL:
View (and disable) SQL Agent Jobs with TSQL
The script will list out all your jobs on your database, and when they will be run next and so forth.
Using the job_name, you should also be able to find out details about your jobs using the SQL Server Agent Stored Procedures in the msdb database on your server.
On SQL Server 2005 and above, you can use the system stored procedure msdb.dbo.sp_help_job to get information, including status, about SQL Server Agent Jobs. You can read more about sp_help_job at http://msdn.microsoft.com/en-us/library/ms186722(v=SQL.90).aspx.
Here is the sample code to do this from C#.
private Dictionary<int, string> ExecutionStatusDictionary = new Dictionary<int, string>()
{
{0, "Not idle or suspended"},
{1, "Executing"},
{2, "Waiting for thread"},
{3, "Between retries"},
{4, "Idle"},
{5, "Suspended"},
{7, "Performing completion actions"}
};
public string GetStatus()
{
SqlConnection msdbConnection = new SqlConnection("Data Source=SERVERNAME;Initial Catalog=msdb;Integrated Security=SSPI");
System.Text.StringBuilder resultBuilder = new System.Text.StringBuilder();
try
{
msdbConnection.Open();
SqlCommand jobStatusCommand = msdbConnection.CreateCommand();
jobStatusCommand.CommandType = CommandType.StoredProcedure;
jobStatusCommand.CommandText = "sp_help_job";
SqlParameter jobName = jobStatusCommand.Parameters.Add("#job_name", SqlDbType.VarChar);
jobName.Direction = ParameterDirection.Input;
jobName.Value = "LoadRegions";
SqlParameter jobAspect = jobStatusCommand.Parameters.Add("#job_aspect", SqlDbType.VarChar);
jobAspect.Direction = ParameterDirection.Input;
jobAspect.Value = "JOB";
SqlDataReader jobStatusReader = jobStatusCommand.ExecuteReader();
while (jobStatusReader.Read())
{
resultBuilder.Append(string.Format("{0} {1}",
jobStatusReader["name"].ToString(),
ExecutionStatusDictionary[(int)jobStatusReader["current_execution_status"]]
));
}
jobStatusReader.Close();
}
finally
{
msdbConnection.Close();
}
return resultBuilder.ToString();
}
You can get a list of all server jobs using this SELECT:
SELECT [name] FROM msdb.dbo.sysjobs
If you'd like to get a list of currently running jobs and their information, I would recommend writing a stored procedure in SQL which your application calls. There's a good demonstration here you could use...
http://feodorgeorgiev.com/blog/2010/03/how-to-query-currently-running-sql-server-agent-jobs/
Good luck!
For my use case, I specifically needed to know when the job was finished running, and whether or not it succeeded. Here is my code to do that:
using System;
using System.Data;
using System.Data.SqlClient;
namespace LaunchJobAndWaitTillDone
{
class Program
{
const string connectionString = "Data Source=YOURSERVERNAMEHERE;Initial Catalog=msdb;Integrated Security=SSPI";
const string jobName = "YOURJOBNAMEHERE";
static readonly TimeSpan waitFor = TimeSpan.FromSeconds(1.0);
enum JobExecutionResult
{
Succeeded,
FailedToStart,
FailedAfterStart,
Unknown
}
static void Main(string[] args)
{
var instance = new Program();
JobExecutionResult jobResult = instance.RunJob(jobName);
switch (jobResult)
{
case JobExecutionResult.Succeeded:
Console.WriteLine($"SQL Server Agent job, '{jobName}', ran successfully to completion.");
break;
case JobExecutionResult.FailedToStart:
Console.WriteLine($"SQL Server Agent job, '{jobName}', failed to start.");
break;
case JobExecutionResult.FailedAfterStart:
Console.WriteLine($"SQL Server Agent job, '{jobName}', started successfully, but encountered an error.");
break;
default:
Console.WriteLine($"Unknown result from attempting to run SQL Server Agent job, '{jobName}'.");
break;
}
Console.ReadLine();
return;
}
JobExecutionResult RunJob(string jobName)
{
int jobResult;
using (var jobConnection = new SqlConnection(connectionString))
{
SqlCommand jobCommand;
SqlParameter jobReturnValue;
SqlParameter jobParameter;
jobCommand = new SqlCommand("sp_start_job", jobConnection);
jobCommand.CommandType = CommandType.StoredProcedure;
jobReturnValue = new SqlParameter("#RETURN_VALUE", SqlDbType.Int);
jobReturnValue.Direction = ParameterDirection.ReturnValue;
jobCommand.Parameters.Add(jobReturnValue);
jobParameter = new SqlParameter("#job_name", SqlDbType.VarChar);
jobParameter.Direction = ParameterDirection.Input;
jobCommand.Parameters.Add(jobParameter);
jobParameter.Value = jobName;
jobConnection.Open();
try
{
jobCommand.ExecuteNonQuery();
jobResult = (Int32)jobCommand.Parameters["#RETURN_VALUE"].Value;
}
catch (SqlException)
{
jobResult = -1;
}
}
switch (jobResult)
{
case 0:
break;
default:
return JobExecutionResult.FailedToStart;
}
while (true)
{
using (var jobConnection2 = new SqlConnection(connectionString))
{
SqlCommand jobCommand2 = new SqlCommand("sp_help_jobactivity", jobConnection2);
jobCommand2.CommandType = CommandType.StoredProcedure;
SqlParameter jobReturnValue2 = new SqlParameter("#RETURN_VALUE", SqlDbType.Int);
jobReturnValue2.Direction = ParameterDirection.ReturnValue;
jobCommand2.Parameters.Add(jobReturnValue2);
SqlParameter jobParameter2 = new SqlParameter("#job_name", SqlDbType.VarChar);
jobParameter2.Direction = ParameterDirection.Input;
jobCommand2.Parameters.Add(jobParameter2);
jobParameter2.Value = jobName;
jobConnection2.Open();
SqlDataReader rdr = jobCommand2.ExecuteReader();
while (rdr.Read())
{
object msg = rdr["message"];
object run_status = rdr["run_status"];
if (!DBNull.Value.Equals(msg))
{
var message = msg as string;
var runStatus = run_status as Int32?;
if (message != null && message.StartsWith("The job succeeded")
&& runStatus.HasValue && runStatus.Value == 1)
{
return JobExecutionResult.Succeeded;
}
else if (message != null && message.StartsWith("The job failed"))
{
return JobExecutionResult.FailedAfterStart;
}
else if (runStatus.HasValue && runStatus.Value == 1)
{
return JobExecutionResult.Unknown;
}
}
}
}
System.Threading.Thread.Sleep(waitFor);
}
}
}
}
Note that you may need database/server owner permissions or something like that for this code to work.
I created a method while back that:
Locked a table
Read value from it
Wrote updated value back
Unlocked the table
The code worked for Oracle. Now I can't get it work for SQL Server 2008. The method is below and executing my unlocking command results in a SqlException with text:
"NOLOC" is not a recognized table hints option. If it is intended as a
parameter to a table-valued function or to the CHANGETABLE function,
ensure that your database compatibility mode is set to 90.
Code:
public static int GetAndSetMaxIdTable(DbProviderFactory factory, DbConnection cnctn, DbTransaction txn, int tableId, string userName, int numberOfIds)
{
bool isLocked = false;
string sql = string.Empty;
string maxIdTableName;
if (tableId == 0)
maxIdTableName = "IdMax";
else
maxIdTableName = "IdMaxTable";
try
{
bool noPrevRow = false;
int realMaxId;
if (factory is OracleClientFactory)
sql = string.Format("lock table {0} in exclusive mode", maxIdTableName);
else if (factory is SqlClientFactory)
sql = string.Format("select * from {0} with (TABLOCKX)", maxIdTableName);
else
throw new Exception(string.Format("Unsupported DbProviderFactory -type: {0}", factory.GetType().ToString()));
using (DbCommand lockCmd = cnctn.CreateCommand())
{
lockCmd.CommandText = sql;
lockCmd.Transaction = txn;
lockCmd.ExecuteNonQuery();
isLocked = true;
}
using (DbCommand getCmd = cnctn.CreateCommand())
{
getCmd.CommandText = CreateSelectCommand(factory, tableId, userName, getCmd, txn);
object o = getCmd.ExecuteScalar();
if (o == null)
{
noPrevRow = true;
realMaxId = 0;
}
else
{
realMaxId = Convert.ToInt32(o);
}
}
using (DbCommand setCmd = cnctn.CreateCommand())
{
if (noPrevRow)
setCmd.CommandText = CreateInsertCommand(factory, tableId, userName, numberOfIds, realMaxId, setCmd, txn);
else
setCmd.CommandText = CreateUpdateCommand(factory, tableId, userName, numberOfIds, realMaxId, setCmd, txn);
setCmd.ExecuteNonQuery();
}
if (factory is OracleClientFactory)
sql = string.Format("lock table {0} in share mode", maxIdTableName);
else if (factory is SqlClientFactory)
sql = string.Format("select * from {0} with (NOLOC)", maxIdTableName);
using (DbCommand lockCmd = cnctn.CreateCommand())
{
lockCmd.CommandText = sql;
lockCmd.Transaction = txn;
lockCmd.ExecuteNonQuery();
isLocked = false;
}
return realMaxId;
}
catch (Exception e)
{
...
}
}
So what goes wrong here? Where does this error come from? Server or client? I copied the statement from C code and it's supposed to work there. Unfortunately I can't debug and check if it works for me.
Edit: Just trying to lock and unlock (without reading or updating) results in same exception.
Thanks & BR -Matti
The TABLOCKX hint locks the table as you intend, but you can't unlock it manually. How long the lock stays on depends on your transaction level. If you don't have an active transaction on your connection, the lock is held while the SELECT executes and is discarded thereafter.
If you want to realize the sequence "lock the table -> do something with the table -> release the lock" you would need to implement the ADO.NET equivalent of this T-SQL script:
BEGIN TRAN
SELECT TOP (1) 1 FROM myTable (TABLOCKX, KEEPLOCK)
-- do something with the table
COMMIT -- This will release the lock, if there is no outer transaction present
you can either execute the "BEGIN TRAN"/"COMMIT" through DbCommand objects or you can use the System.Data.SqlClient.SqlTransaction class to start a transaction and commit it.
Attention: This approach only works if your connection is not enlisted in a transaction already! SQL Server doesn't support nested transaction, so the COMMIT wouldn't do anything and the lock would be held. If you have a transaction already running, you cannot release the lock until the transaction finishes. In this case maybe a synchronisation through sp_getapplock/sp_releaseapplock might help.
Edit: If you want to educate yourself about transactions, locking and blocking, I recommend these two videos: http://technet.microsoft.com/en-us/sqlserver/gg545007.aspx and http://technet.microsoft.com/en-us/sqlserver/gg508892.aspx
Here is answer for one table for SqlClient with code I made based on TToni's answer:
public static int GetAndSetMaxIdTable(DbProviderFactory factory, DbConnection cnctn, DbTransaction txn, int numberOfIds)
{
bool noPrevRow = false;
int realMaxId;
using (DbCommand getCmd = cnctn.CreateCommand())
{
getCmd.CommandText = "SELECT MaxId FROM IdMax WITH (TABLOCKX)"
getCmd.Transaction = txn;
object o = getCmd.ExecuteScalar();
if (o == null)
{
noPrevRow = true;
realMaxId = 0;
}
else
{
realMaxId = Convert.ToInt32(o);
}
}
using (DbCommand setCmd = cnctn.CreateCommand())
{
if (noPrevRow)
setCmd.CommandText = CreateInsertCommand(factory, tableId, userName, numberOfIds, realMaxId, setCmd, txn);
else
setCmd.CommandText = CreateUpdateCommand(factory, tableId, userName, numberOfIds, realMaxId, setCmd, txn);
setCmd.ExecuteNonQuery();
}
return realMaxId;
}
and it i's like this:
...
try
{
using (txn = cnctn.BeginTransaction())
{
oldMaxId = GetAndSetMaxIdTable(factory, cnctn, txn, 5);
for (i = 0; i < 5; i++)
{
UseNewIdToInsertStuff(factory, cnctn, txn, oldMaxId + i + 1)
}
txn.Commit();
return true;
}
}
catch (Exception e)
{
// don't know if this is needed
if (txn != null && cnctn.State == ConnectionState.Open)
txn.Rollback();
throw e;
}
...
For oracle client it seems to be desirable to have:
SELECT MaxId from IdMax WHERE ... FOR UPDATE OF MaxId
-m