Keep DbContext open for DataGridView - c#

I have a DataGridView and the DataGridView's DataSource is a BindingList I got from the Entity Framework (V6) via context.Person.Local.ToBindingList().
After I set the DataSource to this BindingList, I dispose the context, because I read that keeping the context open would be bad practice.
So, if I wanted to add a new row, I would click on the "add" button that comes with the BindingNavigator that got created when I dragged the "people" object data source to my Windows Form.
Every time I click the "add" button, I get an exception that tells me that the context has been disposed.
Do I need to keep the context open all the time when using DataGridView? Oh and: the DataSource might change during runtime depending on the selection of a ListBox Item.
Also, when the context has been disposed and I edited one row from the DataGridView, how could I find out (after multiple changes) which row has changed?
I tried to do:
foreach(DataGridViewRow row in peopleDataGridView.Rows)
{
People item = (People)row.DataBoundItem;
if (item != null)
{
db.People.Attach(item);
}
}
db.SaveChanges();
...but SaveChanges() did not recognize any changes. However, if I force every attached item to a "modified" state, it works. But I do not want to change 100 items to "modified", if only one got actually modified.
Any ideas?
EDIT 1
Oh well, so I changed my code to keep the context open all the time (or at least as long as the form gets displayed).
Now, I ran into a different problem (people may have many jobs):
private void listBox1_SelectedValueChanged(object sender, EventArgs e)
{
People p = (People)listBox1.SelectedItem;
if(p != null)
{
//jobBindingSource.Clear(); this caused another error at runtime...
db.Entry(p).Collection(b => b.Job).Load();
jobBindingSource.DataSource = db.Job.Local.ToBindingList();
}
}
The DataGridView that is bound to this jobBindingSource instance shows the correct jobs for a person, but in addition to the jobs from the previously selected person. I tried to Clear() the entries, but if I do this and click on the same person twice, the datagridview starts to sometimes show no entries at all. A strange behaviour.
What am I doing wrong now?
EDIT 2
Okay... I found a solution myself. But I refuse to accept that this is the correct way to do it:
private void listBox1_SelectedValueChanged(object sender, EventArgs e)
{
People p = (People)listBox1.SelectedItem;
if(p != null)
{
db.Dispose();
db = new PeopleJobsEntities();
db.People.Attach(p);
db.Entry(p).Collection(person => person.Job).Load();
jobBindingSource.DataSource = db.Job.Local.ToBindingList();
}
}
Only if I dispose the context and open it anew, the whole thing works. The reason is that if I clear the local cache (of db.Job.Local), its entries will not be reloaded again even if I use the Load() method. Is there some way to force the reloading of entities?

While I try not to keep the DBContext open for a long period of time, with datagrids you don't have much choice. I set my grid's DataSource property to IQueryable<T> and then all the edits, deletes and additions are taken care of by the grid and context itself. You just have to call dbContext.SubmitChanges() whenever you want to persist the changes. You can save each time a user leaves a row by saving on the RowLeave or the RowValidated event. Or you can save when you close the form. But also make sure you call dbContext.Dispose() when you close the form as well.
To find out which rows change you can view the ChangeSet that is returned by doing the following:
var changes = dbContext.GetChangeSet();
dbContext.SubmitChanges();

Be sure if your item is not null.
Check your connection string.
And, try this :
db.People.Add(item);
Instead of :
db.People.Attach(item);

