.NET DataSet not updating with new row - c#

I am trying to add a new typed row in a DataTable and update it to the source but its not working
public partial class JobTableAdapter
{
public OpenMassSenderCore.OpenMassSenderDBDataSet.JobRow getNewRow()
{
return OpenMassSenderDBDataSet.getInstance().Job.NewJobRow();
}
public void submitRow(OpenMassSenderCore.OpenMassSenderDBDataSet.JobRow row)
{
OpenMassSenderDBDataSet.getInstance().Job.Rows.Add(row);
OpenMassSenderDBDataSet.getInstance().Job.AcceptChanges();
Update(OpenMassSenderDBDataSet.getInstance().Job);//thats the default tableadapter's update
}
private static JobTableAdapter instance;
public static JobTableAdapter getInstance()
{
if (instance == null) instance = new JobTableAdapter();
return instance;
}
}
private void button1_Click(object sender, EventArgs e)
{
OpenMassSenderCore.OpenMassSenderDBDataSet.JobRow job=JobTableAdapter.getInstance().getNewRow();
job.ID = 2;
job.query = "";
job.group = "smaplist1";
job.sender_account = 1;
job.status = OpenMassSenderCore.OpenMassSenderDBDataSet.JobRow.JobStatus.PENDING;
job.user = 1;
job.message =1;
JobTableAdapter.getInstance().submitRow(job);
}
If I press the button twice I get the key(ID) existing exception so this part works ok, the only problem is that its not updating the database(access)

Well, duh, you called AcceptChanges.
You basically said "Ok, this data is now committed, everything is fine. By the way, DataAdapter, can you commit all the changes on this dataset to the database? Thanks." "Sure, let me see... okay, your data set has no changes, so I'm done". You've thrown away the only information the data adapter can use to actually update the database :)
(As a side note - please, don't write Java in C#. It hurts. And DataSets are an ancient technology that wasn't updated in years, you might want to find something a bit more uptodate.)

Related

Clearing MongoDb keys on IIS website

New to IIS and Mongo, and I'm trying to find a way to clear the keys from my server to avoid an "item with the same key has already been added" exception.
IMongoDatabase _db;
IMongoCollection<BoardStorageItem> _boardsCollection;
public MongoDb()
{
var client = new MongoClient("mongodb://localhost");
_db = client.GetDatabase("KanbanDemonstration");
BsonClassMap.RegisterClassMap<CardStorageItem>();
BsonClassMap.RegisterClassMap<ColumnStorageItem>();
BsonClassMap.RegisterClassMap<BoardStorageItem>();
BsonClassMap.RegisterClassMap<EmployeeStorageItem>();
_boardsCollection = _db.GetCollection<BoardStorageItem>("Board");
}
public BoardStorageItem GetBoardByName(string name)
{
var board = _boardsCollection.AsQueryable().FirstOrDefault(b => b.Name == name);
return board;
}
public class MongoConverter
{
MongoDb _mongoDb;
public MongoConverter()
{
_mongoDb = new MongoDb();
}
public BoardStorageItem GetBoardByName(string name)
{
return _mongoDb.GetBoardByName(name);
}
}
and then for the code on the web page itself
protected void Button1_Click(object sender, EventArgs e)
{
_mongoConverter = new MongoConverter();
var board = _mongoConverter.GetBoardByName(TextBox1.Text);
BoardName = board.Name;
BoardId = board.Id;
Label3.Text = BoardName;
Label4.Text = BoardId;
Session.Clear();
}
This works perfectly this first time I use the button to get a board, but if I try a second time, I get an exception "item with the same key has already been added" when attempting to new up MongoConverter. I had thought that clearing the session after would clear out the keys as well, but the only thing that seems to work is resetting the server itself.
Calling BsonClassMap.RegisterClassMap<T>() should only be done once per type. And it should be done at application startup, before opening any database connections according to the documentation.
Since you're in ASP.NET, put this in your Application_Start event in your global application class (global.asax)
void Application_Start(object sender, EventArgs e)
{
BsonClassMap.RegisterClassMap<CardStorageItem>();
BsonClassMap.RegisterClassMap<ColumnStorageItem>();
BsonClassMap.RegisterClassMap<BoardStorageItem>();
BsonClassMap.RegisterClassMap<EmployeeStorageItem>();
}
You can make another method that calls it so you don't pollute your global application class if you want. And of course, remove the registrations from your constructor.

Keep DbContext open for DataGridView

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

C# Take combobox item from one form and add its name as text to another

