I use SQL Server and I have 3 Application servers. When a table in my database have changed I need to those application servers refresh there local cached data. I use a trigger to known change and send a message via Service broker queue. Then I create a stored procedure and assign it to activate stored procedure of my queue, In this stored procedure I receive message, but I don't know How should I call refresh method in my application.
I had similar issue but this code resolved the issue :
public class QueryNotification
{
public DataSet DataToWatch { get; set; }
public SqlConnection Connection { get; set; }
public SqlCommand Command { get; set; }
public string GetSQL()
{
return "SELECT * From YourTable";
}
public string GetConnection()
{
return ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
}
public bool CanRequestNotifications()
{
try
{
var perm = new SqlClientPermission(PermissionState.Unrestricted);
perm.Demand();
return true;
}
catch
{
return false;
}
}
public void GetData()
{
DataToWatch.Clear();
Command.Notification = null;
var dependency = new SqlDependency(Command);
dependency.OnChange += dependency_OnChange;
using (var adapter = new SqlDataAdapter(Command))
{
adapter.Fill(DataToWatch, "YourTableName");
}
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
var i = (ISynchronizeInvoke)sender;
if (i.InvokeRequired)
{
var tempDelegate = new OnChangeEventHandler(dependency_OnChange);
object[] args = { sender, e };
i.BeginInvoke(tempDelegate, args);
return;
}
var dependency = (SqlDependency)sender;
dependency.OnChange -= dependency_OnChange;
GetData();
}
}
Update:
Check for permission:
public bool CanRequestNotifications()
{
try
{
var perm = new SqlClientPermission(PermissionState.Unrestricted);
perm.Demand();
return true;
}
catch
{
return false;
}
}
For Instance in your window load:
if (!_queryNotification.CanRequestNotifications())
{
MessageBox.Show("ERROR:Cannot Connect To Database");
}
SqlDependency.Stop(_queryNotification.GetConnection());
SqlDependency.Start(_queryNotification.GetConnection());
if (_queryNotification.Connection == null)
{
_queryNotification.Connection = new SqlConnection(_queryNotification.GetConnection());
}
if (_queryNotification.Command == null)
{
_queryNotification.Command = new SqlCommand(_queryNotification.GetSQL(),
_queryNotification.Connection);
}
if (_queryNotification.DataToWatch == null)
{
_queryNotification.DataToWatch = new DataSet();
}
GetData();
You should look at using the SqlDependency class.
More information at: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldependency(v=vs.110).aspx
I can suggest you to try solving the problem using TCP. Each app listens to a port and when another app updates the db it sends a message to the other apps saying they need to refresh.
Hope that was a good idea.
Related
System.InvalidOperationException: 'Timeout expired. The timeout
period elapsed prior to obtaining a connection from the pool. This
may have occurred because all pooled connections were in use and max
pool size was reached.'
Im trying to add existing username to a collection of errors and i started receiving this exception...
Im getting exception in this class:
public class UsersCollection: ObservableCollection<Users>
{
public static UsersCollection GetAllUserNames()
{
UsersCollection users = new UsersCollection();
Users user = null;
using (SqlConnection conn = new SqlConnection())
{
conn.ConnectionString = ConfigurationManager.ConnectionStrings["ConnString"].ConnectionString;
conn.Open(); //I GET EXCEPTION ON THIS LINE.
SqlCommand command = new SqlCommand("SELECT UserName FROM Users", conn);
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
user = Users.GetUserNamesFromResultSet(reader);
users.Add(user);
}
}
}
return users;
}
}
Class with code that causes the exception:
public class Users : INotifyPropertyChanged, INotifyDataErrorInfo
{
private int _id;
private string _username;
private string _password;
private bool _isAdmin;
private Dictionary<string, List<string>> errors = new Dictionary<string, List<string>>();
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
public string UserName
{
get { return _username; }
set
{
if (_username == value)
{
return;
}
_username = value;
List<string> errors = new List<string>();
bool valid = true;
if (value == null || UserName.Length < 5)
{
errors.Add("Username can not have less than 5 characters.");
SetErrors("UserName", errors);
valid = false;
}
UsersCollection collection = new UsersCollection();
collection = UsersCollection.GetAllUserNames();
for (int i = 0; i < collection.Count; i++)
{
if (UserName == (object)collection[i])
{
errors.Add("Username already exist.");
SetErrors("UserName", errors);
valid = false;
}
}
if (!Regex.Match(value, #"^\w+$").Success)
{
errors.Add("Username can only contain letters, numbers and underscore characters.");
SetErrors("UserName", errors);
valid = false;
}
if (valid)
{
ClearErrors("UserName");
}
OnPropertyChanged(new PropertyChangedEventArgs("UserName"));
}
private void SetErrors(string propertyName, List<string> propertyErrors)
{
errors.Remove(propertyName);
errors.Add(propertyName, propertyErrors);
if(ErrorsChanged != null)
{
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
private void ClearErrors(string propertyName)
{
errors.Remove(propertyName);
if (ErrorsChanged != null)
{
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
public static Users GetUserNamesFromResultSet(SqlDataReader reader)
{
Users user = new Users((string)reader["UserName"]);
return user;
}
I started receiving exception after i added these lines (I'm not shure if code would do as i wanted, i couldn't test it):
UsersCollection collection = new UsersCollection();
collection = UsersCollection.GetAllUserNames();
for (int i = 0; i < collection.Count; i++)
{
if (UserName == (object)collection[i])
{
errors.Add("Username already exist.");
SetErrors("UserName", errors);
valid = false;
}
}
How can i fix this?
what i understood from your question is that you are trying to connect to a pool and from his pool your trying to select a data from users table but sadly you ran into a problem with the pool because its reached max size or limit mmm if you are correct with the connection string try to make cmd.Timeout = 0; there will try reconnect again and again and again until you able to get into the pool.
I'm getting the following error on my C# Web API: "Exception thrown: 'System.Threading.ThreadAbortException' in System.Data.dll
Thread was being aborted". I have a long running process on one thread using my data access logic class to get and update records being process. Meanwhile a user submits another group to process which has need of the same data access logic class, thus resulting in the error. Here is a rough sketch of what I'm doing.
WebAPI Class:
public IHttpActionResult OkToProcess(string groupNameToProcess)
{
var logic = GetLogic();
//Gets All Unprocessed Records and Adds them to Blocking Queue
Task.Factory.StartNew(() => dataAccessLogic.LoadAndProcess(groupNameToProcess);
}
public IHttpActionResult AddToProcess(int recordIdToProcess)
{
StaticProcessingFactory.AddToQueue(recordIdToProcess);
}
StaticProcessingFactory
internal static ConcurrentDictionary<ApplicationEnvironment, Logic> correctors = new ConcurrentDictionary<ApplicationEnvironment, Logic>();
internal static BlockingCollection<CorrectionMessage> MessageQueue = new BlockingCollection<Message>(2000);
public void StartService(){
Task.Factory.StartNew(() => LoadService());
}
public void LoadService(){
var logic = GetLogic();
if(isFirstGroupOkToProcessAsPerTextFileLog())
logic.LoadAndProcess("FirstGroup");
if(isSeconddGroupOkToProcessAsPerTextFileLog())
logic.LoadAndProcess("SecondGroup");
}
public static GetLogic(){
var sqlConnectionFactory = Tools.GetSqlConnectionFactory();
string environment = ConfigurationManager.AppSettings["DefaultApplicationEnvironment"];
ApplicationEnvironment applicationEnvironment =
ApplicationEnvironmentExtensions.ToApplicationEnvironment(environment);
return correctors.GetOrAdd(applicationEnvironment, new Logic(sqlConnectionFactory ));
}
public static void AddToQueue(Message message, bool completeAdding = true)
{
if (MessageQueue.IsAddingCompleted)
MessageQueue = new BlockingCollection<Message>();
if (completeAdding && message.ProcessImmediately)
StartQueue(message);
else
MessageQueue.Add(message);
}
public static void StartQueue(Message message = null)
{
if (message != null)
{
if(!string.IsNullOrEmpty(message.ID))
MessageQueue.Add(message);
Logic logic = GetLogic(message.Environment);
try
{
var messages = MessageQueue.TakeWhile(x => logic.IsPartOfGroup(x.GroupName, message.GroupName));
if (messages.Count() > 0)
MessageQueue.CompleteAdding();
int i = 0;
foreach (var msg in messages)
{
i++;
Process(msg);
}
}
catch (InvalidOperationException) { MessageQueue.CompleteAdding(); }
}
}
public static void Process(Message message)
{
Var logic = GetLogic(message.Environment);
var record = logic.GetRecord(message.ID);
record.Status = Status.Processed;
logic.Save(record);
}
Logic Class
private readonly DataAccess DataAccess;
public Logic(SqlConnectionFactory factory)
{
DataAccess = new DataAcess(factory);
}
public void LoadAndProcess(string groupName)
{
var groups = DataAccess.GetGroups();
var records = DataAccess.GetRecordsReadyToProcess(groups);
for(int i = 0; i < records.Count; i++)
{
Message message = new Message();
message.Enviornment = environment.ToString();
message.ID = records[i].ID;
message.User = user;
message.Group = groupName;
message.ProcessImmediately = true;
StaticProcessingFactory.AddToQueue(message, i + 1 == records.Count);
}
}
Any ideas how I might ensure that all traffic from all threads have access to the Data Access Logic without threads being systematically aborted?
I have a problem that I just can't solve on my own. I am new to programming and I would appreciate if you could help me with this issue:
I have a class I would like to inherit from:
namespace rsDeployer.Common.SQLServerCommunication
{
public class RSDatabaseConnectionCreator: LoggerBase
{
public RSProfile profile;
public RSDatabaseConnectionCreator(RSProfile profile)
{
this.profile = profile;
}
public SqlConnection CreateConnection(RSDatabaseNames DatabaseName, bool UseWindowsAuthentication, bool testConnection = false)
{
var connectionString = BuildRSDatabaseConnectionString(DatabaseName, UseWindowsAuthentication);
if (testConnection)
{
return IsConnectionAvailable(connectionString) ? new SqlConnection(connectionString) : null;
}
return new SqlConnection(connectionString);
}
}
}
and I would like to call CreateConnection() in another class to inject to methods to allow me to open connection and then execute scripts.
Edit 1 - class I would like to have it injected to.
public void QueryExecution(string SQLQuery)
{
//here's where I would like to inject it
SqlCommand command = new SqlCommand(SQLQuery, conn);
var file = new StreamWriter(#"D:\Project\rsDeployer\ImportedQuery.txt");
file.WriteLine(command);
file.Close();
}
If this question is way to silly to deserve answer would you just point in the direction where I should read about it?
I hope this question is well asked and clear.
Thanks in advance.
Like this,
public void QueryExecution(string SQLQuery)
{
RSProfile profile = new RSProfile();
RSDatabaseConnectionCreator instance = new RSDatabaseConnectionCreator(profile);
SqlConnection conn = instance.CreateConnection(...);
SqlCommand command = new SqlCommand(SQLQuery, conn);
var file = new StreamWriter(#"D:\Project\rsDeployer\ImportedQuery.txt");
file.WriteLine(command);
file.Close();
conn.Close();
}
You also told that you want to inherit from this class, here is another approach,
public class RSDatabaseConnectionCreator : LoggerBase
{
public virtual object CreateConnection() // by virtual you can override it.
{
return new object();
}
}
public class AnotherClass : RSDatabaseConnectionCreator {
public AnotherClass() {
CreateConnection(); // by inheriting RSDatabaseConnectionCreator , you can reach public functions.
}
public override object CreateConnection() // or you can override it
{
// here might be some user Login check
return base.CreateConnection(); // then you open connection
}
}
Hope helps,
Hope this is what you are requesting for
public class ClassX
{
private RSProfile _rsprofile;
RSDatabaseConnectionCreator _dbConnectionCreator;
private SqlConnection _sqlConnection;
public ClassX()
{
_rsProfile = xxx; // Get the RSProfile object
_dbConnectionCreator = new RSDatabaseConnectionCreator (_rsProfile);
RSDatabaseNames databaseName = yyy; // get the RSDatabaseNames
var useWindowsAuthentication = true;
var testConnection = false;
_sqlConnection = _dbConnectionCreator.CreateConnection(databaseName,useWindowsAuthentication ,testConnection );
}
}
This is how you do it. Apologies for a major blunder in the earlier answer. This one has the connection surrounded by a using.
namespace rsDeployer.Common.SQLServerCommunication
{
public class ConsumerClass
{
public void QueryExecution(string SQLQuery)
{
var profile = new RsProfile();
var rsConnectionCreator = new RSDatabaseConnectionCreator(profile);
using(var sqlConnection = rsConnectionCreator.CreateConnection(...Parameters here...)){
SqlCommand command = new SqlCommand(SQLQuery, sqlConnection );
}
var file = new StreamWriter(#"D:\Project\rsDeployer\ImportedQuery.txt");
file.WriteLine(command);
file.Close();
}
}
}
You can inject the connection creator into the consumer class through the constructor.
public class Consumer
{
private RSDatabaseConnectionCreator _connectionCreator;
// Constructor injection
public Consumer (RSDatabaseConnectionCreator connectionCreator)
{
_connectionCreator = connectionCreator;
}
public void QueryExecution(string SQLQuery)
{
using (var conn = _connectionCreator.CreateConnection(dbName, true, true)) {
if (conn != null) {
...
}
}
}
}
Note: The using statement automatically closes the connection.
Usage
var connectionCreator = new RSDatabaseConnectionCreator(profile);
var consumer = new Consumer(connectionCreator);
consumer.QueryExecution(sqlQuery);
If you want to inject the connection creator at each call of QueryExecution, you can inject it directly into the method as an additional parameter, instead.
public void QueryExecution(string SQLQuery, RSDatabaseConnectionCreator connectionCreator)
{
using (var conn = connectionCreator.CreateConnection(dbName, true, true)) {
if (conn != null) {
...
}
}
}
Usage
var connectionCreator = new RSDatabaseConnectionCreator(profile);
var consumer = new Consumer();
consumer.QueryExecution(sqlQuery, connectionCreator);
I have created this class for work with my DB connection.
Applications works fine but one or two times in a day i get this errors.
Invalid operation, the connection is completed. when running query: (different queries)
or other times I received this error.
Value Timeout expired. The timeout period expired before completion of the operation or the server is not responding. This error has occurred while attempting to connect to the server Principal.
and rare errors:
Error at the transport level when receiving results from the server. (provider: Session Provider, error:? 19 - You can not use the connection nf sica) when running query
Thread aborted. when executing query:
ExecuteNonQuery requires an open and available Connection. The current state of the connection is closed. when running query
My db Class(resumed):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Data;
using System.Net.Mail;
using System.Web;
using System.IO;
namespace BBDD
{
public class clsBBDD
{
private string strConexion;
private int intTransaciones;
private SqlTransaction Transaccion;
private SqlConnection Conexion;
public clsBBDD(string ParamConexion)
{
strConexion = ParamConexion;
intTransaciones = 0;
}
public int Execute(string Sql)
{
return ExecutePrivado(Sql);
}
public int Execute(string Sql, bool Force)
{
return ExecutePrivado(Sql, Force);
}
private int ExecutePrivado(string Sql,bool Force = false)
{
int result = 0;
SqlCommand sentencia;
try
{
if (!Force && !Sql.ToUpper().Contains("WHERE") && !Sql.ToUpper().Contains("INSERT"))
{
throw new Exception("Sentencia Update o Delete sin WHERE");
}
if (intTransaciones > 0)
{
sentencia = new SqlCommand(Sql, Conexion, Transaccion);
}
else
{
abrirConexion();
sentencia = new SqlCommand(Sql, Conexion);
}
sentencia.CommandTimeout = 0;
result = sentencia.ExecuteNonQuery();
cerrarConexion();
}
catch (Exception e)
{
SendMailError(Sql, e);
result = 0;
}
return result;
}
public bool AbrirTrans()
{
try
{
intTransaciones += 1;
if (intTransaciones == 1)
{
abrirConexion();
Transaccion = Conexion.BeginTransaction();
}
}
catch (Exception e)
{
SendMailError("Error al abrir transacción", e);
return false;
}
return true;
}
public bool CerrarTrans(bool CommitTrans)
{
try
{
intTransaciones -= 1;
if (intTransaciones == 0)
{
if (CommitTrans)
{
Transaccion.Commit();
}
else
{
Transaccion.Rollback();
}
cerrarConexion();
}
}
catch (Exception e)
{
SendMailError("Error al cerrar transacción", e);
return false;
}
return true;
}
public object GetValue(string Sql)
{
object resultado = null;
try
{
SqlCommand sentencia = getCommand(Sql);
SqlDataReader reader = sentencia.ExecuteReader();
while (reader.Read())
{
resultado = reader[0];
}
reader.Close();
cerrarConexion();
}
catch (Exception e)
{
SendMailError(Sql, e);
resultado = null;
}
return resultado;
}
public DataRow GetValuesInRow(string sql)
{
DataRowCollection dsr;
DataRow result;
try
{
dsr = GetDSr(sql);
if (dsr.Count > 0)
{
result = dsr[0];
}
else
{
result = null;
}
}
catch (Exception e)
{
SendMailError(sql,e);
result = null;
}
return result;
}
public DataSet GetDS(string Sql)
{
DataSet result = new DataSet();
SqlDataAdapter adapter;
try
{
SqlCommand command = getCommand(Sql);
adapter = new SqlDataAdapter(command);
adapter.Fill(result);
cerrarConexion();
}
catch (Exception e)
{
SendMailError(Sql, e);
result = null;
}
return result;
}
public DataRowCollection GetDSr(string sql)
{
DataRowCollection result;
try
{
DataSet ds = GetDS(sql);
result = ds.Tables[0].Rows;
}
catch (Exception e)
{
SendMailError(sql, e);
result = null;
}
return result;
}
private void abrirConexion()
{
Conexion = new SqlConnection(strConexion);
if (Conexion.State == ConnectionState.Closed)
{
Conexion.Open();
}
}
private void cerrarConexion(bool Force = false)
{
try
{
if (intTransaciones == 0 && Conexion != null)
{
Conexion.Close();
}
}
catch
{
}
}
private SqlCommand getCommand(string Sql)
{
SqlCommand result;
if (intTransaciones == 0)
{
abrirConexion();
}
result = Conexion.CreateCommand();
if (intTransaciones > 0)
{
result.Transaction = Transaccion;
}
result.CommandText = Sql;
result.CommandTimeout = 0;
return result;
}
}
}
How I Initialize class:
public partial class frmBase : System.Web.UI.Page
{
protected clsBBDD BD;
protected void Page_Init(object sender, EventArgs e)
{
BD = new clsBBDD(ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString);
clsBase.BD = BD;
}
}
All my webforms heritage from frmBase and all clases heritatge from clsBase
For use BD I call like this:
string sql;
DataRowCollection DSTrabajos;
sql = "UPDATE tabletest SET validacion = '" + ValidacionText + "', ModificadoPor = '" + Username + "' WHERE referencia = " + ReferenciaID;
BD.Execute(sql);
sql = "SELECT orderID FROM TrabajosINBOX where referencia = " + ReferenciaID;
DSTrabajos = BD.GetDSr(sql);
foreach (DataRow r in DSTrabajos)
{
//more code inside
}
My connection string params
Data Source=ServerIP;Initial Catalog=BBDDNAME;User ID=USER;Password=***********;Max Pool Size=500;MultipleActiveResultSets=true
I have a code a base that iterates through a list of records and does the follwing
'select * from Table to see if fields exist
then later in the interation
'select * from Table to retreive some data
then farther down in the interation
select field1, field2 from Table to get certain pieces of information
I need to do all of these functions for each record. Would the process speed up if I called the query once for each record and hold the data in a datatable and retreive what I need from there? Or is there another more efficient way to not have to make 3 db calls to the same table which will speed up the process?
You can cache query data to System.Data.DataTable. To simplify things I has written CMyDynaset class, which populates DataTable with data from DB. Here how to use it for example with MySQL:
using System;
using System.Data.Common;
using MySql.Data.MySqlClient;
namesapce MyProg
{
class Program
{
private const string strMyConnection = "Host=localhost;Database=mydb;User Id=myuser;Password=mypsw";
static void Main(string[] args)
{
using (MySqlConnection myConnection = new MySqlConnection(strMyConnection))
{
using (MyDb.CMyDynaset dyn = new MyDb.CMyDynaset(myConnection, MySqlClientFactory.Instance))
{
// populate dyn.Table (System.Data.DataTable)
dyn.Init("select * from Table");
dyn.Load();
// access fields
foreach (DataColumn column in dyn.Table.Columns)
{
// ...
}
// get data
long nCountAll = dyn.Table.Rows.Count; // rows count
foreach (DataRow row in dyn.Table.Rows)
{
Object val1 = row[1]; // acess by index
Object val2 = row["id"]; // acess by name
// ...
}
// update data
dyn.Table.Rows[0]["name"] = "ABC";
dyn.Update();
}
}
}
}
}
CMyDynaset class (CMyDynaset.cs):
// CMyDynaset.cs
using System;
using System.Data.Common;
namespace MyDb
{
/// <summary>
/// Summary description for CMyDynaset.
/// </summary>
public class CMyDynaset : IDisposable
{
public System.Data.DataTable Table = null;
// private
private DbConnection myConnection = null;
private DbProviderFactory myFactory = null;
private DbDataAdapter dataAdap = null;
private DbCommandBuilder cmdBld = null;
private bool bIsSchema = false;
public CMyDynaset(DbConnection conn, DbProviderFactory factory)
{
this.myConnection = conn;
this.myFactory = factory;
}
#region IDisposable Members
public void Dispose()
{
if (this.Table != null)
{
this.Table.Dispose();
this.Table = null;
}
if (this.cmdBld != null)
{
this.cmdBld.Dispose();
this.cmdBld = null;
}
if (this.dataAdap != null)
{
this.dataAdap.Dispose();
this.dataAdap = null;
}
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
#endregion
// Init
public void Init(string strSelect)
{
DbCommand cmdSel = this.myConnection.CreateCommand();
cmdSel.CommandText = strSelect;
this.dataAdap = this.myFactory.CreateDataAdapter();
this.dataAdap.SelectCommand = cmdSel;
this.cmdBld = this.myFactory.CreateCommandBuilder();
this.cmdBld.DataAdapter = this.dataAdap;
this.Table = new System.Data.DataTable();
// schema
this.bIsSchema = false;
}
public void AddParameter(string name, object value)
{
DbParameter param = this.dataAdap.SelectCommand.CreateParameter();
param.ParameterName = name;
param.Value = value;
this.dataAdap.SelectCommand.Parameters.Add(param);
}
public void AddParameter(DbParameter param)
{
this.dataAdap.SelectCommand.Parameters.Add(param);
}
// Open, Close
private void Open(ref bool bClose)
{
if (this.myConnection.State == System.Data.ConnectionState.Closed)
{
this.myConnection.Open();
bClose = true;
}
if (!this.bIsSchema)
{ // schema
this.dataAdap.FillSchema(this.Table, System.Data.SchemaType.Mapped);
this.bIsSchema = true;
}
}
private void Close(bool bClose)
{
if (bClose)
this.myConnection.Close();
}
// Load, Update
public void Load()
{
bool bClose = false;
try
{
this.Table.Clear();
this.Open(ref bClose);
this.dataAdap.Fill(this.Table);
}
catch (System.Exception ex)
{
throw ex;
}
finally
{
this.Close(bClose);
}
}
public void Update()
{
bool bClose = false;
try
{
this.Open(ref bClose);
this.dataAdap.Update(this.Table);
}
catch (System.Exception ex)
{
throw ex;
}
finally
{
this.Close(bClose);
}
}
}
}