I know there is a similar question on SO that has an answer, and after reading that and other articles, I'm still not sure why I can't get this wired up properly. I've tried quite a bit of examples and have been trying different things and simply can't get it to work. I know there are refactoring opportunities but I'm trying to keep it simple right now just to get it working first.
I have permissions to the db, and I can see that a temporary service and queue gets created. One thing I noticed though was that if I run a query, the queue does not display any records and my dependency_OnChange event does not get fired. I'm assuming it's not getting fired because nothing is entering the queue...
UPDATE: I'm getting it to fire now, however, the only event I get is Subscribe. The SqlNotificationEventArgs show Invalid for the Info, but my query and table match all the SELECT rules for sql dependency.
Global.asax:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
SqlDependency.Start(_entityConnString);
}
protected void Application_End()
{
SqlDependency.Stop(_entityConnString);
}
InfectionPreventionHub.cs: (This is a simple SignalR Hub that starts up with a certain area)
/// <summary>
/// Sends the notifications.
/// </summary>
[HubMethodName("sendUpdates")]
private void SendUpdates()
{
var infectionPreventionItems = InfectionPreventionOperations.GetInfectionPrevention();
var vm = new InfectionPreventionViewModel
{
InfectionPrevention = infectionPreventionItems
};
var result = JsonConvert.SerializeObject(vm);
GetUpdates();
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<InfectionPreventionHub>();
context.Clients.All.RecieveUpdates(result);
}
/// <summary>
/// Gets the updates.
/// </summary>
private void GetUpdates()
{
const string commandText = #"SELECT [Id], [FirstName] FROM [TestDb].[dbo].[Association]";
using (var connection = new SqlConnection(EntityConnString))
{
connection.Open();
using (var command = new SqlCommand(commandText, connection))
{
command.Notification = null;
var dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(sqlDependency_OnChange);
if (connection.State == ConnectionState.Closed)
connection.Open();
// NOTE: You have to execute the command, or the notification will never fire.
using (var reader = command.ExecuteReader())
{
}
}
}
}
/// <summary>
/// Handles the OnChange event of the sqlDependency control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="SqlNotificationEventArgs"/> instance containing the event data.</param>
private void sqlDependency_OnChange(object sender, SqlNotificationEventArgs e)
{
// TODO: Check for the type of event
GetUpdates();
SendUpdates();
}
It was real easy to wire this up to RavenDB, but SqlServer is giving me a hard time.
GetUpdates only fires once when the hub connection happens, which I thought would start up the dependency. However, when updating or adding something to the Association table, no events get raised. I'm required to use SignalR for the UI's realtime updates and data will be entered via 3rd party tools. Any help is appreciated.
Related
I have a Winforms application where I am trying to print a pdf document which has multiple layers on it.
But the problem is, This all operation are running on UI thread and it is hanging the UI(not responding) for long time.
I know, this is happening because of UI thread is blocked so, I have tried to make this operation asynchronous by the help of powerful async/await keyword but still my long running method is not being asynchronous. It is not coming forward from the await tasks and still opearation is taking the same time as like synchronous operation.
What I tried:
Please see below:
/// <summary>
/// Show Print Dialog
/// </summary>
private void ShowPrintDialog()
{
// Initialize print dialog
System.Windows.Controls.PrintDialog prtDialog = new System.Windows.Controls.PrintDialog();
prtDialog.PageRangeSelection = PageRangeSelection.AllPages;
prtDialog.UserPageRangeEnabled = false;
_printOptions.PrintQueue = null;
_printOptions.PrintTicket = null;
Enabled = false;
// if there is a default printer then set it
string defaulPrinter = prtDialog.PrintQueue == null ? string.Empty : prtDialog.PrintQueue.FullName;
// Display the dialog. This returns true if the user selects the Print button.
if (prtDialog.ShowDialog() == true)
{
_printOptions.PrintQueue = prtDialog.PrintQueue;
_printOptions.PrintTicket = prtDialog.PrintTicket;
_printOptions.UseDefaultPrinter = (defaulPrinter == prtDialog.PrintQueue.FullName);
}
// Re-enable the form
Enabled = true;
}
/// <summary>
/// Event raised when user clicks Print
/// </summary>
/// <param name="sender">Source of the event</param>
/// <param name="e">Event specific arguments</param>
private void cmdOk_Click(object sender, EventArgs e)
{
ShowPrintDialog();
if (_printOptions.PrintTicket != null)
{
//Set search Options
_print.ExportDataItem = true;
_print.FileName = SearchTemplateName;
//shows progress bar form.
using (frmPrintSearchResultsProgress frmProgress =
new frmPrintSearchResultsProgress(_print, this, _printOptions))
{
frmProgress.ShowDialog(this);
}
if (_print.ExportDataItem && !_print.DataItemExported && !_print.CancelExport)
{
MessageBox.Show("No Document printed.");
}
}
//Store selected options for current user
SaveOptions();
if (!SkipExport)
Close();
}
/// <summary>
/// Event raised when progress form is shown.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void frmExportSearchResultsProgress_Shown(object sender, EventArgs e)
{
try
{
Application.DoEvents();
dispatcher = Dispatcher.CurrentDispatcher;
// record export/print job start time
_startedUtc = DateTime.UtcNow;
_print.WritingToPdfIndicator = lblWritingPdfFile;
lblProgress.Text = Properties.Resources.PrintSearchResults;
await dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(DoDataItemPrint));
}
}
/// <summary>
/// Prints the selected data items.
/// </summary>
private void DoDataItemPrint()
{
// LONG RUNNING OPERATIONS..
// THIS OPERATION IS BLOCKING THE UI.
}
So, as per mentioned in above code when I opened the PringDialogForm then it is opening a Progress Bar form to see the progress of printing the document and from here frmExportSearchResultsProgress_Shown() event is fired and inside it, I am calling the DoDataItemPrint() method which is time consuming.
So, I tried to make frmExportSearchResultsProgress_Shown event as async/await but still operation is taking the same time as previous.
Can anyone please suggest me where I am doing wrong?
your frmExportSearchResultsProgress_Shown method starts on the UI thread
it then dispatches DoDataItemPrint to the ... same UI thread
it schedules a continuation (via await) so that when that incomplete thing happens, we get back into frmExportSearchResultsProgress_Shown, and since there's probably a sync-context in play here, the sync-context capture (implicit in await) would push us to ... the UI thread
As you can see: everything is happening on the UI thread.
If you want to not block the UI, you need to get off the UI thread. That could be as simple as using Task.Run to invoke DoDataItemPrint, but without knowing what that code contains, it is impossible to know whether you're using thread-bound controls to do the printing. If you are... it will be hard to get away from that.
Apologize for duplicate question. I have googled and everywhere I found only the question but no successful solution. I am facing exactly the same issue described here and here and struggling from past week,but I did not find any success, so finally I decided to ask a question here. I have tried to solve it myself by following the comments suggested in these question, but to success. I want SqlDependency onchange event to fire exactly once for any number for instance or users logged in.
This method is called once whenever a new user login into the application
public List<TransactionMaster> GetUnregisteredTransactions()
{
List<TransactionMaster> ltrans = new List<TransactionMaster>();
TransactionMaster trans = new TransactionMaster();
using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
{
string query = "SELECT TransId, ReceivedFrom,ReceivedOn,Mask FROM [dbo].TransactionMaster WHERE StageId =1";
SqlDependency.Start(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString);
using (SqlCommand cmd = new SqlCommand(query, connection))
{
cmd.Notification = null;
DataTable dt = new DataTable();
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
if (connection.State == ConnectionState.Closed)
connection.Open();
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
adapter.Fill(dt);
ltrans = dal.ConvertDataTable<TransactionMaster>(dt);
}
}
return ltrans;
}
And then again this method is called from onchange event
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
TransactionHub.GetUnregTransactions(GetUnregisteredTransactions());
}
For my case each time event fired I added events to the dependency
dependency.OnChange += new OnChangeEventHandler(SqlNotification.dependency_OnChange);
after I found this solution, i changed my code to :
dependency.OnChange -= new OnChangeEventHandler(SqlNotification.dependency_OnChange);
dependency.OnChange += new OnChangeEventHandler(SqlNotification.dependency_OnChange);
and this solved my problem.
Every time you call GetUnregisteredTransactions you are calling dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);, This is causing a new event to registered, so now you have two events that will fire. when the two events fire it will call dependency.OnChange += new OnChangeEventHandler(dependency_OnChange); two more times making it 4 events that will fire. This number will double with every call till the object dependency is pointing at is garbage collected.
You need to either not re-register the dependency on calls from the event version (for example pass a bool to the function to know if it should do the registration or not) or you need to unregister the old notification before you create a new one.
Here is a example of unregistering.
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
SqlDependency dependency = (SqlDependency)sender;
dependency.OnChange -= new OnChangeEventHandler(dependency_OnChange);
if (e.Type == SqlNotificationType.Change)
TransactionHub.GetUnregTransactions(GetUnregisteredTransactions());
}
I know this is a late answer but I wanted to share my very fun experience with the SqlDependecy firing OnChange event multiple times.
So I ran into this problem as some people did and started looking for answers and quickly realized that there were no code examples that painted the whole picture of how to go about this problem.
This DatabaseWatcher class that I have snatched from
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/detecting-changes-with-sqldependency, I had to modify it to prevent multiple subscriptions to OnChange events that in the end cause multiple OnChange events to be fired due to what I suspect, multiple instances of SqlDepency floating around with those OnChange subscriptions still attached.
The way I go about is by applying the solution from Scott Chamberlain and then catching the current instance of the SqlDepency when subscribing to the OnChange event to make sure to unsubscribe from the OnChange event before liquidating the DatabaseWatcher instance.
Unrelated to the subject, I also forward the OnChange event to hide it in my business class.
public class DatabaseWatcher : IDisposable
{
/// <summary>
/// Forwards the original OnChangeEventHandler event with the database table notification data
/// </summary>
public event EventHandler<SqlNotificationEventArgs> OnTableChange;
public SqlDependency CurrentSqlDependencyInstance { get; set; }
public string ConnectionString { get; set; }
public string Query { get; set; }
public DatabaseWatcher(string connectionString, string query)
{
ConnectionString = connectionString;
Query = query;
}
/// <summary>
/// Starts the database notification listener. Needs to be started before subscribing to the database notifications.
/// </summary>
public void StartDatabaseNotificationsListener()
{
// Create a dependency connection.
SqlDependency.Start(ConnectionString);
}
/// <summary>
/// Subscribes to the database table change notifications. After each notification, a new re-subscription is needed to receive the next database/table notification.
/// </summary>
public void SubscribeToDatabaseNotifications()
{
//create database connection
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
//open database conneciton
connection.Open();
// Create a new SqlCommand object.
using (SqlCommand command = new SqlCommand(Query, connection))
{
// Create a dependency and associate it with the SqlCommand.
SqlDependency dependency = new SqlDependency(command);
//catch current SqlDependency instance
CurrentSqlDependencyInstance = dependency;
// Subscribe to databas/table notifications
dependency.OnChange += new OnChangeEventHandler(OnDependencyChange);
// Execute the command for the initial table snapshot to later compare with when changes arise
var affectedRowsCount = command.ExecuteNonQuery();
}
}
}
private void OnDependencyChange(object sender, SqlNotificationEventArgs e)
{
var sqlDependency = (SqlDependency)sender;
//don't unsubscribe after the subscription event
if (e.Type == SqlNotificationType.Subscribe)
return;
//unsubscribe from the OnChange event from the current instance of the SqlDepency object
sqlDependency.OnChange -= new OnChangeEventHandler(OnDependencyChange);
//don't fire the event if no changes were done to the table records
if (e.Type != SqlNotificationType.Change)
return;
if (e.Info != SqlNotificationInfo.Insert && e.Info != SqlNotificationInfo.Delete)
return;
OnTableChange = new EventHandler<SqlNotificationEventArgs>(OnTableChange);
OnTableChange?.Invoke(sender, e);
}
/// <summary>
/// Stops listening to database notifications, method is called on Dispose
/// </summary>
public void StopDatabaseNotificationsListener()
{
//unsubscribe from the OnChange event from the current instance of the SqlDepency object
CurrentSqlDependencyInstance.OnChange -= new OnChangeEventHandler(OnDependencyChange);
SqlDependency.Stop(ConnectionString);
}
public void Dispose()
{
StopDatabaseNotificationsListener();
}
}
So there it is, please leave comments, thoughts, etc,
Cheers.
I have implemented sql dependency in windows service. when data will be changed in table then onchange event will fire and from there I am invoking a web service.
I will share my full code. I tested many time on my PC before installing the windows service on production PC it works. Suppose if I install on production PC today then it works for today but when I test next day then its onchange event was not firing.
so I found onchange event firing only first day and from the next day onchange event not firing. May be I have made some mistake in code. so it is my request please some one see my code in details and help me where I have made the mistake for which it is not working properly.
public partial class PartIndexer : ServiceBase
{
static string connectionString = "server=xxx;uid=xxx;password=xxx;database=xxx;Pooling=true;Connect Timeout=20;";
SqlDependency dep;
public PartIndexer()
{
InitializeComponent();
}
private string GetLoggedInUser()
{
string userName = "";
if (System.Security.Principal.WindowsIdentity.GetCurrent() != null)
{
userName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
}
return userName;
}
#region OnStart
protected override void OnStart(string[] args)
{
BBALogger.Write("PartIndexer Service OnStart called start", BBALogger.MsgType.Info);
RegisterNotification();
MailSend(); // notification mail send
BBALogger.Write("PartIndexer Service OnStart called end, logged in user " + GetLoggedInUser(), BBALogger.MsgType.Info);
}
#endregion
#region RegisterNotification
/// <summary>
/// RegisterNotification
/// this is main routine which will monitor data change in ContentChangeLog table
/// </summary>
private void RegisterNotification()
{
string tmpdata = "";
BBALogger.Write("PartIndexer Service RegisterNotification called start", BBALogger.MsgType.Info);
System.Data.SqlClient.SqlDependency.Stop(connectionString);
System.Data.SqlClient.SqlDependency.Start(connectionString);
try
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "SELECT TestTable FROM [dbo].ContentChangeLog";
dep = new SqlDependency(cmd);
dep.OnChange += new OnChangeEventHandler(OnDataChange);
SqlDataReader dr = cmd.ExecuteReader();
{
if (dr.HasRows)
{
dr.Read();
tmpdata = dr[0].ToString();
}
}
dr.Dispose();
cmd.Dispose();
}
}
catch (Exception ex)
{
BBALogger.Write("PartIndexer Service RegisterNotification Error "+ex.Message.ToString(), BBALogger.MsgType.Error);
}
finally
{
BBALogger.Write("PartIndexer Service RegisterNotification called end", BBALogger.MsgType.Info);
}
}
#endregion
#region OnDataChange
/// <summary>
/// OnDataChange
/// OnDataChange will fire when after data change found in ContentChangeLog table
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void OnDataChange(object sender, SqlNotificationEventArgs e)
{
((SqlDependency)sender).OnChange -= OnDataChange;
BBALogger.Write("PartIndexer Service RegisterNotification called end", BBALogger.MsgType.Info);
if (e.Source == SqlNotificationSource.Timeout)
{
MailSend(); // notification mail send
BBALogger.Write("PartIndexer Service SqlNotificationSource.Timeout error", BBALogger.MsgType.Error);
Environment.Exit(1);
}
else if (e.Source != SqlNotificationSource.Data)
{
MailSend(); // notification mail send
BBALogger.Write("PartIndexer Service SqlNotificationSource.Data", BBALogger.MsgType.Error);
Environment.Exit(1);
}
else if (e.Type == SqlNotificationType.Change)
{
StartIndex();
BBALogger.Write("PartIndexer Service Data changed", BBALogger.MsgType.Info);
}
else
{
BBALogger.Write(string.Format("Ignored change notification {0}/{1} ({2})", e.Type, e.Info, e.Source), BBALogger.MsgType.Warnings);
}
RegisterNotification();
}
#endregion
#region StartIndex
/// <summary>
/// StartIndex
/// this routine will call web service in bba reman website which will invoke routine to re-index data
/// </summary>
void StartIndex()
{
// calling web service if change is notified
}
#endregion
#region MailSend
/// <summary>
/// MailNotify
/// fire mail when apps start & exit
/// </summary>
/// <param name="strStatus"></param>
void MailSend()
{
// mail send code
}
#endregion
#region OnStop
protected override void OnStop()
{
BBALogger.Write("PartIndexer Service StartIndex called end, logged in user " + GetLoggedInUser(), BBALogger.MsgType.Info);
System.Data.SqlClient.SqlDependency.Stop(connectionString);
MailNotify("STOPPED");
}
#endregion
}
Another issue I noticed that when I start my windows service and leave it run for one day then when I try to stop or restart the the windows service then I windows service cannot be stopped. It means definitely there is some flaw in my code which I am not being able to point out. so please help me. thanks
i just noticed that you used
System.Data.SqlClient.SqlDependency.Stop(connectionString);
System.Data.SqlClient.SqlDependency.Start(connectionString);
sql dependency start and stop inside RegisterNotification() function, but its not correct way, and it may cause the problem to call event next time. it may work one time correctly.
you just try to use in windows start function
#region OnStart
protected override void OnStart(string[] args)
{
System.Data.SqlClient.SqlDependency.Stop(connectionString);
System.Data.SqlClient.SqlDependency.Start(connectionString);
BBALogger.Write("PartIndexer Service OnStart called start", BBALogger.MsgType.Info);
RegisterNotification();
MailSend(); // notification mail send
BBALogger.Write("PartIndexer Service OnStart called end, logged in user " + GetLoggedInUser(), BBALogger.MsgType.Info);
}
#endregion
i hope it will resolve your issue, thanks.
With this code:
using (pbDialog = new pbDialogs())
{
ProgressBar = new frmProgress(this, false);
ProgressBar.SetProgressLabelText("Inventory Data");
MessageBox.Show("Set progress label text to Inventory data");
typeProgress = (int) ProgressStates.ProgressQRY;
ProgressBar.label1.Text += " (Receiving)";
if (pbDialog != null)
{
MessageBox.Show("pbDialog is not null");
//pbDialog.ShowDialog(ProgressBar, this);
}
else
{
MessageBox.Show("pbDialog IS null");
ProgressBar.ShowDialog();
}
ProgressBar = null;
MessageBox.Show("Made it to compressDB()");
compressDB();
. . .
}
I see "Set progress label text to Inventory data"
then "pbDialog is not null"
then "Made it to compressDB()"
Nothing too odd there; but if I uncomment the line that is commented above, I see only "pbDialog is not null"
It is hanging for some reason as a result to the call to ShowDialog(); what is really strange, though, is that this prevents "Set progress label text to Inventory data" from displaying. Why would that be the case?
Note: I think the "pb" in the code stands for "peanut brittle" or some such; I'm pretty sure about the "brittle" part, anyway.
UPDATE
Yeah, the use of ShowDialog() with pbDialog is one of scads of examples that the original coder was practicing job security by obscurity - but then he [un]fortunately skedaddled, leaving in his wake a cesspool of spaghetti/eggshell code with no comments, misleading names and every sort of bizarre and convoluted, counterintuitive practice imaginable in the witches brew he purportedly considered a masterpiece of elegant design and clever-clever tricks.
pbDialog is an instance of a class (pbDialogs). Just to give you a taste of how macabre and convoluted and tangled it all is, here is that class:
public class pbDialogs : IDisposable
{
private static Form m_top;
public pbDialogs()
{
} // pbDialogs Constructor
public static void Activate( Form form )
{
form.Capture = true;
IntPtr hwnd = OpenNETCF.Win32.Win32Window.GetCapture();
form.Capture = false;
OpenNETCF.Win32.Win32Window.SetForegroundWindow( hwnd );
} // Activate
/// <summary>
/// This method makes ShowDialog work the way I want, I think.
/// </summary>
/// <remarks>
/// Here is what it does:
/// 1. Sets the caption of the new window to the same as the caller's.
/// 2. Clears the caption of the parent so it won't show up in the task list.
/// 3. When the ShowDialog call returns, brings the previous window
/// back to the foreground.
/// </remarks>
/// <param name="dialog"></param>
/// <param name="parent"></param>
public void ShowDialog( Form dialog, Control parent )
{
Control top = parent.TopLevelControl;
string caption = top.Text;
dialog.Text = caption;
top.Text = "--pending--"; // Don't show parent in task list
dialog.Activated += new EventHandler( form_Activated );
dialog.Closed += new EventHandler( form_Closed );
m_top = dialog; // New top-most form
dialog.ShowDialog();
m_top = (Form)top; // The top dialog just changed
dialog.Activated -= new EventHandler( form_Activated );
dialog.Closed -= new EventHandler( form_Closed );
top.Text = caption; // Make visible in task list again
Activate( (Form)top ); // And make it the active window
} // ShowDialog
/// <summary>
/// If one of our other windows, such as the main window,
/// receives an activate event, it will activate the current
/// top-most window instead.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void form_Activated( object sender, EventArgs e )
{
if( (m_top != null) && !(sender == m_top) ) // Is this the top-most window?
Activate( m_top ); // No, activate the top dialog
} // form_Activated
private static void form_Closed( object sender, EventArgs e )
{
m_top = null; // When you close the top dialog, it's not top anymore
} // form_Closed
#region IDisposable Members
public void Dispose()
{
// TODO: Add pbDialogs.Dispose implementation
}
#endregion
} // class pbDialogs
There is also a "related" ProgressBar -- a form which shares a file with pbDialogs, and whose instance variable is defined in the file that contains the code above thusly:
public static frmProgress ProgressBar;
This is definitely "whack-a-mole" code - if I make one small, seemingly innocuous change, all Dallas breaks loose in what even a semi-sane person would consider a completely unrelated part of the code.
This may be an indication of just how squirrelly this code/project is: I will make a new build after commenting out a couple of lines, and the size of the file will change from 400KB to 408, or from 412 to 408, etc. It's not normal behavior for an .exe to change that much in size (in a relative sense) with such a small change, is it?
UPATE 2
This, in frmProgress (which has both "public class frmProgress : System.Windows.Forms.Form" and "public class pbDialogs : IDisposable") scares me:
using System.Windows.Forms;
using OpenNETCF.Windows.Forms;
The second (OpenNETCF) is grayed out, indicating it's not really used, but it may be that it was previously used, and somehow that "Windows.Forms" code inadvertently got switched to "System" code, and that may be contributing to its current groundsquirellyness.
ShowDialog is generally a blocking call. The code will not continue past this until the dialog is closed.
I have a grid on a windows form which needs to be filled when the form loads. Data comes from the DB. Currently im calling a LoadData() within form onload. So it takes considerable amount of time to load the screen as there is a DB call.
Is this a good approach ?
Can't i use a background worker and invoke the LoadData() asynchronously so that there wont be any delay in loading the window ?
Can someone explain me what is the best approach ?
Loading data synchronously from database on form load is not a good approach, you should load it asynchronously on separate thread.
You can use either of the following ways:
Thread class
Background Worker
Notes :
Query optimization and indexing the search traget tables
datagrids are complex objects and get time in updating and filling its ordinary( spcially when have more column )
your System hardware (Ram , CPU ,VGA, ... ) that must be considered
i think best approach is sort your query result and paging your result by Last code accessed and use this :
void NextPage()
{
Qr="select Top N from tbl where Code>LastCode order by Code asc";
//reading data
FirstCode=Drr["Code"];//for first record result
LastCode=Drr["Code"];//for last record result
}
void PreviousPage()
{
Qr="select Top N from tbl where Code < FirstCode order by Code asc";
//reading data
FirstCode=Drr["Code"];//for first record result
LastCode=Drr["Code"];//for last record result
}
Hope this Help
you can simply do that using Dispatcher to load data Asynchronously After form Has Rendered
/// <summary>
/// Handles the Loaded event of the Window control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new LoadDataDelegate(LoadData));
}
private delegate void LoadDataDelegate();
/// <summary>
/// Loads the data.
/// </summary>
private void LoadData()
{
List<string> numberDescriptions = new List<string>();
for (int i = 1; i <= 10000000; i++)
{
numberDescriptions.Add("Number " + i.ToString());
}
listBox1.ItemsSource = numberDescriptions;
}