MYSQL EntityFramework MVVM SaveChanges() with ObservableCollection - c#

i'm using VS2017 and created a C# WPF MVVM app with MYSQL entityframework.
In my view and view model of USERS(utenti) i binded and observable collection to a Datagrid.
Everything works fine, and when i change a cell in a row i use SaveChanges() to save into the db the updated data.
The only problem is when i add a row to my datagrid. when i save nothing happens.
SaveChanges() works only if i modify an existing row, not if i add a row.
Do you know why? I paste the code of my ViewModel
namespace LegalNote.ViewModels
{
class UCAccountsVM : BaseViewModel
{
legalnoteEntities legEnt = new legalnoteEntities();
public UCAccountsVM()
{
List<utenti> listaUtenti = (from recordset in legEnt.utenti
where recordset.id >= 0
orderby recordset.id
select recordset).ToList();
AccountsList = new ObservableCollection<utenti>(listaUtenti);
}
private ObservableCollection<utenti> accountsList;
public ObservableCollection<utenti> AccountsList
{
get
{
return accountsList;
}
set
{
accountsList = value;
RaisePropertyChanged("AccountsList");
}
}
private ICommand saveData;
public ICommand SaveData
{
get
{
if (saveData == null)
saveData = new RelayCommand(o => salvaDati());
return saveData;
}
}
private void salvaDati()
{
legEnt.SaveChanges();
}
}
}

Related

WPF MVVM How can i bind a property to comboBox which changes context query?

I have an WPF view with one Combobox and one DataGrid. I use Entity Framework database first context as a log-term context in my app.
Let's say, this context wold be Global.DbContext.
My entites created by EF are: Log and Client.
In my XAML i have such bindigs:
<DataGrid ItemsSource = {Binding LogEntries} />
<ComboBox ItemsSource="{Binding Clients}" SelectedItem = {Binding SelectedClient} DisplayMemberPath="fullDomainName"
IsSynchronizedWithCurrentItem="True"/>
In my view model i have these properties (i use Catel Framework, so the properties look a bit strange):
public ObservableCollection<Log> LogEntries
{
get { return GetValue<ObservableCollection<Log>>(LogEntriesProperty); }
set { SetValue(LogEntriesProperty, value); }
}
public static readonly PropertyData LogEntriesProperty = RegisterProperty("LogEntries", typeof(ObservableCollection<Log>), null);
public ObservableCollection<Client> Clients
{
get { return GetValue<ObservableCollection<Client>>(ClientsProperty); }
set { SetValue(ClientsProperty, value); }
}
public static readonly PropertyData ClientsProperty = RegisterProperty("Clients", typeof(ObservableCollection<Client>), null);
public Client SelectedClient
{
get { return GetValue<Client>(SelectedClientProperty); }
set { SetValue(SelectedClientProperty, value); }
}
public static readonly PropertyData SelectedClientProperty = RegisterProperty("SelectedClient", typeof(Client), null);
and a constructor:
public LogWindowViewModel()
{
Global.DbContext.Clients.Load();
Clients = Global.DbContext.Clients.Local;
var qry = Global.DbContext.Logs.Where(c => c.client_id == SelectedClient.client_id);
qry.Load();
LogEntries = new ObservableCollection<Log>(qry);
}
which is not working, because at the time of constructor execution a SelectedClient is null. I want my dbset to contain LogEntries only by selected client (both of clients and logs tables in db have a client_id field). How can i achieve that?
I undestand that my constructor code is completely wrong, but i can't figure out what to do in context of 'pure MVVM' approach. Please help me if you can.
If I get you right this is the way I would do it:
1) Create new instances of the ObserableCollections in the constructor so the binding will be established.
2) move the code to fill the LogEntry List to the Setter of SelectedClient
public Client SelectedClient
{
get { return GetValue<Client>(SelectedClientProperty); }
set {
SetValue(SelectedClientProperty, value);
if(value == null)
{
return;
}
var qry = Global.DbContext.Logs.Where(c => c.client_id == value.client_id);
qry.Load();
LogEntries.Clear();
foreach(var entry in qry)
{
LogEntries.Add(entry);
}
}
}

How to make a bound DataGrid have empty rows without reflecting in binding source