Ok so I'm attempting to create a simple game. In a nutshell it's a resource management game where the player will attempt to manage a thieves guild. In regards to running missions I've created a Thief class, a new instance of which is created when a new thief is recruited. I have coded within the thief class the ability to gain experience and level up.
Here's my specific problem:
I want the player to be able to select which thief/thieves to send on a mission. I have thought about it and figured that opening a new form and populating it with checkboxes is the easiest way to allow this. These checkboxes will be related to a List<thief> of thieves, the player then checks the thieves s/he wants to send and these are then stored in another List<thief> and passed on to the run mission function.
I've built a separate project with the intention of testing and playing around with this before putting it into the main program. The test project consists of two forms: The first (frmMain) with a textbox to hold the selected options and a button to open the second form (frmSelect). Currently I can open and populate the second form (frmSelect) but when I try to add the checked options to the textbox I simply...well can't.
So far I have tried directly accessing the textbox by typing frmMain.txtOptionsDisplay in the cs file of frmSelect but it causes the following error:
An object reference is required for the non-static field, method or
property
I tried to create a new form in frmSelect and make it equal to the active instance of frmMain with: Form frmTemp = frmMain.ActiveForm; and then alter the textbox using frmTemp as a go-between but that produced the error:
'System.Windows.Forms.Form' does not contain a definition for
'txtOptionsDisplay'.
Having searched both google and stackoverflow forums I've encountered answers that I either have never heard of (Threading) or answers that I kind've recognise but can't interpret the code pasted to make it relevant to my problem (delegates).
Any advice or pointers would be fantastic.
EDIT:
frmMain code:
public frmMain()
{
InitializeComponent();
selections.Add("Option 1");
selections.Add("Option 2");
}
private void btnClick_Click(object sender, EventArgs e)
{
frmSelectOptions.Show();
int length = selections.Count();
for (int i = 0; i < length; i++)
{
CheckBox box = new CheckBox();
box.Text = selections[i];
box.AutoSize = true;
box.Location = new Point(50, 50*(i+1));
frmSelectOptions.grpControls.Controls.Add(box);
}
}
public void updateText(string option)
{
txtOptionsDisplay.Text += option;
}
}
frmSelect code:
public List<CheckBox> selectedOptions = new List<CheckBox>();
Form frmTemp = frmMain.ActiveForm;
public frmSelect()
{
InitializeComponent();
}
private void btnSelect_Click(object sender, EventArgs e)
{
foreach (CheckBox box in grpControls.Controls)
{
if (box.Checked == true)
selectedOptions.Add(box);
}
this.Hide();
}
}
I hope this formats correctly... I'm kinda new and don't know how to indent. Oh look there's a preview...
Does this help?
Your problem is that controls defined within a form by default receive the private access identifier. Hence you could just add a property along the lines of
public ControlType ProxyProperty {
get {
return txtOptionsDisplay;
}
}
Besides from that you should think about wether what you're trying is actually a good solution. Manipulating forms from one to another will become a huge clusterfuck in terms of maintenance later on.
I'd suggest using the Singleton pattern for your frmMain. This will help safeguard you from accidentally launching another instance of frmMain and at the same time, will give you access to frmMain's objects. From there, you can either write accessors to Get your txtOptionsDisplay or you can make it public. Below is an example:
public class frmMain
{
private static frmMain Instance = null;
private static object LockObj = new object();
public static frmMain GetMain()
{
// Thread-safe singleton
lock(LockObj)
{
if(Instance == null)
Instance = new frmMain();
return Instance;
}
}
public string GetOptionsDisplayText()
{
return txtOptionsDisplay.Text;
}
}
public class frmSelect
{
private void frmSelect_Load(object sender, EventArgs e)
{
// Set whatever text you want to frmMain's txtOptionsDisplay text
txtDisplay.Text = frmMain.GetMain().GetOptionsDisplayText();
}
}
If you do go this route, don't forget to update Program.cs to use frmMain's singleton.
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// Application.Run(new frmMain()); - Old method
Application.Run(frmMain.GetMain());
}

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 do I safely populate with data and Refresh() a DataGridView in a multi-threaded application?