Ok, thanks to #jaredbaszler I came up with this solution that works fine for me.
I decided to keep the DbContext alive all the time. To clear the local cache, I detached every entity inside in a loop. I think this is a very disgusting way to do it. There must be a better way...
This is what I have:
PeopleJobsEntities db;
public FormTest()
{
InitializeComponent();
db = new PeopleJobsEntities();
db.Database.Log = Console.Write;
db.People.Load();
List<People> peoplelist = db.People.Local.ToList();
listBox1.DataSource = peoplelist;
}
private void FormTest_FormClosing(object sender, FormClosingEventArgs e)
{
if (db != null)
db.Dispose();
}
private void listBox1_SelectedValueChanged(object sender, EventArgs e)
{
People p = (People)listBox1.SelectedItem;
if(p != null)
{
List<Job> oldlist = db.Job.Local.ToList();
foreach (Job j in oldlist)
{
db.Entry(j).State = EntityState.Detached;
}
db.Entry(p).Collection(b => b.Job).Load();
jobBindingSource.DataSource = db.Job.Local.ToBindingList();
}
}
private void jobBindingNavigatorSaveItem_Click(object sender, EventArgs e)
{
foreach(DataGridViewRow row in jobDataGridView.Rows)
{
if(row != null && row.DataBoundItem != null)
{
Job j = (Job)row.DataBoundItem;
if(db.Entry(j).State == EntityState.Added)
{
if(j.People.Count == 0)
{
People people = (People)listBox1.SelectedItem;
if (people != null)
j.People.Add(people);
}
}
}
}
db.SaveChanges();
}
Editing entries works
Adding new entries works
Deleting entries works

Related

Performance of refreshing listview

I have a ListView-Control which holds several entries (about 300). When I open it, it starts up pretty quick. I do some things and call the btnRefresh_Click-Button event to fill data into the ListView.
public ListWords()
{
InitializeComponent();
lvwColumnSorter = new ListViewColumnSorter();
this.lstWords.Visible = true;
this.btnCheckLinks.Visible = true;
this.lstWords.ListViewItemSorter = lvwColumnSorter;
btnRefresh_Click(null, new EventArgs());
}
The event does this:
public void btnRefresh_Click(object sender, EventArgs e)
{
lstWords.Items.Clear();
foreach (var word in WordData.AllWords)
{
string[] lvi = {
word.Id.ToString(), word.Name, word.Link,
word.Status, word.Count.ToString()
};
lstWords.Items.Add(new ListViewItem(lvi));
}
}
So far so good. But if I actually PRESS the button to manually refresh the ListView, it takes around 4 to 5 seconds! I'ts even faster if I close the form and open a new one, then the refreshed data appears in an instant.
What is the cause for this behaviour? I can't figure out what the difference is between calling it programmatically in the constructor and calling it manually by a button press event. Thanks in advance!

CollectionViewSource.View not refreshing at end points

I'm running into a problem with my CollectionViewSource.
I'm stopping the user to go BEYOND the the items within the CollectionViewSource to prevent my code from falling over.
I have this:
private CollectionViewSource _source = new CollectionViewSource();
private List<Items> _itemLists;
private MyDbContext _context;
Constructor()
{
_itemLists = _context.Items.ToList();
_source.Source = _itemLists;
_source.View.CurrentChanged += View_CurrentChanged;
}
private void View_CurrentChanged(object sender, EventArgs e)
{
if (_source.View.IsCurrentBeforeFirst)
{
_source.View.MoveCurrentToFirst();
_source.View.Refresh();
return;
}
if (_source.View.IsCurrentAfterLast)
{
_source.View.MoveCurrentToLast();
_source.View.Refresh();
return;
}
// ... Do some other work here ...
}
I had to put in a return statement in each if blocks to prevent the View.CurrentItem from being NULL.
This works great from preventing a Null exception being thrown, but the strange behavior is that when I try to call View.MoveCurrentToNext() after the user tries to go beyond the first record (IsCurrentViewBeforeFirst), nothing happens. You have to call it again in order for the View to be updated to the next record after the first one.
Inversely, the same behavior goes for the IsCurrentViewAfterLast. You have to call View.MoveCurrentToPrevious() twice before the View shows the previous record after the last one when the user tries to go beyond the last record.
So then how can this be fixed so I don't need to call the methods twice?
You can prevent event bubbling in WPF like below:
e.Handled = true;
I think your CollectionViewSource object inherited from an IEnumerable object. Which has a member as Index you can hide your object in a session or any static container (for example Application.Current.Resources in wpf) so you can update just your index while user click next or before buttons.
If your object doesnt inherited from IEnumerable i suggest to this also
Cheer codding