I have a DataGrid that is bound to a data source via ItemsSource.
<DataGrid ItemsSource="{Binding MyData}" ...
private ObservableCollection<Data> myData = null;
public ObservableCollection<Data> MyData
{
get
{
if (myData == null)
myData = new ObservableCollection<Data>();
return myData;
}
}
These are my requirements.
This datagrid needs to have several empty rows (without any data) when it first loads, and these empty rows will always be there at the bottom of the datagrid even after other data are loaded later.
These empty rows must not be reflected at the source collection (MyData). The reason being the source collection is used by other parts of the program.
The empty rows allow user to add in new "template" Data via doubleclicking on any of the empty rows; "template" here means a Data object with pre-defined set of attributes (non-empty, though).
This project is not following MVVM so it is fine to break any or even all the rules of MVVM.
I know the requirements may look stupid, but these are the requirements that I need to fulfill, regardless stupid or not. I would prefer to do it in a more proper way, but I am tied to these requirements and I could do nothing.
Any suggestions are appreciated. Thanks.
Edit
Hmm, I actually figured it out one solution myself. I made another property that mirrors the original property, by subscribing to original ObservableCollection's CollectionChanged event.
private static object _emptyData = new object();
private ObservableCollection<object> myClonedData =
new ObservableCollection<object>() { _emptyData, _emptyData, _emptyData };
public ObservableCollection<object> MyClonedData
{
get
{
return myClonedData;
}
private set
{
if (myClonedData != value)
{
myClonedData = value;
OnPropertyChanged("MyClonedData");
}
}
}
private void MyData_OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var newitem in e.NewItems)
{
MyClonedData.Insert(MyClonedData.Count - 3, newitem);
}
OnPropertyChanged("MyClonedData");
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var newitem in e.NewItems)
{
MyClonedData.Remove(newitem);
}
OnPropertyChanged("MyClonedData");
}
}
public ObservableCollection<Data> MyData
{
get
{
if (myData == null)
myData = new ObservableCollection<Data>();
return myData;
}
set
{
if (value != myData)
{
if (myData != null)
myData.CollectionChanged -= MyData_OnCollectionChanged;
value.CollectionChanged += MyData_OnCollectionChanged;
myData = value;
OnPropertyChanged("MyData");
MyClonedData = new ObservableCollection<object>()
{ _emptyData, _emptyData, _emptyData };
}
}
}
Probably not very elegant considering how much I am using setters to do stuff, but it works. I am still looking for better alternatives out there.
Edit 2
I rephrased some of my requirements as they were not too clear. Also to answer Kylo regarding my own solution in previous edit. In that solution, I made another property (MyClonedData collection) which my datagrid could bind with, and that collection is a clone of the original MyData collection. This way, the empty rows are added through generic Object instance, but are added only to the cloned collection. This way, my original collection is untouched, so other parts of my program is able to access and process it.
Change your property MyData get method like this:
private ObservableCollection<Data> myData = null;
public ObservableCollection<Data> MyData
{
get
{
if (myData == null)
myData = new ObservableCollection<Data>();
if (myData != null)
{
myData = new ObservableCollection<MyClass>(myData.Where(x => !string.IsNullOrEmpty(x.Name)));
//If Class Data has property Name
//Additional consitions also can be added in where clause
}
return myData;
}
}
But I recommend you better filter out this collection when you needed it via a method instead.

Changing the data source of ListView dynamically

