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
Related
I'm hopeful that I can get some help/guidance from you fine folks on stackoverflow. I'm in the midst of moving everything from a vba macro project over to a C# word interop add in. For the most part, the conversion has been fine, if a little verbose as I sus out how to achieve in c# what was a 1 or 2 liner in vba. Part of my vba project has some forms, of which some have comboboxes that are loaded with data from a database server (14k+ count at this moment). This data is populated via the following code:
Private Sub IndividualsLoad(Optional vCompany As Variant)
'load the individuals
'if the company is provided, list the individuals in that company
Dim sSql As String
Dim sCompany As String
Dim rsData As Recordset
Dim sOldValue As String
If (IsMissing(vCompany)) Then
sSql = "SELECT DISTINCT LastName,FirstName " & _
"FROM tblIndividuals " & _
"ORDER BY LastName, FirstName;"
Else
sCompany = SQLSanatize(CStr(vCompany))
sSql = "SELECT DISTINCT LastName, FirstName " & _
"FROM tblIndividuals " & _
"WHERE CompanyName = '" + sCompany + "' " & _
"ORDER BY LastName, FirstName"
End If
Set rsData = New ADODB.Recordset
rsData.Open sSql, gdb, adOpenForwardOnly
sOldValue = cboIndividuals
cboIndividuals.Clear
Do Until rsData.EOF
cboIndividuals.AddItem rsData("LastName") & ", " & rsData("FirstName")
rsData.MoveNext
Loop
rsData.Close
Set rsData = Nothing
cboIndividuals = sOldValue
End Sub
that function is called on form load , and the form loads extremely quickly (Less than 2 seconds) when invoked from Word.
Doing that same thing in C# winforms has been much slower (as quick as about 10 seconds [Mississippily], as long as a minute and a half, whether I keep the populate individuals function in Form_Load or Form_Shown events).
I have tried various ways to speed it up combing through answers to other people's stack over flow questions. I've tried filling a data set from a dataadapter directly from the SQL server and making that the data source to the combobox:
public void PopulateIndividualList()
{
/*start test to see if we can speed up form loading a bit*/
DataSet ds = new DataSet();
using (SqlConnection conn = new SqlConnection(Globals.ThisAddIn.GenDBConnectionString()))
{
string qry = GenFileNumbersLoadQry();
SqlDataAdapter da = new SqlDataAdapter(qry, conn);
conn.Open();
da.Fill(ds, "Individuals");
}
cboIndividuals.DataSource = ds;
}
I've tried async populating of the bindinglist:
public static async Task<List<Individual>> IndividualList(string sSQL)
{
var list = new List<Individual>();
using (var cn = new SqlConnection { ConnectionString = Globals.ThisAddIn.GenDBConnectionString() })
{
using (var cmd = new SqlCommand { Connection = cn })
{
cmd.CommandText = sSQL;
await cn.OpenAsync();
var reader = cmd.ExecuteReader();
while (reader.Read())
{
list.Add(new Individual() { FirstName = reader.GetString(1), LastName = reader.GetString(0) });
}
}
}
return list;
}
BindingList<Individual> individuals = new BindingList<Individual>(await ServerOps.IndividualList(qry));
cboIndividuals.DataSource = individuals;
I've tried populating the bindinglist prior to creating the form, and just passing it in the constructor to the form and assigning the list as the data source in either the Form_Load or the Form_Shown event.
I've tried adding the items as the vba code above does in a while reader.Read() loop:
private void IndividualsLoad()
{
string qry = GenIndividualsLoadQry();
string connString = Globals.ThisAddIn.GenDBConnectionString();
using (SqlConnection connection = new SqlConnection(connString))
{
using (SqlCommand command = connection.CreateCommand())
{
command.CommandText = qry;
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
cboIndividuals.Items.Add(reader["FullName"]);
}
}
}
}
}
I've tried adding the items via Combobox.AddRange():
cboIndividuals.Items.AddRange(individuals.ToArray());
I've tried using SendMessage from user32.dll (which acccording to one of the answers on this question would account for a 60% speed up vs. additem():
private const long CB_ERR = -1;
private const uint CB_ADDSTRING = 0x143;
private const uint CB_RESETCONTENT = 0x14B;
private const long CB_SETITEMDATA = 0x151;
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern IntPtr SendMessage(
IntPtr hWnd,
uint Msg,
UIntPtr wParam,
string lParam
);
private void AddItem(ComboBox cmb, string txt)
{
SendMessage(cmb.Handle, CB_ADDSTRING, UIntPtr.Zero, txt);
}
I've long since lost my hair, but I'm just boggled that the vba form is so much quicker for the same thing.
Based on my research, it seems that populating the items into the combobox is definitely the overhead (as opposed to pulling the items down from the SQL server), which makes me assume that the myriad advanced features of the c# combobox vs the vba combobox are the cause for the difference. I toyed with just setting a custom autocomplete source:
cboIndividuals.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
cboIndividuals.AutoCompleteSource = AutoCompleteSource.CustomSource;
AutoCompleteStringCollection combData = new AutoCompleteStringCollection();
getData(combData);
cboIndividuals.AutoCompleteCustomSource = combData;
and if I do that, at least an end user can type out the person they're searching for and the form autosuggests/completes the entry, and most-importantly opens as speedily as the vba version, but I know that change is going to be super hard to swallow for my end users, and if I'm going to keep the dropdown functionality at the expense of loading speed, I'm going to have a tough sale to management of "let's use this slower version".
Does anyone have any ideas out there that I haven't tried to populate a combobox with a large amount of items, or maybe a simple vba-ish combobox without the overhead of the c# one. Or, just another idea completely, as I'm too close to this at this point. Any help/ideas/suggestions here would be greatly appreciated. Thank you in advance for your time.
If you need to let the users browse all the 14k items, then it's not a good solution, but what I ment is something like this. (not yet properly elaborated, but might be a good start)
using System;
using System.Windows.Forms;
namespace ComboBox
{
public partial class Form1 : Form
{
private Timer _timer;
public Form1()
{
InitializeComponent();
InitTimer();
}
private void InitTimer()
{
_timer = new();
_timer.Interval = 700;
_timer.Tick += OnTypeTimerTicked;
}
private void OnTypeTimerTicked(object sender, EventArgs e)
{
_timer.Stop();
//BeginUpdate-EndUpdate helps to gain some speed
comboBox1.BeginUpdate();
//here you can reach out to your repo and probably use AddRange instead of Add
//here you have to filter the results according to your search logic
//and populate the combobox
comboBox1.Items.Add("Item 1");
comboBox1.Items.Add("Item 2");
comboBox1.Items.Add("Item 3");
comboBox1.Items.Add("Item 4");
comboBox1.Items.Add("Item 5");
comboBox1.Items.Add("Item 6");
comboBox1.Items.Add("Item 7");
comboBox1.Items.Add("Item 8");
comboBox1.Items.Add("Item 9");
comboBox1.Items.Add("Item 10");
comboBox1.EndUpdate();
}
private void comboBox1_TextChanged(object sender, EventArgs e)
{
_timer.Start();
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
//your code here
}
finally
{
//maybe it's not the right place for your needs but it must be done somewhere to prevent memory leaks
_timer.Tick -= OnTypeTimerTicked;
}
}
}
}
one more detail about the combobox: it has an AutocompleteMode property. You have to set it to other than None - either in the Designer or in code.
In my Add-in for Visio, I have set a handler for 'ShapeAdded'.
This fired for the first 2 or 3 shapes that are added, but then just stop firing altogether.
Here's the basic outline of how my add-in functions:
User adds shape to page
On ShapeAdded, a form is displayed.
User enters text in form, presses search button
Call to stored Procedure (parameter = user text)
Form's datagridview is populated with results.
User double-clicks result row required.
Form closes, selected value becomes shape text.
If I comment out my code after item (3) - then my event handler continues firing without issue. I can add new shapes all day long.
BUT - once I let code call the stored procedure (step 4), then that is where problems arise.
Very specifically : da.Fill(dt)
I may manage 1 to 6 shape adds, but sooner or later, the event just stops firing.
(*update 8th Jan: Recordset size seems to affect the issue. When 1100 rows are returned each time, I manage to add around 6 shapes to my page. When 3 rows returned each time, I get to add up to 18 shapes before the events stop firing.
That tells me there is something not 'clean' about the way I am handling my data calls - but I cannot see what it is !!)
I am totally baffled as to why calling the stored procedure would interfere with my event handler !?!?
Especially without any error messages.
Has anyone any ideas whatsoever as to what I might be doing wrong ?
Or even, ideas around how to debug this in a better manner ?
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Globals.ThisAddIn.Application.MarkerEvent += new Visio.EApplication_MarkerEventEventHandler(Application_MarkerEvent);
}
private void Application_MarkerEvent(Visio.Application visapp, int SequenceNum, string ContextString)
{
if (ContextString.Contains("soln=myApplication") && ContextString.Contains("/cmd=DocCreated"))
{
SetDocEvents();
}
}
public void SetDocEvents()
{
Microsoft.Office.Interop.Visio.Document doc = Globals.ThisAddIn.Application.ActiveDocument;
// set event handler
try
{
doc.ShapeAdded += new Microsoft.Office.Interop.Visio.EDocument_ShapeAddedEventHandler(onShapeAdded);
}
catch (Exception err)
{
System.Diagnostics.Debug.WriteLine(err.Message);
throw;
}
}
private void onShapeAdded(Visio.Shape Shape)
{
Form_Entity fe = new Form_Entity(Shape.Text);
fe.ShowDialog();
fe.Dispose();
}
// ... other stuff
}
Form Code:
public partial class Form_Entity : Form
{
public Int32 eid { get { return m_id; } }
public string ename { get { return m_name; } }
public string eabbr { get { return m_abbr; } }
private Int32 m_id;
private string m_name;
private string m_abbr;
public Form_Entity()
{
InitializeComponent();
}
public Form_Entity(String search)
{
InitializeComponent();
txt_search.Text = search;
}
private void Cmd_Search_Click(object sender, EventArgs e)
{
String sample = txt_search.Text;
DataTable dt = new DataTable();
SqlConnection myConn = new SqlConnection("Server=xxxxxxx;Database=xxxxxxxx;Trusted_Connection=True;");
myConn.Open();
SqlCommand myCmd = new SqlCommand("[dbo].[mySearch]", myConn);
myCmd.Parameters.Add(new SqlParameter("#searchText", sample));
myCmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter(myCmd);
da.Fill(dt);
dataGridView1.DataSource = dt;
myCmd.Dispose();
myConn.Close();
}
}
** Files for this project
Visual Studio Solution
T-SQL to create sample table/data.procedure
Viso Template
ReadMe.txt
http://www.netshed.co.uk/temp/Vis_Sample.zip
I think the problem here is that you're not keeping the doc object in scope and Visio will stop reporting events for which there is no reference.
You can add a field (or property) as follows and then the reference and associated events should be maintained:
public partial class ThisAddIn
{
private Visio.Document _targetDoc = null;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Application.MarkerEvent += Application_MarkerEvent;
}
public void SetDocEvents()
{
_targetDoc = Globals.ThisAddIn.Application.ActiveDocument;
// set event handler
try
{
_targetDoc.ShapeAdded += onShapeAdded;
}
catch (Exception err)
{
System.Diagnostics.Debug.WriteLine(err.Message);
throw;
}
}
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.
Im trying to create UPDATE command to my program based on C# and Access database. And its working how i want it, but VS2013 display error when i try to update another or the same record second time.
InvalidComObjectException was unhandled.
COM object that has been separated from its underlying RCW can not be
used.
This is how my program looks like:
FormA - Main windows with DataGridView1 of table "Grafik" and Button to open FormB
FormB - Second form with DataGridView2 of table Employyes and Button to FormC
FormC - Form to add, delete and update records directly in to the database "Kategorie" using TextBox'es, ComboBox'es and Buttons (no directly on DataGridView)
UPDATE procedure uses ComboBox (comboBoxWybierzKategorie) to select "category" from database "Kategorie" to update, textBox (textBoxEdytujKategorie) to set new name of selected "Kategoria" and Button to accept procedure.
Broker.cs
public void Update_Kategorie(Kategorie oldKategoria, Kategorie newKategoria)
{
try
{
command.CommandText = "UPDATE Kategorie SET Kategoria = #kat WHERE IDKategorii= #old";
//command.CommandType = CommandType.Text;
command.Parameters.AddWithValue("#kat", newKategoria.Kategoria);
command.Parameters.AddWithValue("#old", oldKategoria.IDKategorii);
connection.Open();
//command.ExecuteNonQuery();
int cmd = command.ExecuteNonQuery();
//connection.Close();
if (cmd > 0)
{
MessageBox.Show("Kategoria zaktualizowana pomyślnie");
//connection.Close();
}
else
{
MessageBox.Show("Wystąpił błąd podczas aktualizacji kategorii.",
"Dodawanie kategorii",
MessageBoxButtons.OK,
MessageBoxIcon.Exclamation,
MessageBoxDefaultButton.Button1);
}
}
catch (OleDbException ex)
{
FormC.cs
private void buttonEdytujKategorie_Click(object sender, EventArgs e)
{
Kategorie oldKategoria = new Kategorie();
Kategorie newKategoria = new Kategorie();
oldKategoria = comboBoxWybierzKategorie.SelectedItem as Kategorie;
newKategoria.Kategoria = Convert.ToString(textBoxEdytujKategorie.Text);
b.Update_Kategorie(oldKategoria, newKategoria);
comboBoxWybierzKategorie.DataSource = b.FillComboBox_Kategorie(); //wypełnij comboBoxWybierzKategorie
textBoxEdytujKategorie.Text = String.Empty; //wyczyść textBoxEdytujKategorie
//this.Close();
//this.Controls.Clear();
//this.InitializeComponent();
I know that code is messed up, sorry for that. More interesting is when i close FormC and reopen it using Button, update function working fine, unless i want use it again.
VS2013 selecting this line as a reason of error:
int cmd = command.ExecuteNonQuery();
Connection to database:
OleDbConnection connection;
OleDbCommand command;
private void ConnectTo()
{
//inside//connection = new OleDbConnection(#"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=bc3e-ps.accdb");
/*outside*/
connection = new OleDbConnection(#"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=D:\bc3e-ps.accdb");
command = connection.CreateCommand();
What im doing wrong?
Since you are adding parameters to the command every time, you should not re-use the command object again.
Just call connection.CreateCommand() every time you want to execute another command.
You can re-use commands, but then you only want to set the existing parameters on each call instead of adding parameters on each call.
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(). [...]