Why isn't my entity framework deleting my record?

New to entity framework, but I would think this should be pretty simple.
My form load creates a context from my Entities. I create a list of clients and have a binding source that I assign clients to. The binding source is assigned to a Binding Navigator - clientBindingNavigator.
private void ClientExtForm_Load (object sender, EventArgs e)
{
_context = new IDVisitorEntities ();
List<IDVM.Client> clients = _context.Clients.ToList ();
clientBindingSource.DataSource = clients;
}
excerpt from ClientExtForm.Designer.cs
//
// clientBindingNavigator
//
this.clientBindingNavigator.AddNewItem = this.bindingNavigatorAddNewItem;
this.clientBindingNavigator.BindingSource = this.clientBindingSource;
this.clientBindingNavigator.CountItem = this.bindingNavigatorCountItem;
this.clientBindingNavigator.DeleteItem = this.bindingNavigatorDeleteItem;
this.clientBindingNavigator.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.bindingNavigatorMoveFirstItem,
this.bindingNavigatorMovePreviousItem,
this.bindingNavigatorSeparator,
this.bindingNavigatorPositionItem,
this.bindingNavigatorCountItem,
this.bindingNavigatorSeparator1,
this.bindingNavigatorMoveNextItem,
this.bindingNavigatorMoveLastItem,
this.bindingNavigatorSeparator2,
this.bindingNavigatorAddNewItem,
this.bindingNavigatorDeleteItem,
this.clientBindingNavigatorSaveItem});
When I click the Delete Button on the navigator tool bar the the ClientBindingSource.Count has been reduced by 1 .
private void clientBindingNavigatorSaveItem_Click (object sender, EventArgs e)
{
this.OnSave ();
}
public override void OnSave ()
{
foreach (ObjectStateEntry entry in _context.ObjectStateManager.GetObjectStateEntries (EntityState.Deleted))
{
// nothing shows up in this
}
foreach (ObjectStateEntry entry in _context.ObjectStateManager.GetObjectStateEntries (EntityState.Modified))
{
// when modified
}
foreach (ObjectStateEntry entry in _context.ObjectStateManager.GetObjectStateEntries (EntityState.Added))
{
// when adding this finds it
}
clientBindingSource.EndEdit ();
visitorHostsBindingSource.EndEdit ();
_context.SaveChanges ();
base.OnSave ();
}
It appears as though the navigator is removing the item from the collection.
Added info: It appears that in the navigator the DeleteItem button corresponds to the RemoveCurrent method (on click event calls it). Not sure how to tie in before the RemoveCurrent does it's thing.
What are my options for preforming the delete?
Removing an item from the clientBindingSource will have no effect on the item at the database level. You have to explicitly call _context.Clients.DeleteObject(deletedClient); You perform must all CRUD operations through the ObjectContext.
After looking around found some blogs that suggest to not use the default DeleteItem.
this.clientBindingNavigator.DeleteItem = null;//= this.bindingNavigatorDeleteItem;
In my case to make it clear for the BindingNavigator I replaced this.bindingNavigatorDeleteItem with a new button this.toolStripButton1 in the Items list.
this.clientBindingNavigator.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.bindingNavigatorMoveFirstItem,
this.bindingNavigatorMovePreviousItem,
this.bindingNavigatorSeparator,
this.bindingNavigatorPositionItem,
this.bindingNavigatorCountItem,
this.bindingNavigatorSeparator1,
this.bindingNavigatorMoveNextItem,
this.bindingNavigatorMoveLastItem,
this.bindingNavigatorSeparator2,
this.bindingNavigatorAddNewItem,
this.toolStripButton1,
this.clientBindingNavigatorSaveItem});
Creation of the new button looks like this:
//
// toolStripButton1
//
this.toolStripButton1.Image = ((System.Drawing.Image) (resources.GetObject ("bindingNavigatorDeleteItem.Image")));
this.toolStripButton1.RightToLeftAutoMirrorImage = true;
this.toolStripButton1.Name = "toolStripDeleteItem";
this.toolStripButton1.Size = new System.Drawing.Size(23, 22);
this.toolStripButton1.Text = "Delete";
this.toolStripButton1.Click += new System.EventHandler(this.toolStripButton1_Click);
The Click event then calls the RemoveCurrent (just like the default does) but I can get the current entity and stash it in an arrraylist for use on save.
private void toolStripButton1_Click (object sender, EventArgs e)
{
var currentclient = (Client) clientBindingSource.Current;
clientstodelete.Add (currentclient);
clientBindingSource.RemoveCurrent ();
}
I didn't need to create a new button, I just needed to have the this.clientBindingNavigator.DeleteItem not tied to a button. Because the DeleteItem creates a click event under the hood that calls the BindingSource.RemoveCurrent(). I might change the button back to the default one created but for illustration wanted everyone to see what was happening.
I agree it seemed odd to have to delete the record from the table directly if I had just deleted using RemoveCurrent(). But it is what it is... This took care of the record in the datagridview and the datasource in one clean sweep.
Here is how I solved the problem:
t_StaffDaysOff sdo = (t_StaffDaysOff)t_StaffDaysOffbindingSource.Current;
t_StaffDaysOffbindingSource.RemoveCurrent();
t_StaffDaysOffbindingSource.EndEdit();
db.t_StaffDaysOff.Remove(sdo);
db.SaveChanges();