I have a ListView and data source for it which it populate from the Internet. Once it's populate it should remain static unless a user makes a new http request. Now I have something like this:
class MyDataItem {
public int Field1 { get; set; }
public string Field2 { get; set; }
}
class Window1: Window {
private List<MyDataItem> dataSource = new ...
void sendHttpRequest(...) {
dataSource = getFromInternet();
myListView.ItemsSource = dataSource ;
}
}
And say, I have a checkbox. When I click on it, I want to filter the data by some filter.
//.........
// the checkbox is checked
var filterDataSource = dataSource.Where(....)
How can I make my ListView update its data with data source to be filterDataSource? And then when the checkbox is unchecked again, how will I make it show the initial data source?
Here is some code to help you. Please note that this was not tested nor compiled but it can give you some hints on how to handle your case. The trick is to use a CollectionViewSource that lets you filter your data.
class Window1: Window {
private readonly ObservableCollection<MyDataItem> _children;
private readonly CollectionViewSource _viewSource;
public Window1()
{
// ...
_children = new ObservableCollection<MyDataItem>();
_viewSource = new CollectionViewSource
{
Source = _children
};
myListView.ItemsSource = _viewSource;
// ...
}
// This method needs to be called when your checkbox state is modified.
// "filter = null" means no filter
public void ApplyFilter(Func<MyDataItem, bool> filter)
{
if (_viewSource.View.CanFilter)
{
_viewSource.View.Filter = (filter == null) ? (o => true): (o => filter((MyDataItem) o));
}
}

Multibinding XamDataGrid

I am trying to use the following code example from the Infragistics site and I'd like edits in the XamDataCards to be reflected in the XamDataGrid. However, my DataSource for the XamDataGrid is an ObservableCollection<Companies> in my ViewModel. How can I also bind to the card and relay updates back to my Companies object in the ViewModel?
<igDP:XamDataGrid x:Name="dgCompanies" Theme="IGTheme" DataSource="{Binding Companies}" SelectedDataItemsScope="RecordsOnly">
<igDP:XamDataGrid.FieldSettings>
<igDP:FieldSettings CellClickAction="SelectCell" AllowEdit="True"/>
</igDP:XamDataGrid.FieldSettings>
</igDP:XamDataGrid>
<igDP:XamDataCards x:Name="XamDataCards1"
Grid.Row="1"
DataSource="{Binding Path=SelectedDataItems, ElementName=dgCompanies}"
Theme="IGTheme">
Edit: Added ViewModel
public class CompanyMgmtViewModel : ViewModelBase
{
private ObservableCollection<Object> _Companies = null;
public ObservableCollection<Object> Companies
{
get { return _Companies; }
set
{
if (_Companies != value)
{
_Companies = value;
RaisePropertyChanged(GetPropertyName(() => Companies));
}
}
}
public CompanyMgmtViewModel()
{
this.LoadData();
}
public void LoadData()
{
ObservableCollection<Object> records = new ObservableCollection<Object>();
var results = from res in AODB.Context.TCompanies
select res;
foreach (var item in results)
if (item != null) records.Add(item);
Companies = records;
}
}
The Model/Context code is just EF Database First generated.
You would need to bind your XamDataGrid's SelectedDataItems property to a property of type object[] ie. SelectedCompanies in your ViewModel and bind to that for your XamDataCards' datasource.
The accepted answer in this thread has a sample that shows how to do this, albeit with a ListBox instead of XamDataCards:
http://www.infragistics.com/community/forums/t/89122.aspx
Just replace that ListBox with your XamDataCards control, it works and updates the XamDataGrid. The ViewModel in the example is contained in the MainWindow code-behind, so it is MVVM like you want.
more info:
http://help.infragistics.com/Help/Doc/WPF/2014.1/CLR4.0/html/xamDataGrid_Selected_Data_Items.html
IG's SelectedDataItems is an object[] :
http://help.infragistics.com/Help/Doc/WPF/2014.1/CLR4.0/html/InfragisticsWPF4.DataPresenter.v14.1~Infragistics.Windows.DataPresenter.DataPresenterBase~SelectedDataItems.html
I couldn't have gotten to this answer without Theodosius' and Ganesh's input - so thanks to them, they both had partial answers.
I first tried to bind the SelectedDataItems of the XamDataGrid to the XamDataCards by way of a property on the ViewModel as Theodosius suggested, but that wasn't enough. Thanks to Ganesh, I implemented INotifyPropertyChanged on my model objects, by inheriting from ObservableObject in MVVMLight (how did I not know the Model needed this?).
Below are the relevant pieces of code to make it work.
I also implemented PropertyChanged.Fody as documented here; that's where the TypedViewModelBase<T> and removal of RaisePropertyChanged() comes from.
I'm also creating my Model objects by using a LINQ/Automapper .Project().To<T>() call which can be found here.
Model
public class Company : ObservableObject
{
public Company() { }
public int id { get; set; }
public string strName { get; set; }
public string strDomicileCode { get; set; }
}
ViewModel
public class CompanyMgmtViewModel : TypedViewModelBase<Company>
{
private ObservableCollection<Object> _Companies = null;
private Object[] _selectedCompany = null;
public Object[] Company
{
get { return _selectedCompany; }
set
{
if (_Company != value)
{
_selectedCompany = value;
}
}
}
public ObservableCollection<Object> Companies
{
get { return _Companies; }
set
{
if (_Companies != value)
{
_Companies = value;
}
}
}
public CompanyMgmtViewModel()
{
this.LoadData();
}
public void LoadData()
{
ObservableCollection<Object> records = new ObservableCollection<Object>();
var results = AODB.Context.TCompanies.Project().To<Company>();
foreach (var item in results)
if (item != null) records.Add(item);
Companies = records;
}
}
View
<igDP:XamDataGrid x:Name="dgCompanies"
Theme="IGTheme"
DataSource="{Binding Companies, Mode=OneWay}"
SelectedDataItemsScope="RecordsOnly"
SelectedDataItems="{Binding Company}">
...
<igDP:XamDataCards x:Name="XamDataCards1"
Grid.Row="1"
DataSource="{Binding ElementName=dgCompanies, Path=SelectedDataItems}"
Theme="IGTheme">

Edit an entity in a new window in wpf

i have a window that shows a list of entities and i want to edit the selecteitem of gridview in a new window (Not in grid). when i submit my form no error occurred but entity have no changes in database! please help me.
in top of my list window code behind:
private ObservableCollection<Employee> AllEmployeesData { get; set; }
private ListCollectionView View;
and in window_loaded i use this method for fetch data:
public void LoadAllEmployees()
{
IEnumerable<Employee> data = null;
using (ArchiveEntities db = new ArchiveEntities())
{
data = db.Employees.Include("Department");
this.AllEmployeesData = new ObservableCollection<Employee>(data);
}
CollectionViewSource employeeSource = (CollectionViewSource)this.FindResource("AllEmployeesDataSource");
employeeSource.Source = this.AllEmployeesData;
this.View = (ListCollectionView)employeeSource.View;
}
Editbutton click event:
EditEmployeeView win = new EditEmployeeView();
View.EditItem(SelectedEmployee);
win.DataContext = SelectedEmployee;
if ((bool)win.ShowDialog())
{
using (ArchiveEntities db = new ArchiveEntities())
{
Employee employee = db.Employees.Single(x => x.Id == SelectedEmployee.Id);
db.Employees.ApplyCurrentValues(employee);
db.SaveChanges();
View.CommitEdit();
}
}
else
{
View.CancelEdit();
}
all of the above code is in my first window (window that shows a list of entities).
and in my second window (window for edit selected item of a first window):
submitbutton click event:
DialogResult = true;
Close();
my problem is: when i submit edit form no error occurred but data dont save in database and when i cancel edit form i get this error:
InvalidOperationException was unhandled: CancelEdit is not supported
for the current edit item.
Go away from "using" in datacontext is a really bad approach for entity framework!
If you close your datacontext before save, all entity result disconnected and save as no resut.
Try this way, use a class level context, stay connected and use all power of entityframework
public mainClass{
private ArchiveEntities db;
private ObservableCollection<Employee> allEmployeesData;
private Employee selctedEmplyee;
// property in binding
public ObservableCollection<Employee> AllEmployeesData { get{return allEmployeesData;} set{allEmployeesData=value; onPropertyChanged("AllEmployeesData"); }
public Employee SelctedEmplyee { get{return selctedEmplyee;} set{selctedEmplyee=value; onPropertyChanged("SelctedEmplyee"); }
mainWindow (){ //Constructor
db=new ArchiveEntities();
}
private void onedit(){
new detailWindow(SelectedEmployee).ShowDialog();
//reload from db, upadte current element if modified in the detail window
SelectedEmployee = db.Employees.Single(x => x.Id == SelectedEmployee.Id);
}
//no need to save in main window (is only for view)
}
public class detailWindow(){
private ArchiveEntities db;
private Employee selctedEmplyee;
//employee to modify
public Employee SelctedEmplyee { get{return selctedEmplyee;} set{selctedEmplyee=value; onPropertyChanged("SelctedEmplyee"); }
public detailWindow(Employee SelectedEmployee){
db=new ArchiveEntities; // a new indipendent context
SelectedEmployee = db.Employees.Single(x => x.Id == SelectedEmployee.Id);
}
public void onSave(){
db.SaveChanges(); //effect only in SelectedEmployee
// if you don'save main window data will not change
}
}
why you use View.EditItem,View.CommitEdit and View.CancelEdit? all you need is your win.DataContext = SelectedEmployee. what i dont get is when you set your new edited data to your entity?
using (ArchiveEntities db = new ArchiveEntities())
{
Employee employee = db.Employees.Single(x => x.Id == SelectedEmployee.Id);
db.Employees.ApplyCurrentValues(employee);
db.SaveChanges();
View.CommitEdit();
}
you get the employee from db but you dont apply the edited data from SelectedEmployee to your employee. or do i miss something?
the SelectedEmployee is a entity from your db
data = db.Employees.Include("Department");
this.AllEmployeesData = new ObservableCollection<Employee>(data);
so why you dont use it and save it back to db?
db.SaveChanges(SelectedEmployee );
Employee class must implement IEditableObject
you can see an example here : https://msdn.microsoft.com/en-us/library/system.componentmodel.ieditableobject.aspx
After this implementation, it should work as expected

Categories

Resources