I've tried setting up an SQL Dependency to fire on a "Count Rows" query (written in C#, SQL Server 2008 Express), but after the original subscription SQLNotificationType goes, the event handler never seems to want to fire again (despite rows being added, and I've checked the SQL and it's returning the expected value...).
My code is below. Any thoughts are much appreciated!
EDIT: The project that this code is in is a WPF program. I have this particular code stored in a separate class, which my WPF program creates an instance of in an 'Initialized' event handler. I then have a method in this class that basically calls ConnectToDatabase() first, and then calls SetupSQLDependency().
EDIT 2: As a side note, this program is a WPF which I was hoping to distribute to a few users. The goal was to have the WPF update with certain information whenever new rows were added to a database. I thought that this would be the best way to go about it, as opposed to always querying the database.
private void ConnectToDatabase()
{
//This method is the first to be called, and is the entry
// point into my SQL database code.
databaseConnection = new SqlConnection(connectionString);
// Setup command used in SqlDependecy
SqlCommand tempCmd = new SqlCommand();
tempCmd.Connection = databaseConnection;
tempCmd.CommandText = "SELECT COUNT(ID) FROM [Example].[dbo].[ExampleTable]";
sqlCmd = tempCmd;
try
{ databaseConnection.Open(); }
catch (Exception e)
{ writeDebug(e.ToString()); }
}
private void SetupSQLDependency()
{
SqlDependency.Stop(connectionString);
SqlDependency.Start(connectionString);
sqlCmd.Notification = null;
// create new dependency for SqlCommand
SqlDependency sqlDep = new SqlDependency(sqlCmd);
sqlDep.OnChange += new OnChangeEventHandler(sqlDep_OnChange);
SqlDataReader reader = sqlCmd.ExecuteReader();
}
private void sqlDep_OnChange(object sender, SqlNotificationEventArgs e)
{
// FROM: http://msdn.microsoft.com/en-us/a52dhwx7.aspx
if (e.Type == SqlNotificationType.Change)
{
//++++++ THIS IS THE BLOCK THAT IS NEVER TRIGGERED ++++++//
// Have to remove this as it only work's once
SqlDependency sqlDep = sender as SqlDependency;
sqlDep.OnChange -= sqlDep_OnChange;
// Resetup Dependecy
SetupSQLDependency();
}
else if (e.Type == SqlNotificationType.Subscribe)
{
double te = 12; // Used this just to test a break... code is useless
}
}
I believe the problem here is the COUNT. Refer to the MSDN documentation for Supported SELECT Statements for more info:
The projected columns in the SELECT statement may not contain aggregate expressions unless the statement uses a GROUP BY expression. When a GROUP BY expression is provided, the select list may contain the aggregate functions COUNT_BIG() or SUM(). [...]
Related
I would like to ask if it is possible to load a database into an array or list, and then run queries on it? I have the following code.
string cs = "Data Source=dataBase.sqlite;Version=3;";
SQLiteConnection con;
SQLiteDataAdapter adapt;
DataTable dt;
private void textBox1_TextChanged(object sender, EventArgs e)
{
con = new SQLiteConnection(cs);
con.Open();
adapt = new SQLiteDataAdapter("select * from Table1 where CnName1 like '" + textBox1.Text + "%'", con);
dt = new DataTable();
adapt.Fill(dt);
dataGridView1.DataSource = dt;
con.Close();
}
This works, but it creates a new dataTable whenever a query is run, the problem code is:
dt = new DataTable();
The program is meant to be constantly running, so this is inefficient, since it will eat up a lot of memory. How do I load the database into an object, and then run queries on that object? The table is only meant to have 1 column, and the queries run will only serve as a search function. I want to load the database only once, that is when the program is started, then the connection will be closed, and everything else will be done with the program, not with the database.
Edit: I would like to state for anyone else viewing this question to also view saab669's answer, as it provides useful information as well, however I can not choose two answers.
Assuming you have a form containing your text box. Declare a class level variable to store you datatable.
private DataTable _data;
Create a class to encapsulate your database connection and retrieval of data.
public class MyDataBaseConnection
{
public DataTable ReturnMyData(string valueFromTextBox)
{
var cs = "Data Source=dataBase.sqlite;Version=3;";
SQLiteConnection con;
SQLiteDataAdapter adapt;
DataTable dt;
try
{
con = new SQLiteConnection(cs);
con.Open();
adapt = new SQLiteDataAdapter("select * from Table1 where CnName1 like '" + textBox1.Text + "%'", con);
dt = new DataTable();
adapt.Fill(dt);
con.Close();
return dt;
}
catch (Exception ex)
{
//Log here.
throw;
}
finally
{
con = null;
adapt = null;
//Or Dispose. I dont have SQL lite so dont know if they implement IDispose
}
}
}
In your textbox change event, call the db code and assign to your class level var
private void textBox1_TextChanged(object sender, EventArgs e)
{
var myDBConnection = new MyDataBaseConnection();
_data = myDBConnection.ReturnMyData(textBox1.Text);
dataGridView1.DataSource = null;
dataGridView1.DataSource = _data;
}
When the text changed event fires again, the data will be changed in the grid.
Hope that helps.
My response is too long for a comment, but I want to reply to a few things you said:
but it creates a new dataTable whenever a query is run, the problem code is dt = new DataTable(); The program is meant to be constantly running, so this is inefficient, since it will eat up a lot of memory.
Sort of. You only ever have one DataTable object. It's just that every time that event fires, it's re-querying the database. So you're not consuming an excessive amount of memory (unless your DB is huge!), or at least not in the way you think you are.
How do I load the database into an object, and then run queries on that object?
As I mentioned in my comment, you're doing that already. Just in a less-than-ideal event handler. Depending on how you want the application to behave, you should move this to the form load event as defined here. Or perhaps it might make more sense to have a button with a click event. The form load could add a long delay to your application's start up and the user might think it froze, depending on how long it takes you to fetch all the records from the DB.
Also, from the code snippet you provide, am I correct in assuming you define DataTable dt; outside of the event handler, on the class level?
Anyways, per the MSDN article I linked in my comment, once you have a DataTable populated then you can simply do things like this:
DataRow[] foundRows;
foundRows = dataSet1.Tables["TableName"].Select("ColumnName LIKE 'your_search_value%'");
Lastly, and I cannot stress this enough about the code you provided: DO NOT concatenate strings for a query which will execute against a database. I don't care if it's for a homework assignment or a little tool only you will use, there's no excuse for not taking the extra time to learn how to do it the right way. There's no reason to build a bad habbit, as this is susceptible to SQL injection which is comically easy to protect against. You absolutely should spend the 30 minutes to learn about parameterized queries.
I have two functions with the first one filling a comboBox with the tables within my SQL Database, this it below:
private void FillCombo()
{
comboBox1.Items.Clear();
try
{
string connectionString = "Data Source=LPMSW09000012JD\\SQLEXPRESS;Initial Catalog=Pharmacies;Integrated Security=True";
using (SqlConnection con2 = new SqlConnection(connectionString))
{
con2.Open();
string query = "SELECT * FROM INFORMATION_SCHEMA.TABLES ";
SqlCommand cmd2 = new SqlCommand(query, con2);
SqlDataReader dr2 = cmd2.ExecuteReader();
while (dr2.Read())
{
int col = dr2.GetOrdinal("TABLE_NAME");
comboBox1.Items.Add(dr2[col].ToString());
}
comboBox1.SelectedIndex = 0;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
And I have another one which is filling the second comboBox based on the value from the previous function's combobox. This is it below
async void fillLiguanea()
{
comboBox2.Items.Clear();
try
{
string connectionString = "Data Source=LPMSW09000012JD\\SQLEXPRESS;Initial Catalog=Pharmacies;Integrated Security=True";
SqlConnection con = new SqlConnection(connectionString);
con.Open();
string query = "SELECT * FROM " + comboBox1.Text;
SqlCommand cmd = new SqlCommand(query, con);
var reader = await cmd.ExecuteReaderAsync();
comboBox2.BeginUpdate();
while (reader.Read())
{
string scode = reader.GetString(reader.GetOrdinal("code"));
comboBox2.Items.Add(scode);
}
comboBox2.EndUpdate();
comboBox2.SelectedIndex = 0;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}}
What am trying to do is to refresh the "fillLiguanea" function's comboBox value based on the table selected in the "fillCombo" function. Eg. if there are tables with the name "cat" and "dog" in my comboBox that is filled by "fillLiguanea" then when selected it should automatically change the comboBox which is filled by "fillLiguanea" with the various cat or dog breeds.
I was reading and saw something about the SelectionChangeCommitted event. Is this the way to go or is there a better way to do this?
I've achieved this through a refresh button that targets my second comboBox but I would rather eliminate the use of buttons for the user
You have to use events.
See: https://msdn.microsoft.com/en-us/library/awbftdfh.aspx, https://msdn.microsoft.com/en-us/library/edzehd2t(v=vs.110).aspx
In short, an event is simply a way to trigger code by following the Observer Pattern.
Basically, an event represents multiple methods (I'll refer to these as "subscribing methods" as they "subscribe" to the event) that all get called when the event is raised from within the class it is defined in.
"Okay," you might say. "But when implementing a method to be called when the event is raised, how do I ensure I have the correct parameter types?".
Delegates.
A delegate represents the signature of a method (ie, the number of parameters/the parameter types/the method's return type). Each event is declared as being a type of a specific delegate.
For example, let's say you want an event that will call it's subscribing methods when an arbitrary message is received. How would you setup such a system using these things called "Events"?
Define your delegate
public delegate void MessageDelegate(string data);
This means (and enforces) that all subscribing methods to the event must contain just one parameter and it's type must be string. Also that each subscribing method must not return any value.
Define your event
public event MessageDelegate MessageReceived;
This says that we create an event (a collection of subscribed methods) where each subscribing method must match the signature defined by the delegate MessageDelegate.
Subscribe to your event (probably the part you're most interested in)
To subscribe to the event (or add a method to the event's collection of subscribers) you first must create a method that matches the delegate:
private void OnMessageReceived(string msg)
{
//Do something with the received message.
}
Then comes the actual subscribing:
MessageReceived += new MessageDelegate(OnMessageReceived).
Now whenever the event is triggered our OnMessageReceived method will be called.
Finally, to trigger or raise the event (the process of calling each subscribing method) you simply can do:
MessageReceived("Hello World!")
All that being said, how does this apply to your particular problem?
Well, the combobox control already contains the event. So you obviously don't have to define that yourself. That means all you're responsible for is subscribing to the event.
comboBox1.SelectionChangeCommitted += new EventHandler(comboBox1_SelectionChangeCommitted);
private void comboBox1_SelectionChangeCommitted(object sender, EventArgs e)
{
//Refresh combobox 2.
}
I recommend you put the actual subscription in the form's Form_Load event.
private void Form1_Load(object sender, EventArgs e)
{
comboBox1.SelectionChangeCommitted += new EventHandler(comboBox1_SelectionChangeCommitted);
}
Now however, you must subscribe to the form's load event. Typically done in the constructor after the call to InitializeComponent.
public Form1()
{
InitializeComponent();
Load += new EventHandler(Form1_Load);
}
You can of course, bypass the form load subscription
public Form1()
{
InitializeComponent();
comboBox1.SelectionChangeCommitted += new EventHandler(comboBox1_SelectionChangeCommitted);
}
Lastly, if you're using the WindowsForms designer in Visual Studio:
Somewhere on the property pane there should be a way to view events for the selected control. Scroll down to your desired event. Double-click it. Visual studio should automatically create a method and subscribe to the event with the new method.
There is so much power behind events and delegates. I highly encourage you to do some reading up on them.
Sorry this wasn't as small as I envisioned but I thought I would try to explain why it works and not just "This is how it should be done.".
This turned out being a simple solution. I just passed the comboBox from the fillCombo function into the fillLiguanea function and everything worked as I wanted. The two functions are listed below:
This is the fillCombo method which is populating from the SQL database to my comboBox called "comboBox4"
private void FillCombo()
{
comboBox4.Items.Clear();
try
{
string connectionString = "Data Source=LPMSW09000012JD\\SQLEXPRESS;Initial Catalog=Pharmacies;Integrated Security=True";
using (SqlConnection con2 = new SqlConnection(connectionString))
{
con2.Open();
string query = "SELECT * FROM INFORMATION_SCHEMA.TABLES ";
SqlCommand cmd2 = new SqlCommand(query, con2);
SqlDataReader dr2 = cmd2.ExecuteReader();
while (dr2.Read())
{
int col = dr2.GetOrdinal("TABLE_NAME");
comboBox4.Items.Add(dr2[col].ToString());
}
// comboBox4.SelectedIndex = 0;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
And this is my fillLiguanea method that updates the table based on the comboBox4 selection from my above function.
async void fillLiguanea()
{
comboBox2.Items.Clear();
try
{
string connectionString = "Data Source=LPMSW09000012JD\\SQLEXPRESS;Initial Catalog=Pharmacies;Integrated Security=True";
SqlConnection con = new SqlConnection(connectionString);
con.Open();
string query = "SELECT * FROM " + comboBox4.Text;
SqlCommand cmd = new SqlCommand(query, con);
var reader = await cmd.ExecuteReaderAsync();
comboBox2.BeginUpdate();
while (reader.Read())
{
string scode = reader.GetString(reader.GetOrdinal("code"));
comboBox2.Items.Add(scode);
}
comboBox2.EndUpdate();
comboBox2.SelectedIndex = 0;
// comboBox2.Sorted = true;
}
This is the important line of code:
string query = "SELECT * FROM " + comboBox4.Text;
With that everything was solved
I am creating a form with several calculations from the same table (in this case). The code works fine, but I could with with some guidance to make sure I am doing things efficiently:
When the form loads, I simply want two textblocks to be populated with counts. I know that I will need to put some error checking in, but outside of that - is this a good way of doing it?
private void Window_Loaded(object sender, RoutedEventArgs e)
{
int intCount = ReturnNumber("SELECT COUNT(ActivityID) FROM tblActivity WHERE [Activity_Category] = 'Productivity'");
TxtBlockProductivityPerc.Text = intCount.ToString();
intCount = ReturnNumber("SELECT COUNT(ActivityID) FROM tblActivity WHERE [Activity_Category] = 'Revenue'");
TxtBlockRevenuePerc.Text = intCount.ToString();
}
public int ReturnNumber(string StrQuery)
{
string cs = ClsVariables.StrDb;
string cmdText = StrQuery;
using (SQLiteConnection con = new SQLiteConnection(cs))
using (SQLiteCommand cmd = new SQLiteCommand(cmdText, con))
{
con.Open();
int count = Convert.ToInt32(cmd.ExecuteScalar());
con.close();
return count;
}
}
Basically, if you are not developing an application in MVVM style, such an approach is not bad, but it's just my opinion. Here a couple of my comments:
In this situation, I think better to use event Window.ContentRendered, because Loaded event is triggered when loading Window as Control, and the ContentRendered event triggered when rendering the contents of the Window. But the big difference no between them link.
You have to be separately stored query strings, because every time we need to change them, you'll have to climb into your function, which is not convenient.
Add to the functions that work with SQL server prefix FromSQL, in your case will be something like this: ReturnNumberFromSQL().
You do not need a temporary variable, you can call the function and immediately get a result from it.
My pseudo example:
private void Window_ContentRendered(object sender, EventArgs e)
{
string Test1Sql = "Test1 SQL query"; // stored separately
string Test2Sql = "Test2 SQL query"; // stored separately
MyTextBlock1.Text = ReturnNumberFromSQL(Test1Sql).ToString();
MyTextBlock2.Text = ReturnNumberFromSQL(Test2Sql).ToString();
}
public int ReturnNumberFromSQL(string StrQuery)
{
return 777;
}
And think on the expense of having to store procedures that do not work with GUI separately in the appropriate class.
I'm trying to make a DataGridView that displays data from an SQL database (GSM.sdf) and saves changes made to the DataGridView back to the database when a save button is pressed. The data displays fine, but nothing happens when the save button is pressed. I've been following the top answer from this thread:
http://social.msdn.microsoft.com/Forums/en/csharpgeneral/thread/98bc0b4d-a2ea-4b74-81f0-473da624528a
But it isn't working out. Here is my code:
namespace WindowsFormsApplication5
{
public partial class Zeelot : Form
{
DataTable table = new DataTable();
SqlCeDataAdapter z;
DataSet gSMDataSet = new DataSet();
public Zeelot()
{
InitializeComponent();
}
private void Zeelot_Load(object sender, EventArgs e)
{
string b = #"Data Source =.\SQLEXPRESS;database=GSM;Integrated Security=FALSE;Connection Timeout=30;User Instance=FALSE";
SqlCeConnection conn = new SqlCeConnection(b);
conn.Open();
string cd = "SELECT * FROM PhoneNumbers";
z = new SqlCeDataAdapter(cd, conn);
z.Fill(gSMDataSet, "PhoneNumbers");
table = gSMDataSet.Tables[0];
conn.Close();
dataGridView1.DataSource = table;
}
private void SaveButton_Click(object sender, EventArgs e)
{
SqlCeCommandBuilder local_SqlCommandBuilder = new SqlCeCommandBuilder(z);
local_SqlCommandBuilder.ConflictOption = System.Data.ConflictOption.OverwriteChanges;
z.UpdateCommand = local_SqlCommandBuilder.GetUpdateCommand();
z.Update(((System.Data.DataTable)this.dataGridView1.DataSource));
((System.Data.DataTable)this.dataGridView1.DataSource).AcceptChanges();
}
}
}
I believe your problem lies where you are casting a DataSource to System.Data.DataTable
Try z.Update(gSMDataSet);
I also don't believe you need AcceptChanges()
you dont need to call .AcceptChanges() on the DataGridView at the end.
AcceptChanges() submits the changes from your DGV to the connected DataSource (your DataTable)
Try calling AcceptChanges() before you try to submit the changes to your Database.
I think your problem is with your connection. You manually create and open a connection and assign it to your DataAdapter. But within your _Load() Method, you close the connection. As the DataAdapter used inside the _Click EventHandler is the same as the one used in your _Load() Method (therefore uses the same connection). It can't submit any changes to your Database because you already closed the connection.
Don't you get any exceptions at runtime?
Try using breakpoints to examine the current state of your connection Object before you attempt to submit your changes to your Database.
Also, AFAIK MS advises against creating connections manually if you want to use them within a DataAdapter.
DataAdapter's Constructor can create a connection on its own
Is there a way to get SQL Server 2005 to call back to a connected application, such that the connected application will know when a record in a table has had a field modified by another application using the same database?
A simple example would be two instances of the same application connecting to the same table in the same database. When one instance of the application makes a change to a table, the other instance would get a notification that something has changed and be able to query the database for the change.
UPDATE
Thanks so much for the help so far. I would have never even known to look for the SqlDependency class. I've followed the instruction on this page http://msdn.microsoft.com/en-us/a52dhwx7.aspx in creating the SqlDependency test demo. However, I wasn't able to get that to work. I never see the OnChange event get called.
I've also attempted to modify my own application using the instructions as a guide with no luck. I've included the code from my own application below. Basically, the Position table has a PositionID field along with a LocationX and LocationY field. I've written another application that allows me to update the LocationX field of a given row.
What am I missing? Why won't the database changes trigger my even handler?
UPDATE #2
Also note that I am using a hard coded SQL string for my command. I would prefer not to and use the commented out LINQ statement instead. Is it considered OK to use LINQ in this way to generate the SQL string that will be used to build the command?
UPDATE #3
So I managed to figure out what was wrong with my code below. Apparently you have to execute the command once so there will be a cache of data, or else the server doesn't know when to notify you? I added in a line to do a DataAdapter.Fill() with my SqlCommand and the event now seems to fire when expected.
Which brings me to my next problem. The SqlDependency.OnChange event only lets you know that something has changed. How can I figure out from my old DataSet and the new DataSet what the row-by-row changes are?
I could of course, read the entire query again and update all of my data structures, but that seems excessive.
I can call the DataContext.Refresh() and have it do all the updates to my data structures, but that doesn't seem to trigger any of the DataContext generated OnChanging() events. It seems that Refresh() actually tears down all my structures and creates new ones. So I can never figure out what has changed.
Does anyone have any recommendations?
public partial class MainForm : Form
{
private ArpPhase2DbContextDataContext db = null;
private SqlConnection connection = null;
private SqlCommand command = null;
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
this.canRequestNotifications();
this.db = ArpPhase2DbContextDataContext.Instance;
this.setupSqlDependency();
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
SqlDependency.Stop(this.db.Connection.ConnectionString);
if (this.connection != null)
{
this.connection.Close();
}
this.db.SubmitChanges();
}
private bool canRequestNotifications()
{
try
{
SqlClientPermission perm = new SqlClientPermission(PermissionState.Unrestricted);
perm.Demand();
return true;
}
catch
{
return false;
}
}
private void setupSqlDependency()
{
// Remove any existing dependency connection, then create a new one.
SqlDependency.Stop(this.db.Connection.ConnectionString);
SqlDependency.Start(this.db.Connection.ConnectionString);
if (this.connection == null)
{
this.connection = new SqlConnection(this.db.Connection.ConnectionString);
}
if (this.command == null)
{
var sql = (from position in this.db.Positions
select position);
//string commandString = sql.ToString();
string commandString = "SELECT * FROM Positions;";
this.command = new SqlCommand(commandString, connection);
}
this.getData();
}
private void getData()
{
// Make sure the command object does not already have
// a notification object associated with it.
this.command.Notification = null;
// Create and bind the SqlDependency object
// to the command object.
SqlDependency dependency = new SqlDependency(this.command);
dependency.OnChange += new OnChangeEventHandler(this.dependency_OnChange);
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
// This event will occur on a thread pool thread.
// Updating the UI from a worker thread is not permitted.
// The following code checks to see if it is safe to
// update the UI.
ISynchronizeInvoke i = (ISynchronizeInvoke)this;
// If InvokeRequired returns True, the code
// is executing on a worker thread.
if (i.InvokeRequired)
{
// Create a delegate to perform the thread switch.
OnChangeEventHandler del = new OnChangeEventHandler(this.dependency_OnChange);
object[] args = { sender, e };
// Marshal the data from the worker thread
// to the UI thread.
i.BeginInvoke(del, args);
return;
}
// Remove the handler, since it is only good
// for a single notification.
SqlDependency dependency = (SqlDependency)sender;
dependency.OnChange -= this.dependency_OnChange;
// Add information from the event arguments to the list box
// for debugging purposes only.
Console.WriteLine("Info: {0}, Source: {1}, Type: {2}", e.Info.ToString(),
e.Source.ToString(), e.Type.ToString());
// Rebind the dependency.
this.setupSqlDependency();
}
}
SQL Server can do this with Query Notifications. There's nothing built into L2S to support this, but also nothing to stop you from using it outside of L2S in the same app.
Query Notifications uses the indexed view technology to detect data changes and notify subscribed queries when the result set has possibly changed. This is the technology that powers the ASP SqlCacheDependency for cache invalidation. You can read more into how it works at The Mysterious Notification.
In .Net Framework the most comonly used component that leverages Query Notifications is SqlDependency. There are various samples on how to integrate linq2sql with SqlDependency, like linqtosqlcache.
You should not use this technology to watch for data that changes frequently but solely for catalog reference data that is worth caching. The cost of setting up and delivering the notification is significant.
Why do you want to do that?
Linq-to-SQL was dead before you began using it.
Now they push EF, WCF-DS etc. (who knows when they will kill them too).
Even query notifications are not a safe bet anymore (as they are so fragile if you have an app that is going to last more than a few years).