My app has a DataGridView object and a List of type MousePos. MousePos is a custom class that holds mouse X,Y coordinates (of type "Point") and a running count of this position. I have a thread (System.Timers.Timer) that raises an event once every second, checks the mouse position, adds and/or updates the count of the mouse position on this List.
I would like to have a similar running thread (again, I think System.Timers.Timer is a good choice) which would again raise an event once a second to automatically Refresh() the DataGridView so that the user can see the data on the screen update. (like TaskManager does.)
Unfortunately, calling the DataGridView.Refresh() method results in VS2005 stopping execution and noting that I've run into a cross-threading situation.
If I'm understanding correctly, I have 3 threads now:
Primary UI thread
MousePos List thread (Timer)
DataGridView Refresh thread (Timer)
To see if I could Refresh() the DataGridView on the primary thread, I added a button to the form which called DataGridView.Refresh(), but this (strangely) didn't do anything. I found a topic which seemed to indicate that if I set DataGridView.DataSource = null and back to my List, that it would refresh the datagrid. And indeed this worked, but only thru the button (which gets handled on the primary thread.)
So this question has turned into a two-parter:
Is setting DataGridView.DataSource to null and back to my List an acceptable way to refresh the datagrid? (It seems inefficient to me...)
How do I safely do this in a multi-threaded environment?
Here's the code I've written so far (C#/.Net 2.0)
public partial class Form1 : Form
{
private static List<MousePos> mousePositionList = new List<MousePos>();
private static System.Timers.Timer mouseCheck = new System.Timers.Timer(1000);
private static System.Timers.Timer refreshWindow = new System.Timers.Timer(1000);
public Form1()
{
InitializeComponent();
mousePositionList.Add(new MousePos()); // ANSWER! Must have at least 1 entry before binding to DataSource
dataGridView1.DataSource = mousePositionList;
mouseCheck.Elapsed += new System.Timers.ElapsedEventHandler(mouseCheck_Elapsed);
mouseCheck.Start();
refreshWindow.Elapsed += new System.Timers.ElapsedEventHandler(refreshWindow_Elapsed);
refreshWindow.Start();
}
public void mouseCheck_Elapsed(object source, EventArgs e)
{
Point mPnt = Control.MousePosition;
MousePos mPos = mousePositionList.Find(ByPoint(mPnt));
if (mPos == null) { mousePositionList.Add(new MousePos(mPnt)); }
else { mPos.Count++; }
}
public void refreshWindow_Elapsed(object source, EventArgs e)
{
//dataGridView1.DataSource = null; // Old way
//dataGridView1.DataSource = mousePositionList; // Old way
dataGridView1.Invalidate(); // <= ANSWER!!
}
private static Predicate<MousePos> ByPoint(Point pnt)
{
return delegate(MousePos mPos) { return (mPos.Pnt == pnt); };
}
}
public class MousePos
{
private Point position = new Point();
private int count = 1;
public Point Pnt { get { return position; } }
public int X { get { return position.X; } set { position.X = value; } }
public int Y { get { return position.Y; } set { position.Y = value; } }
public int Count { get { return count; } set { count = value; } }
public MousePos() { }
public MousePos(Point mouse) { position = mouse; }
}
You have to update the grid on the main UI thread, like all the other controls. See control.Invoke or Control.BeginInvoke.
UPDATE! -- I partially figured out the answer to part #1 in the book "Pro .NET 2.0 Windows Forms and Customer Controls in C#"
I had originally thought that Refresh() wasn't doing anything and that I needed to call the Invalidate() method, to tell Windows to repaint my control at it's leisure. (which is usually right away, but if you need a guarantee to repaint it now, then follow up with an immediate call to the Update() method.)
dataGridView1.Invalidate();
But, it turns out that the Refresh() method is merely an alias for:
dataGridView1.Invalidate(true);
dataGridView1.Update(); // <== forces immediate redraw
The only glitch I found with this was that if there was no data in the dataGridView, no amount of invalidating would refresh the control. I had to reassign the datasource. Then it worked fine after that. But only for the amount of rows (or items in my list) -- If new items were added, the dataGridView would be unaware that there were more rows to display.
So it seems that when binding a source of data (List or Table) to the Datasource, the dataGridView counts the items (rows) and then sets this internally and never checks to see if there are new rows/items or rows/items deleted. This is why re-binding the datasource repeatedly was working before.
Now to figure out how to update the number of rows to display in dataGridView without having to re-bind the datasource... fun, fun, fun! :-)
After doing some digging, I think I have my answer to part #2 of my question (aka. safe Multi-threading):
Rather than using System.Timers.Timer, I found that I should be using System.Windows.Forms.Timer instead.
The event occurs such that the method that is used in the Callback automatically happens on the primary thread. No cross-threading issues!
The declaration looks like this:
private static System.Windows.Forms.Timer refreshWindow2;
refreshWindow2 = new Timer();
refreshWindow2.Interval = 1000;
refreshWindow2.Tick += new EventHandler(refreshWindow2_Tick);
refreshWindow2.Start();
And the method is like this:
private void refreshWindow2_Tick(object sender, EventArgs e)
{
dataGridView1.Invalidate();
}
Looks like you have your answer right there!
Just in cawse you're curious about how to do cross thread calls back to ui:
All controls have a Invoke() method (or BEginInvoke()- in case you want to do things asynchronously), this is used to call any method on the control within the context of the main UI thread.
So, if you were going to call your datagridview from another thread you would need to do the following:
public void refreshWindow_Elapsed(object source, EventArgs e)
{
// we use anonymous delgate here as it saves us declaring a named delegate in our class
// however, as c# type inference sometimes need a bit of 'help' we need to cast it
// to an instance of MethodInvoker
dataGridView1.Invoke((MethodInvoker)delegate() { dataGridView1.Invalidate(); });
}

Categories