The Scenario
Currently I have a C# Silverlight Application That uses the domainservice class and the ADO.Net Entity Framework to communicate with my database. I want to load a child window upon clicking a button with some data that I retrieve from a server-side query to the database.
The Process
The first part of this process involves two load operations to load separate data from 2 tables. The next part of the process involves combining those lists of data to display in a listbox.
The Problem
The problem with this is that the first two asynchronous load operations haven't returned the data by the time the section of code to combine these lists of data is reached, thus result in a null value exception.....
Initial Load Operations To Get The Data:
public void LoadAudits(Guid jobID)
{
var context = new InmZenDomainContext();
var imageLoadOperation = context.Load(context.GetImageByIDQuery(jobID));
imageLoadOperation.Completed += (sender3, e3) =>
{
imageList = ((LoadOperation<InmZen.Web.Image>)sender3).Entities.ToList();
};
var auditLoadOperation = context.Load(context.GetAuditByJobIDQuery(jobID));
auditLoadOperation.Completed += (sender2, e2) =>
{
auditList = ((LoadOperation<Audit>)sender2).Entities.ToList();
};
}
I Then Want To Execute This Immediately:
IEnumerable<JobImageAudit> jobImageAuditList
= from a in auditList
join ai in imageList
on a.ImageID equals ai.ImageID
select new JobImageAudit
{
JobID = a.JobID,
ImageID = a.ImageID.Value,
CreatedBy = a.CreatedBy,
CreatedDate = a.CreatedDate,
Comment = a.Comment,
LowResUrl = ai.LowResUrl,
};
auditTrailList.ItemsSource = jobImageAuditList;
However I can't because the async calls haven't returned with the data yet...
Thus I have to do this (Perform the Load Operations, Then Press A Button On The Child Window To Execute The List Concatenation and binding):
private void LoadAuditsButton_Click(object sender, RoutedEventArgs e)
{
IEnumerable<JobImageAudit> jobImageAuditList
= from a in auditList
join ai in imageList
on a.ImageID equals ai.ImageID
select new JobImageAudit
{
JobID = a.JobID,
ImageID = a.ImageID.Value,
CreatedBy = a.CreatedBy,
CreatedDate = a.CreatedDate,
Comment = a.Comment,
LowResUrl = ai.LowResUrl,
};
auditTrailList.ItemsSource = jobImageAuditList;
}
Potential Ideas for Solutions:
Delay the child window displaying somehow?
Potentially use DomainDataSource and the Activity Load control?!
Any thoughts, help, solutions, samples comments etc. greatly appreciated.
First of there is no point in delaying the display of a window. Instead you should design your code to be able to handle asynchronous updates to the data. In this case you have a somewhat interesting situation where you are performing two asynchronous load operations and you are only able to create the data for display when both operations have completed.
One solution to this problem is to move the query where you combine the data to the server side. Then instead of retrieving Image and Audit objects from the server in two separate operations you can retrieve JobImageAudit objects.
Another solution is to create something similar to a view-model for the data you retrieve. Here is a rough sketch to get you started:
public class JobImageAuditViewModel : INotifyPropertyChanged {
IEnumerable<Image> images;
IEnumerable<Audit> audits;
IEnumerable<JobImageAudit> jobImageAudits;
public void GetData() {
this.images = null;
this.audits = null;
this.jobImageAudits = null;
OnPropertyChanged("JobImageAuditList");
// Load images by using GetImageByIDQuery()
// Load audits by using GetAuditByJobIDQuery()
}
void LoadImageCompleted(Object sender, EventArgs e) {
// Store result of query.
this.images = ...
UpdateJobImageAuditList();
}
void LoadAuditCompleted(Object sender, EventArgs e) {
// Store result of query.
this.audits = ...
UpdateJobImageAudits();
}
void UpdateJobImageAudits() {
if (this.images != null && this.jobs != null) {
// Combine images and audits.
this.jobImageAudits = ...
OnPropertyChanged("JobImageAudits");
}
}
public IEnumerable<JobImageAudit> JobImageAudits {
get {
return this.jobImageAudits;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(String propertyName) {
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
You then have to databind auditTrailList.ItemsSource to JobImageAuditViewModel.JobImageAudits. You can do this by setting the DataContext of the ChildWindow or UserControl that contains auditTrailList to an instance of JobImageAuditViewModel and add this attribute to the auditTrailList XAML:
ItemsSource="{Binding JobImageAudits}"
Actually the .NET RIA framework is designed to let the client-side generated entitiy classes assume the role of the view-model in an MVVM application. They can be extended on the client side and they support INotifyPropertyChanged. However, in your case you are using an entity on the client side that doesn't exist on the server side. Combining my first suggestion with data-binding is probably the ultimate solution to your problem.
Related
I am new to development and trying to retrieve a record to display and edit in a FormView on an ASP.NET web app page, using Entity Framework 5.0 (database first method), but I am not sure of the best way to go about this.
To retrieve the record, I use the following code:
protected void Page_Load(object sender, EventArgs e)
{
LoadData(int.Parse(Session["PersonID"].ToString()));
}
private void LoadData(int iPersonID)
{
using (PeopleEntities ctx = new PeopleEntities())
{
var query = (from a in ctx.People
where a.PersonID == iPersonID
select a).FirstOrDefault();
TextBoxFirstName.Text = query.FirstName;
TextBoxLastName.Text = query.LastName;
}
}
And to save it, I use:
protected void ButtonSave_Click(object sender, EventArgs e)
{
SaveEmployee(int.Parse(Session["PersonID"].ToString()));
}
private void SaveEmployee(int iPersonID = 0)
{
using (PeopleEntities ctx = new PeopleEntities())
{
var query = (from a in ctx.People
where a.PersonID == iPersonID
select a).FirstOrDefault();
query.FirstName = TextBoxFirstName.Text;
query.LastName = TextBoxLastName.Text;
ctx.SaveChanges();
}
}
It seems silly to me to have these two methods each query the database to retrieve and update the record, but, again, I am a novice, and maybe I'm just missing something. Is there a way to populate the controls on the FormView with the entity and have a method to save the record without having to manually assign the values (query.FirstName = TextBoxFirstName.Text, etc.) based on state?
I have seen the EntityDataSource, but I do not think that will be a good option for anything but the simplest of things.
Can anyone please tell me if what I am doing is ok or provide a better example or guidance?
Your help is greatly appreciated!
Best approach, IMHO, is that when you're retrieving data to DISPLAY only, do it without change-tracking. This will avoid performance issues. So, use the AsNoTracking method to avoid change-tracking proxies.
Than, for the update, you should load WITH change-tracking enabled, that's why there is no call to AsNoTracking on the save part.
Remember to check for null values. You're using the FirstOrDefault but since you're using the primary key, there won't be a second record, so just using SingleOrDefault. But since default (null) can happen, check for null values.
Also, use lambda expressions. They are not so easy to get with at first but you'll get used with minor effort and they will simplify your code a lot.
But from your question, there are some workarounds to avoid this but they are not the best approach. You should avoid long-living entities and prefer ViewModels for long-living objects, with the UnitOfWork pattern in mind for the repository and persistent entities.
If you really want that, you can Detach your entity from the context, use it everywhere and when you're ready, Attach it back and set it's state to Modified.
For this, take a look here: http://msdn.microsoft.com/en-us/library/bb896271.aspx
On your case, I'd suggest this:
private void LoadData(int iPersonID)
{
using (PeopleEntities ctx = new PeopleEntities())
{
// AsNoTracking will avoid performance hit of change-tracking here...
// Since we're building-up a view, not an update case yet, you don't have to create
// proxies that will check for entity changing...
var query = ctx.People.AsNoTracking().SingleOrDefault(_people => _people.PersonID == iPersonID)
// Rendering comes into action
if (query != null)
{
TextBoxFirstName.Text = query.FirstName;
TextBoxLastName.Text = query.LastName;
}
}
}
private void SaveEmployee(int iPersonID = 0)
{
using (PeopleEntities ctx = new PeopleEntities())
{
var query = ctx.Prople.SingleOrDefault(_person => _person.PersonID == iPersonID);
if (query != null)
{
query.FirstName = TextBoxFirstName.Text;
query.LastName = TextBoxLastName.Text;
ctx.SaveChanges();
}
}
}
This is also how I do it. It is necessary to retrieve the object you wish to update.
"It seems silly to me to have these two methods each query the database
to retrieve and update the record"
You are absolutely correct Don't Repeat Yourself should be a mantra principal that you should endeavour to follow.
Here you have chosen to retrieve the data in the page load event and retrieve it again in the button click event. Both these events are happening within the same instance of a WebPage. You could store it in an instance variable and re-use it in the button click, or you could set up a property for the entity that is "lazy loaded". There are all sorts of ways of doing it. Lazy loading is probably definitely overkill here because you will probably only use the property in the PageLoad should understand when it is necessary to go to the database and when it isn't.
It is necessary to go to the database to get the data you want to display when the page is first loaded. Thereafter the data is normally present in form values when the page posts back.
It is also necessary to go to the database when you are updating a record - which happens in this example when your user clicks on the save button.
Here is an example of lazy loading something i probably shouldn't have mentioned:
private People _Person;
//lazy loaded property
private People Person
{
get
{
if (_Person == null)
using (PeopleEntities ctx = new PeopleEntities())
_Person = GetPerson(ctx);
//returning a Person that isn't updateable because we've disposed of the context
return _Person;
}
}
//Retrieve an updateable person
private static object GetPerson(PeopleEntities ctx)
{
return (from a in ctx.People
where a.PersonID == int.Parse(Session["PersonID"]
select a).FirstOrDefault();
}
The other problem your code has is that you always set the TextBoxes in the PageLoad event from the values in the database. This means that when you get to the ButtonSave_Click event the values posted back have been overwritten by what was in the database and the changes won't be saved!.
So you should do this instead:
protected void Page_Load(object sender, EventArgs e)
{
if(!IsPostBack)//Only do this first time it's loaded
{
TextBoxFirstName.Text = Person.FirstName;
TextBoxLastName.Text = Person.LastName;
}
}
And your button click can look like this:
protected void ButtonSave_Click(object sender, EventArgs e)
{
SavePerson(TextBoxFirstName.Text, TextBoxLastName.Text);
}
private SavePerson(string firstName, string lastName)
{
using (PeopleEntities ctx = new PeopleEntities())
{
var person = GetPerson(ctx);
person.FirstName = firstName;
person.LastName = lastName;
ctx.SaveChanges();
}
}
As you progress with your coding you will find that you want to repeat the SavePerson and GetPerson code on other pages. - and that is when you start introducing repositories or layers. Don't forget the mantra principal that you should endeavour to follow and move the code to another class so that you can re-use it.
That class should be in a PeopleRepository or some other layer. Eventually you will find that the code in the PeopleRepository looks very like the code in the MantraRepository and you will want to stop repeating yourself for different types.
That is when you should start using "generics". You replace the PeopleRepository and the MantraRepository with a Repository<People> and a Repository<Mantra> and the code is in one class defined something like public class BaseRepository<T>.
Before you go on that journey though, there is another thing about the Entity Framework bit - instead of
var query = (from a in ctx.People
where a.PersonID == iPersonID
select a).FirstOrDefault();
you should/could use
var query = ctx.People.Find(iPersonID)
From this source: Querying/Finding Entities
"The Find method on DbSet uses the primary key value to attempt to find
an entity tracked by the context. If the entity is not found in the
context then a query will be sent to the database to find the entity
there. Null is returned if the entity is not found in the context or
in the database.
Find is different from using a query in two significant ways:
A round-trip to the database will only be made if the entity with the
given key is not found in the context. Find will return entities that
are in the Added state. That is, Find will return entities that have
been added to the context but have not yet been saved to the database."
And now if you want to make that change and because you haven't repeated yourself anywhere you only have to change the code in the GetPerson method.
P.S. the code for getting a record will probably look like something like this when you finally implement that generic repository.
T e = Context.Set<T>().Find(id)
One line to get them all
try using
xxx.xxx.SelectSingleOrDefault(c => c.AccountSenderID == userId_int)
replaces the use of anonymous lambda expressions (using var for instance)
xxx.xxx.Select(c => new { c.FriendInvitationID,c.AccountSenderID,
c.Account1.AccountID, c.Account1.FirstName, c.Account1.LastName, c.Account1.Email,
c.FriendInvitationStatus, c.CreationDate })
.Where(c => c.AccountSenderID == userId_int).ToList();
you dont have to describe your object even though anonymous is more dynamic in that sense (image you want to retrieve a json object with two dfferent references of the same table, in that case you must declare fields because they will have same names, just a though)
I am currently working on Windows Store App in c#.
Now,
I am having a list box 'Listbox1' which gets its items on a button click event from a text box 'tasks', and have selected Items delete property on other button click event.
private void add_Click(object sender, RoutedEventArgs e)
{
string t;
t = tasks.Text;
if (t != "")
{
Listbox1.Items.Add(t);
}
else
{
var a = new MessageDialog("Please Enter the Task First");
a.Commands.Add(new UICommand("Ok"));
a.ShowAsync();
}
tasks.Text = "";
}
private void del_Click(object sender, RoutedEventArgs e)
{
for (int p = 0; p < Listbox1.SelectedItems.Count; p++)
{
Listbox1.Items.Remove(Listbox1.SelectedItems[p].ToString());
p--;
}
}
Now I want to save this list into local application storage, after user complete the changes (on a button click event perhaps).
And also to send all Listbox Items to another page(s).
I am not much a coder, I design things.
Please guide me by sample or reference.
Thank you in advance :)
If you have already stored the data to local storage, you could just read it in the OnNavigatedTo override of the other page. Otherwise, use the navigation parameter: http://social.msdn.microsoft.com/Forums/windowsapps/en-US/8cb42356-82bc-4d77-9bbc-ae186990cfd5/passing-parameters-during-navigation-in-windows-8
Edit: I am not sure whether you also need some information about local storage. This is easy: Windows.Storage.ApplicationData.Current.LocalSettings has a property called Values, which is a Dictionary you can write your settings to. Have a look at http://msdn.microsoft.com/en-us/library/windows/apps/hh700361.aspx
Edit: Try something like this code to store your list.
// Try to get the old stuff from local storage.
object oldData = null;
ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
bool isFound = settings.Values.TryGetValue("List", out oldData);
// Save a list to local storage. (You cannot store the list directly, because it is not
// serialisable, so we use the detours via an array.)
List<string> newData = new List<string>(new string[] { "test", "blah", "blubb" });
settings.Values["List"] = newData.ToArray();
// Test whether the saved list contains the expected data.
Debug.Assert(!isFound || Enumerable.SequenceEqual((string[]) oldData, newData));
Note, this is only demo code for testing - it does not make real sense...
Edit: One advice: Do not persist the list in your click handlers as this will become extremely slow as the list grows. I would load and save the list in the Navigation handlers, i.e. add something like
protected override void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
if (this.ListBox1.ItemsSource == null) {
object list;
if (ApplicationData.Current.LocalSettings.Values.TryGetValue("List", out list)) {
this.ListBox1.ItemsSource = new List<string>((string[]) list);
} else {
this.ListBox1.ItemsSource = new List<string>();
}
}
}
protected override void OnNavigatedFrom(NavigationEventArgs e) {
if (this.ListBox1.ItemsSource != null) {
ApplicationData.Current.LocalSettings.Values["List"] = this.ListBox1.ItemsSource.ToArray();
}
base.OnNavigatedFrom(e);
}
Here is very nice simple example on SQLite DataBase Use in winRT app Development. Look at it and you will know how you can store your Data on the Local Machine. I learned Basic code from this example.
http://blogs.msdn.com/b/robertgreen/archive/2012/11/13/using-sqlite-in-windows-store-apps.aspx
Now, for ease of navigation let me suggest you a flow for this portion of your app.
take one ObservableCollection<> of string and store values of
that textBox into this ObservationCollection with onClick() and then
refer that ObservableCollection<String> to the ItemsList of the
listBox.
now at the time you need to send your Data to the next page, make one parameterised constructor of next page and pass that ObservableCollection<String> as it's parameter.
Now you can access those Data in your constructor and can use as however you want.
Hope this will help..
I'm trying to create a page where you get an overview of the sponsors of the project, the data is fetched from the database with the following service:
[OperationContract]
public IEnumerable<Sponsor> getSponsors()
{
var query = (from p in dc.Sponsors select p);
IEnumerable<Sponsor> i = query;
return i;
}
When I put my breakpoint on the i I can see that the data is correctly in there.
In my Sponsorspage I do the following
public partial class Sponsorspage : UserControl
{
IEnumerable<Sponsor> sponsors = null;
public Sponsorspage()
{
SponsorsServiceClient client = new SponsorsServiceClient();
client.getSponsorsCompleted +=new EventHandler<getSponsorsCompletedEventArgs>(client_getSponsorsCompleted);
client.getSponsorsAsync();
InitializeComponent();
}
void client_getSponsorsCompleted(object sender, getSponsorsCompletedEventArgs e)
{
if (e.Error != null)
MessageBox.Show(e.Error.ToString());
else
{
sponsors = e.Result;
foreach (Sponsor s in sponsors)
{
SponsorView control = new SponsorView(s.tekst);
SLWrapPanel.Children.Add(control);
}
}
}
For each sponsor in the database, I create the Sponsorview to which I give the source and text. You can see the code for my Sponsorview here.
public partial class SponsorView : UserControl
{
public SponsorView(string tekst)
{
txtSponsor.Text = tekst;
//Uri uri = new Uri(imageSource, UriKind.Relative);
//ImageSource imgSource = new BitmapImage(uri);
//imgSponsor.Source = imgSource;
InitializeComponent();
}
}
But when I run the page, I get the following error:
Object reference not set to an instance of an object.
at OndernemersAward.Views.SponsorView..ctor(String tekst)
at OndernemersAward.Views.Sponsorspage.client_getSponsorsCompleted(Object sender, getSponsorsCompletedEventArgs e)
at OndernemersAward.SponsorsServiceReference.SponsorsServiceClient.OngetSponsorsCompleted(Object state)
What I'm trying to do is give information (here string tekst) from the sponsor s to my user control, which it then uses to fill a textblock. Am I doing this wrong or?
Thanks! :)
Well, you're trying to iterate over results that you are supposed to hold in sponsors variable. However, please note that you're calling asynchronus version (and the only one available in Silverlight, as I recall) of getSponsors method. What it means is, you will not get results immediately after calling service method, but instead you need to wait until event with completed execution will be called.
I don't know why such thing could create some problems with debug, but it's definitely error in code that could result in problems with showing the page.
Here is very simple example on how you should retrieve result from service. Hope this will help you notice error in your approach.
I'm beginner with C# and wp7 platform and I have some problem with good idea to get request from web service.
I made webservice in PHP (nusoap - WSDL) and everything is working fine in "normal" using.
Now I have ObservableCollection saved in IsolatedStorage with I load when Page is open (List of watched stacks exchange). Then I want to refresh data for every item from web service.
I don't know whether this is a good idea.
Code:
private GPWWebservicePortTypeClient client = new GPWWebservicePortTypeClient();
private ObservableCollection<WebServiceClass.ItemGetValues> StoredStock =
new ObservableCollection<WebServiceClass.ItemGetValues>();
public const string _fileName = "listaObserwowanych.xml";
public Page()
{
InitializeComponent();
DataContext = App.ViewModel;
this.Loaded += new RoutedEventHandler(Page_Loaded);
client.GetLastValueCompleted +=
new EventHandler<GetLastValueCompletedEventArgs>(client_GetLastValueCompleted);
foreach (var itemGetValuese in App.ViewModel.Items)
{
client.GetLastValueAsync(itemGetValuese.name);
}
var o =
Observable.FromEvent<GetLastValueCompletedEventArgs(client,"GetLastValueCompleted")
.Subscribe(setList);
}
void client_GetLastValueCompleted(object sender, GetLastValueCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(Convert.ToString(e.Error));
}
else
{
ObservableCollection<WebServiceClass.ItemGetValues> ListValues =
(ObservableCollection<WebServiceClass.ItemGetValues>)
JsonConvert.DeserializeObject(e.Result,
typeof(ObservableCollection<WebServiceClass.ItemGetValues>));
StoredStock.Add(ListValues[0]);
}
}
private void setList(IEvent<GetLastValueCompletedEventArgs> ex)
{
List.ItemsSource = StoredStock;
}
void Page_Loaded(object sender, RoutedEventArgs e)
{
App.ViewModel.LoadData();
List.ItemsSource = App.ViewModel.Items;
}
Like u see I use RX to call method client_GetLastValueCompleted add store result to auxiliary variable (StoredStock). Then refresh List in setList method, but that method is client_GetLastValueCompleted what is not soo good idea, becouse I need to run that method only when all of runned GetLastValueAsync in foreach is completed.
Second problem: becouse of async web service method StoredStock sometime have different order than App.ViewModel.Items .
Any good idea how to do that in right way?
Best regards,
Lukas
You're really mixing up a number of ways to call web services and Rx. You really need to decide on a single way and stick to it.
If you're going to use Rx, then you'll have something like this:
public Page()
{
InitializeComponent();
DataContext = App.ViewModel;
this.Loaded += new RoutedEventHandler(Page_Loaded);
}
void Page_Loaded(object sender, RoutedEventArgs e)
{
App.ViewModel.LoadData();
var storedStock =
new ObservableCollection<WebServiceClass.ItemGetValues>();
List.ItemsSource = storedStock;
var values =
Observable.Using<WebServiceClass.ItemGetValues, GPWWebservicePortTypeClient>
(() => new GPWWebservicePortTypeClient(), ws =>
{
var clientGetLastValue = Observable
.FromAsyncPattern<string, GetLastValueResponse>
(ws.BeginGetLastValue, ws.EndGetLastValue);
Func<string, WebServiceClass.ItemGetValues> deserializeFirst = r =>
((List<WebServiceClass.ItemGetValues>)JsonConvert
.DeserializeObject(r,
typeof(List<WebServiceClass.ItemGetValues>)))
.First();
return
from item in App.ViewModel.Items
from e in clientGetLastValue(item)
select deserializeFirst(e.Result);
});
values.Subscribe(storedStock.Add);
}
You'll have to get the right method call names for your web service client, but the code should roughly be right. Let me know how you go.
I corrected the code above. Should have returned the query inside the Using call rather than assign it to values.
I corrected the call to FromAsyncPattern to use the correct method names and return type from the actual web service reference class sent via email.
It should look like this:
Observable.FromAsyncPattern<string, GetLastValueResponse>
(ws.BeginGetLastValue, ws.EndGetLastValue);
If you're a beginner with C#, try to avoid RX for the time being. It is a cool technology, but if you use it without clear understanding of what is going on, it will bring more problems than solve.
Use a simple event, and when each async item arrives, locate and update the correspondent one in the stored list.
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).