Saving data to database using linq

I face problems with my codes about saving data into database. I have a Text box and a Combo box but when I key in data in the Text box and select data in the Combo box and click save, nothing happens and no error were found during compiling. Can I know what actually went wrong and give me some solution to it?
enter code here private void btnCreate_Click(object sender, EventArgs e)
{
using (testEntities Setupctx = new testEntities())
{
string selectST = cbSeats.SelectedItem.ToString();
string inputST = txtStation.Text;
var createStation = (from createST in Setupctx.stations
where createST.Seats == selectST
where createST.Station1 == inputST
select createST).SingleOrDefault();
if (createStation != null)
{
Setupctx.stations.AddObject(createStation);
Setupctx.SaveChanges();
txtStation.Text = "";
MessageBox.Show("New Station Has Been Created.");
}
}
}
Your help will be greatly appreciated.
I'm agreeing with #JamesD on making sure the event handler is called.
Additionally, when you get an object from a linq query and make changes to it, you need to save those changes it by calling SubmitChanges() on the DataContext. (I'm assuming that Setupctx is a DataContext object).
Read here for information on SubmitChanges()
Also, I don't know if you are using SQL or not. If so, here is a great tutorial: Linq to SQL Tutorial
You need to create a new station object like this:
if (createStation != null)
{
var obj = new Staion();
obj.Seats=selectST;
obj.Staion1=inputST;
Setupctx.Staions.Add(obj);
Setupctx.SubmitChanges();
txtStation.Text = "";
MessageBox.Show("New Station Has Been Created.");
}
More on LINQ To SQL here
This is the right way of doing it.
private void btnCreate_Click(object sender, EventArgs e)
{
using (testEntities Setupctx = new testEntities())
{
string[] stations = StationNameList();
station creStation = new station();
creStation.Station1 = txtStation.Text;
creStation.Seats = cbSeats.SelectedItem.ToString();
if (stations.Contains(txtStation.Text))
{
MessageBox.Show("This Station is already been created. Please enter a new Station.");
}
else
{
Setupctx.stations.AddObject(creStation);
Setupctx.SaveChanges();
txtStation.Text = "";
cbSeats.SelectedIndex = -1;
MessageBox.Show("New Station Has Been Created.");
}
}
}
Just to check off the list:
Have you made sure the button event handler is hooked up?
When you say
nothing happens
Do you mean the event handler is not called? You're not actually doing anything with the station you've retrieved from the database either. You're adding it back in to the stations list that you've pulled it out from.

How to stop method from calling themselves recursively?

I have two tables, employees and project, in listbox2, I show all the employees and in listbox1 all the projects, now obviously one employee can be involved in many projects and one project could have many employee. So I have this EmployeeProject that maps the many to many relation that exists. What I want is, if user click a project name in first listbox, then all employees in that project should be selected in listbox2. Also, when a user clicks a item in listbox2, (an employee) all project of which that employee is a part should be selected in listbox1
But If I use ListBox.SelectedIndexChanged event for this process, and select even a single value in listbox2 then it would trigger the SelectedIndexChagned for listbox2, and that would start working by selecting all items in listbox1 that current employee is a part of, but again, as soon as even one item in listbox1 is selected, it would fire up its SelectedIndexChanged event, and it would go on forever like this. So what's the solution of this? So far, I've done this..
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
// Load the list of employees
cmd.CommandText =
"SELECT EmpName FROM Employee WHERE EmpID IN(SELECT EmpID FROM EmployeeProject WHERE ProjectID =(SELECT ProjectID FROM Project WHERE ProjectName = '" +
listBox1.SelectedItem.ToString() + "')) ORDER BY EmpId";
var rdr = cmd.ExecuteReader();
listBox2.Items.Clear();
// right now, I am doing this to escape this recursive loop, but thats not what I want
while (rdr.Read())
{
listBox2.Items.Add(rdr.GetString(0));
}
rdr.Close();
this.AutoScroll = true;
}
private void listBox2_SelectedIndexChanged(object sender, EventArgs e)
{
// Load the list of projects
cmd.CommandText =
"SELECT ProjectName FROM Projects WHERE ProjectID IN(SELECT ProjectID FROM EmployeeProject WHERE EmpId=(SELECT EmpId FROM Employee WHERE EmpName= '" +
listBox2.SelectedItem.ToString() + "')) ORDER BY ProjectID";
var rdr = cmd.ExecuteReader();
listBox1.Items.Clear();
// again, I don't want to clear, but select all those employee in listbox1 that are involved in this selected project, but can't do it because it would cause infinite recursion of these
while (rdr.Read())
{
listBox2.Items.Add(rdr.GetString(0));
}
rdr.Close();
this.AutoScroll = true;
}
So? What should I do to achieve what I want to achieve? And how would I avoid that recursion? I know this way also works what I just showed, but I don't want to clear up and show up again, (this might confuse a simple user). I want for each selection, values corresponding to that selection be selected in other listbox (without causing recursion of course!). How do I do it?
EDIT I don't know how can I select multiple items in listbox programmatically, so if you could tell that, it would be great!
There is a design pattern called Balking, I think it applies here.
http://en.wikipedia.org/wiki/Balking_pattern
The idea is to introduce an auxiliary state variable to control the operation:
private bool doesProcessing { get; set; }
private void listBox1_SelectedIndexChanging( ... )
{
// signal the beginning of processing
if ( doesProcessing )
return;
else
doesProcessing = true;
try
{
// your logic goes here
}
finally
{
// signal the end of processing
doesProcessing = false;
}
}
and the same for listBox2.
When you change the selected items of one listbox, you could temporarily disable the events on the other. For example:
// Store the event handlers in private member variables.
private System.EventHandler selectedEmployeeChanged = new System.EventHandler(this.lbEmployees_SelectedIndexChanged);
private void lbProjects_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
// Remove your event so that updating lbEmployees doesn't cause
// lbEmployees_SelectedIndexChanged to get fired.
lbEmployees.SelectedIndexChanged -= selectedEmployeeChanged;
// other event handler logic
}
finally
{
// Ensure that the handler on lbEmployees is re-added,
// even if an exception was encountered.
lbEmployees.SelectedIndexChanged += selectedEmployeeChanged;
}
}
Note: I have renamed your listBoxes (and the associated events) to be more readable. As per your description, listBox1 is now lbEmployees and listBox2 is now lbProjects.
As for programatically selecting multiple items, if you know the index for each one, you could use the ListBox.SetSelected Method. For example:
listBox1.SetSelected(1, true);
listBox1.SetSelected(3, true);
causes listBox1 to select the items at 1 and 3.
In your case, I would recomend only using the queries to get what values to select (not clearing your listboxes and then just adding back the values that should be selected). Below is my suggestion for how you would rewrite one of your handlers:
// Store the event handlers in private member variables.
private System.EventHandler selectedEmployeeChanged = new System.EventHandler(this.lbEmployees_SelectedIndexChanged);
private void lbProjects_SelectedIndexChanged(object sender, EventArgs e)
{
// Declare this outside of try so that we can close it in finally
DbDataReader reader = null;
try
{
// Remove your event so that updating lbEmployees doesn't cause
// lbEmployees_SelectedIndexChanged to get fired.
lbEmployees.SelectedIndexChanged -= selectedEmployeeChanged;
// Deselect all items in lbEmployees
lbEmployees.ClearSelected();
cmd.CommandText = "SELECT ProjectName FROM Projects WHERE ProjectID IN(SELECT ProjectID FROM EmployeeProject WHERE EmpId=(SELECT EmpId FROM Employee WHERE EmpName= '#EmpName')) ORDER BY ProjectID";
cmd.Parameters.AddWithValue("#EmpName", lbProjects.SelectedItem.ToString());
reader = cmd.ExecuteReader();
// For each row returned, find the index of the matching value
// in lbEmployees and select it.
while (rdr.Read())
{
int index = lbEmployees.FindStringExact(rdr.GetString(0));
if(index != ListBox.NoMatches)
{
lbEmployees.SetSelected(index, true);
}
}
this.AutoScroll = true;
}
finally
{
// Ensure that the reader gets closed, even if an exception ocurred
if(reader != null)
{
reader.Close();
}
// Ensure that the handler on lbEmployees is re-added,
// even if an exception was encountered.
lbEmployees.SelectedIndexChanged += selectedEmployeeChanged;
}
}
As a side note, you may want to look into using a JOIN operator in your query string, rather than multiple nested SELECTS.
You can remove the attached event to one of the controls when you are in the other, before doing any modifications.
then reattach at the end.
Have one eventMask integer variable and use the pattern below to protect your event each other.
Using a bool as suggested in another answer is enough for this scenario. However, I prefer to use an integer rather than a bool : it can handle nested and stacked events calls correctly.
It is particularly useful when I have to deal with selection/deselection events in treeviews and listviews for example.
int eventMask = 0;
void items_PropertyChanged(object sender, PropertyChangedEventArgs e) // for example...
{
if (eventMask > 0)
return;
try
{
eventMask++;
// processing
// it happens some other events can be called. Including this one.
}
finally
{
// put this in finally, so there is no lock risk
eventMask--;
}
}
The logic of the OP code (if working as OP expect) contains a problem.
Suppose you fill the two lists with all possible values.
Then, when the user clicks on one item, the second list is emptied and filled only with the item linked to the other list. But clicking between the two list could remove forever one or more item from the other list. It is better to leave the two list filled and trying to highlight the line of the corresponding items
For example (supposing ListBox.SelectionMode = SelectionMode.MultiSimple or MultiExtended)
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
........
try
{
......
// Remove the event handler, do your selection, No event on the listbox2 will be fired....
listBox2.SelectedIndexChanged -= new System.EventHandler(this.listBox2_SelectedIndexChanged);
// Do not remove the items , instead clear previous selection
listBox2.ClearSelected();
while (rdr.Read())
{
int index = listBox2.FindString(rdr.GetString(0), -1);
if(index != -1) listBox2.SetSelected(index, true));
}
....
}
finally
{
// before exit, reapply the event handler
listBox2.SelectedIndexChanged += new System.EventHandler(this.listBox2_SelectedIndexChanged);
}
}
of course you need to be sure to reapply the event, so use a try/finally block
The same approach will be valid to stop the event firing for listBox1 when you fill it in the listBox2.SelectedIndexChanged event,

Categories