i am designing a local database in .net with wpf as gui. I have added a new database, and added a new table. Through the TableAdapter i generated 2 statements ( 1 statement is a select stmt and 1 is a insert) , i insert name and firstname (id is auto generated). It works fine, i can display the table in a datagrid (wpf toolkit) and also add new items (name,firstname), when i close and start the application everything is fine (data in table is stored) BUT when i try to preview data in my database dataset (where my Adapters exist) , no data is displayed and then the table gets deleted.. why?
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
PlayerTableAdapter objPlayerTableAdapter = new PlayerTableAdapter();
objDataGridResults.ItemsSource = objPlayerTableAdapter.GetDataAllPlayer();
}
//Button Event onClick
private void m_voidAddPlayer(object sender, System.Windows.RoutedEventArgs e)
{
PlayerTableAdapter objPlayerTableAdapter = new PlayerTableAdapter();
objPlayerTableAdapter.InsertQueryPlayer(objTextBoxPlayerName.Text.ToString(), objTextBoxPlayerFirstName.Text.ToString());
objDataGridResults.ItemsSource = objPlayerTableAdapter.GetDataAllPlayer();
}
}
the reason is that you modify not database, but dataset - the in-memory snapshot of database. When you close app it gets lost. You should call objPlayerTableAdapter.Update. And consider moving to Linq to SQL or Entity Framework, DataSets are outdated.
Related
I have bound a DataGridView to an SQL Server table in a .Net 5.0 WinForms project. Displaying the data works well.
I would like to update editions to the database as soon as I move to another row in the DataGridView. But I have not found a way to do this.
The solution presented here seems not to work with an OleDbDataAdapter. The Update method does not accept a DataRow. Examples in DOCS require a DataSet which I try to avoid. Other examples use a button to save changes.
The data gets loaded like this:
var dataAdapter = new OleDbDataAdapter(sqlQueryString, connString);
var dataTable = new DataTable();
dataAdapter.Fill(dataTable); // fill data table from SQL server
var bindingSource = new BindingSource();
bindingSource.PositionChanged += new System.EventHandler(bindingSource_PositionChanged);
bindingSource.DataSource = dataTable; // connect binding source to data table
dataGridView.DataSource = bindingSource; // connect DataGridView to binding source
For the update I finally have tried this:
private void bindingSource_PositionChanged(object sender, EventArgs e)
{
DataRow dataRow = ((DataRowView)((BindingSource)sender).Current).Row;
if (dataRow.RowState == DataRowState.Modified) // this is successful
{
dataAdapter.Update(dataRow); // compile error
}
}
I get the compile error
Cannot convert from 'System.Data.DataRow' to 'System.Data.DataRow[]'.
Any hint is appreciated.
MVVM
In modern programming, there is the tendency to separate the model from the view. This separation makes it easier to change the way that your data is displayed without having to change your model. You can also change parts of the model without having to change the display. It is easier to reuse the model and to unit test it without having to start a forms program.
In WPF this separation between model and view is almost enforced. When using winforms you have to take care that you do not mix them more than needed.
To keep these two separated, adapter code is needed to glue your model to your view. This adapter code is quite often called the viewmodel. the abbreviation of these three is quite often called MVVM. Consider to familiarize yourself with the ideas of MVVM.
Use a BindingList in your DataSource
If you want to separate your model from your view, you need methods to fetch the data that must be displayed from the database, and data to update items.
I don't know what you will be displaying in your DataGridView, but let's assume it is a sequence of Products, something like this:
class Product
{
public int Id {get; set;}
public string ProductCode {get; set;}
public string Name {get; set;}
public string Description {get; set;}
public decimal Price {get; set;}
...
}
You will have methods to fetch the Products that must be displayed, and to Update one Product, or maybe several Products at a time:
IEnumerable<Product> FetchProductsToDisplay(...)
{
// TODO: fetch the products from the database.
}
void UpdateProduct(Product product) {...}
void UpdateProducts(IEnumerable<Product> products) {...}
Implementation is out of scope of this question. By the way, did you notice, that because I put fetching and updating data in separate procedures, I hid where the Products are saved? It can be in an SQL server, but if you want it could also be a CSV or XML file, or even a dictionary, which could be handy for unit tests.
Besides: you can unit tests these methods without using your forms.
Using the visual studio designer you have added the columns and defined which column should show which Product property. You could also have done this in the constructor using property DataGridViewColumn.DataPropertyName
public MyForm()
{
InitializeComponents();
this.columnProductCode.DataPropertyName = nameof(Product.ProductCode);
this.columnName.DataPropertyName = nameof(Product.Name);
...
}
You don't need to set the DataPropertyName for properties that you won't show anyway.
Now to display the products, it is enough to assign the Products to the DataSource of the DataGridView:
var productsToDisplay = this.FetchProductsToDisplay(...);
this.dataGridView1.DataSource = productsToDisplay.ToList();
This will display the products. However, changes that the operator makes: Add / Remove / Edit rows are not updated. If you need this functionality, then the Products need to put in an object that implements IBindingList, like (surprise!) BindingList<Product>:
private BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>)this.dataGridView1.DataSource;
set => this.dataGridView1.DataSource = value;
}
To Initialize the DataGridView:
private void DisplayProducts()
{
this.DisplayedProducts = new BindingList<Product>(this.FetchProductsToDisplay().ToList());
}
Now whenever the operator makes any change to the DataGridView: Add / Remove rows, or change the Displayed values in a row, these changes are reflected in DisplayedProducts.
If for instance the operator clicks Apply Now to indicate he has finished editing the products:
private void OnButtonApplyNow_Clicked(object sender, ...)
{
var products = this.DisplayedProducts;
// find out which Products are Added / Removed / Changed
this.ProcessEditedProducts(products);
}
Of course you can Add / Remove / Change displayed products programmatically:
void AddProductsToDisplay()
{
Product product = this.DisplayedProducts.AddNew();
this.FillNewProduct(product);
}
Back to your question
Ask yourself: Is it wise to update the database as soon as the position is changed?
If the operator starts typing, then remembers he can copy-paste items, he will stop typing, go to other controls to copy, and then continue editing the cell by pasting. Maybe he goes to other rows to look at information to decide what to put in the cell.
Another scenario: the Descriptions of Product A and Product B need to be exchanged. Think of the operator actions needed for this. When would it be wise to update the database? When are you certain that the operator is content with the new data?
Hence it is not wise to update the database as soon as a row is edited. The operator should explicitly indicate he has finished editing.
private void OnButtonOk_Clicked(object sender, ...)
{
var products = this.DisplayedProducts;
// find out which Products are Added / Removed / Changed
this.ProcessEditedProducts(products);
}
Further improvements
Once you've separated your data (model) from the way this data is displayed (view), using the DataSource, it is quite easy to access the Product that is displayed in the current row or in the selected rows:
Product CurrentProduct => (Product) this.dataGridView1.CurrentRow?.DataBoundItem;
IEnumerable<Product> SelectedProducts = this.dataGridView1.SelectedRows
.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<Product>();
you can use foreach loop.
private void AddInfo()
{
// flag so we know if there was one dupe
bool updated = false;
// go through every row
foreach (DataGridViewRow row in dgv_Purchase.Rows)
{
// check if there already is a row with the same id
if (row.Cells["Pro_ID"].ToString() == txt_ProID.Text)
{
// update your row
row.Cells["Purchase_Qty"] = txt_Qty.Text;
updated = true;
break; // no need to go any further
}
}
// if not found, so it's a new one
if (!updated)
{
int index = dgv_Purchase.Rows.Add();
dgv_Purchase.Rows[index].Cells["Purchase_Qty"].Value = txt_Qty.Text;
}
}
Finally I've found the 2 missing lines:
private SqlCommandBuilder commandBuilder; // on UserControl level
commandBuilder = new SqlCommandBuilder(dataAdapter); // when loading data
A book has helped me: Michael Schmalz, C# Database Basics, O'Reilly
It is strange that the DOCS reference of SqlDataAdapter doesn't mention the SqlCommandBuilder.
Thanks to everybody who has spent precious time for a New contributor.
I am writing an app WPF C# .NET 5.0 that converts numbers from metric to imperial (and vice-versa). I am having a weird issue with data not uploading to the database. I successfully downloaded data from the database, but I cannot display it in my data grid.
The database is connected via DataSetProj.xsd file.
Here's how my database looks like:
My app has 3 tabs, one for converter, one for datagrid, and one for other stuff. Whenever input in converter changes, it automatically converts, and adds a record to Stats table.
My app properly reads data from Converters, MetricUnits and ImperialUnits tables (not sure about Stats, because this table is empty).
FYI those are my fields I use to communicate with DB:
DataSetProj DBProj = new DataSetProj();
DataSetProjTableAdapters.ConvertersTableAdapter convertersTableAdapter = new();
DataSetProjTableAdapters.StatsTableAdapter statsTableAdapter = new();
DataSetProjTableAdapters.ImperialUnitsTableAdapter imperialUnitsTableAdapter = new();
DataSetProjTableAdapters.MetricUnitsTableAdapter metricUnitsTableAdapter = new();
DataSetProjTableAdapters.StatsViewTableAdapter statsViewTableAdapter = new();
Here's the launch-time code:
convertersTableAdapter.Fill(DBProj.Converters);
statsTableAdapter.Fill(DBProj.Stats);
imperialUnitsTableAdapter.Fill(DBProj.ImperialUnits);
metricUnitsTableAdapter.Fill(DBProj.MetricUnits);
When I want to add a record to my database, this code invokes:
private void addStat(double input, double output)
{
DataSetProj.ConvertersRow usedConverter = DBProj.Converters.Where(x => x.NameOf == (ConvertersList.SelectedItem as IConverter).ToString()).First();
DataSetProj.MetricUnitsRow metricUnit;
// this if statement exists, because ConvertLength has multiple metric units, like kilometre, metre, etc.
if (ConvertersList.SelectedItem is ConvertLength)
{
metricUnit = DBProj.MetricUnits.Where(x => x.NameOf == BoxUnitChoice.SelectedItem.ToString()).First();
}
else
{
metricUnit = DBProj.MetricUnits.Where(x => x.NameOf == (ConvertersList.SelectedItem as IConverter).MetricUnit(true)).First();
}
DataSetProj.ImperialUnitsRow imperialUnit = DBProj.ImperialUnits.Where(x => x.NameOf == (ConvertersList.SelectedItem as IConverter).ImperialUnit(true)).First();
DBProj.Stats.AddStatsRow(DBProj.Stats.Count, DateTime.Now, (metricToImperial ? 1 : 0), metricUnit, imperialUnit, usedConverter, input, output);
}
And it works - DBProj.Stats.Count increases every time a record is added. However, when I switch to the tab with the data grid (TabStats), it does not show any stats at all. It uses StatsView as a source, not Stats directly (or at least that's how I want it to work).
This code invokes when I switch to TabStats:
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if ((Tabs.SelectedItem as TabItem) == TabStats)
{
DBProj.Stats.AcceptChanges();
statsViewTableAdapter.Fill(DBProj.StatsView);
//neither of these 2 lines populate my datagrid
StatsDataGrid.ItemsSource = DBProj.StatsView;
//StatsDataGrid.ItemsSource = statsViewTableAdapter.GetData();
MessageBox.Show($"{DBProj.Stats.Count} this is stat count\n{DBProj.StatsView.Count} and this is statsView", "test");
}
}
Here's the output of the message box after I input a couple of values (note data grid is empty):
To my knowledge, it looks like the data is stored locally in DBProj.Stats table, but is not sent back to my database (where StatsView attempts to get data from). However, isn't .AcceptChanges() supposed to send any changes I made?
How do I send data back to the database using WPF .NET 5.0? All tutorials I found either used EF or only ever referenced .AcceptChanges() method.
EDIT: I forgot to add, but the data is never saved in the database - every time I run the program, Stats is empty. I want it to save when I switch to TabStats tab, as stated above.
Thanks to inspiration from #Andy, I managed to solve the issue. Instead of using .AcceptChanges() on the Data table, I had to .Update() Data table adapter - I found this doc, which explains how to add data to an existing data table using SQL INSERT command. Here's the working code:
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if ((Tabs.SelectedItem as TabItem) == TabStats)
{
//DBProj.Stats.AcceptChanges();
statsTableAdapter.Update(DBProj.Stats);
statsViewTableAdapter.Fill(DBProj.StatsView);
StatsDataGrid.ItemsSource = DBProj.StatsView;
//StatsDataGrid.ItemsSource = statsViewTableAdapter.GetData();
MessageBox.Show($"{DBProj.Stats.Count} this is stat count\n{DBProj.StatsView.Count} and this is statsView", "test");
}
}
i am newbie of c# and win phone 7
i create a simple database i read this example
http://f5debug.net/2012/02/26/learn-windows-phone-7-development-in-31-days-day-26-working-with-creating-a-local-database-in-wp7/
i open Db in Mainpage
public partial class MainPage : PhoneApplicationPage
{
// short connection string format
private const string strConnectionString = #"isostore:/ManutenzioneDB.sdf";
// Costruttore
public MainPage()
{
InitializeComponent();
using (SampleData.EventoDataContext Empdb = new SampleData.EventoDataContext(strConnectionString))
{
// se il db non esiste creo il db
if (Empdb.DatabaseExists() == false)
{
Empdb.CreateDatabase();
// MessageBox.Show("Employee Database Created Successfully!!!");
}
}
now in Main page i create a button than open an other page
private void button1_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new Uri("/InsertData.xaml", UriKind.Relative));
}
now i don't know can access to Db from InsertData page (InsertData.xaml.cs),
best regads
Antonio
Simpler than you think. :)
var db = new SampleData.EventoDataContext();
db.MyTable.InsertOnSubmit(new MyTable() { ... });
db.Submit();
"MyTable" is the name of the table you defined inside the database.
Make sure you define a primary key, or inserting into the table will fail.
You will need to initialize your table inside the {...} part.
To get items from the table:
foreach (var item in db.MyTable.Where(x => x.SomeProp == 1))
{
//…
}
This will return all the rows where SomeProp is 1. You can now inspect item to see what the row contains.
Try to study the vici cool stored procedure. It is very very simple to create, add, and retrieve data from any db in WP7 applications
http://viciproject.com/wiki/projects/coolstorage/home
I am creating a new window popup window using
PopupWindows.PaymentsSummary paymentsSummary = new PopupWindows.PaymentsSummary
paymentsSummary.ParentWindow = Window.GetWindow(this);
paymentsSummary.ShowDialog();
on my load function in the Payment summary window I have
private void Window_Loaded(object sender, RoutedEventArgs e)
{
basepage.payments.BindPaymentSummaryToDataGrid(uiActiveItems, basepage.user.terminal.TerminalId, true);
basepage.payments.BindPaymentSummaryToDataGrid(uiInActiveItems, basepage.user.terminal.TerminalId, false);
}
The function is
public void BindPaymentSummaryToDataGrid(DataGrid dgrid, int terminalId, bool isActivePayment)
{
BLPinNumber pins = new BLPinNumber();
string pinNumber = String.Empty;
long pinId = pins.getPinId(terminalId, ref pinNumber);
using (var dbEntities = new DatabaseAccess.Schema.Entities())
{
dgrid.DataContext = dbEntities.getPaymentRecordsByPinId((int)pinId, isActivePayment);
}
}
The above code calls a Stored Proc in SQL Server and returns an object,
However when the app runs I get the error when clicking to show the popup on the following line paymentsSummary.ShowDialog();
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
I have worked that down to the following code in the XAML for the datagrid
DataGrid ItemsSource="{Binding}" Grid.Column="{Binding}"
If i remove this code it works but the data doesnt load obvioulsy.
So what I believe I need to do is bind the datagrid onShowDialog method.
How do i create this ?
Or is there a better way of doing this using the Entity framework, im used to ASP.NET where working with DATAGRIDS seem easier, if ablight less powerful.
Many thanks
Your problem is lazy loading!, you got 2 options:
select the data with eager loading (change the getPaymentRecordsByPinId).
do not dispose the dbEntities while popup is open.
Given a DataGridView that has a BindingSource set like this:
On a winform, we add a BindingSource object using the designer, called myBindingScource.
Then on the Form.Designer.cs we add this to the InitializeComponents()
myBindingSource.DataSource = typeof(MyLinq.Person); //Mylinq is the autogenerated Linq Model/Diagram
Later, in the form itself we do:
myDataView.DataSource = myBindingSource;
and then we have a method that populates the Grid…
using ( myDataContext mdc = new MyDataContext() )
{
myDataView.DataSource = from per in mdc.person
select per;
}
As an aside note, I've set up the columns in Design Time, and everything shows ok.
Since the LINQ 2 SQL is not returning an Anonymous, the "myDataView" is editable, and here comes the question…
Question is: how do I persist those changes?
There are dozens of events in the datagrid, and I'm not sure which one is more appropriate. Even if I try one of the events, I still don't know what is the code I need to execute to send those changes back to the DB in order to persist the changes.
I remember back in the ADO.NET DataSet days, you would do dataadapter.Update(dataset);
Also imagine that both the retrieve and the persist() are on a Business Layer and the method signature looks like this:
public void LoadMyDataGrid(DataGridView grid);
that method takes the form's grid and populates it using the LINQ2SQL query shown above.
Now I'd like to create a method like this:
public void SaveMyDataGrid(DataGridView grid); // or similar
The idea is that this method is not on the same class (form), many examples tend to assume that everything is together.
RowValidated event would be a good place to check to see if it's time to persist changes to the database.
this.dataGridView1.RowValidated += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView1_RowValidated);
private void dataGridView1_RowValidated(object sender, DataGridViewCellEventArgs e)
{
MyLinq.Person person = dataGridView1.Rows[e.RowIndex].DataBoundItem as MyLinq.Person;
if (person != null)
{
// save this person back to data access layer
}
}
After your edit:
I wouldn't pass back a datagrid instance to your service layer. I'd pass back IEnumerable<MyLinq.Person> or IList<MyLinq.Person> then iterate over the collection in your service layer, and depending on the logic performed; persist the changes to the data access layer (your database)
The 'save' method on the DataContext object is SubmitChanges().
using (MyContext c = new MyContext())
{
var q = (from p in c.People
where p.Id == 1
select p).First();
q.FirstName = "Mark";
c.SubmitChanges();
}
As Michael G mentioned, you'll need to gather the changes, and pass them back to the bll object.