Edit an entity in a new window in wpf - c#

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

Related

MYSQL EntityFramework MVVM SaveChanges() with ObservableCollection

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();
}
}
}

How to update a single property in Entity Framework Core

I need to update only one or two properties of an Entity. In other words, I have an entity with an Id, ParentId, Name, and Description.
The issue is that when the name is updated, the Description is wiped out if it already existed in the database.
This is the code:
internal void Update(ItemInfo itemInfo)
{
var item = new Item { Id = itemInfo.Id, ParentId = itemInfo.ParentId, Name = itemInfo.Name };
var entry = this.DbContext.Items.Attach(item);
if (item.ParentId != null) entry.Property(x => x.ParentId).IsModified = true;
if (!(String.IsNullOrWhiteSpace(item.Name))) entry.Property(x => x.Name).IsModified = true;
this.SaveChanges();;
}
I thought that since I was setting the particular property as modified, only that property would be updated.
Or should I be getting the entity from the database first, and then just setting the property and saving. I wanted to avoid two trips to the database for a single save.
You can do following in order to update a single property:
internal void Update(ItemInfo itemInfo)
{
if (itemInfo == null) { throw new Exception($"item info was not supplied"); }
var itemInfoEntity = new ItemInfo()
{
Id = itemInfo.Id,
ParentId = itemInfo.ParentId,
Name = itemInfo.Name
};
dbContext.ItemInfo.Attach(itemInfoEntity);
dbContext.Entry(itemInfoEntity).Property(x => x.Id).IsModified = true;
dbContext.Entry(itemInfoEntity).Property(x => x.ParentId).IsModified = true;
dbContext.Entry(itemInfoEntity).Property(x => x.Name).IsModified = true;
dbContext.SaveChanges();
}
But if you only are updating few properties, then I think you should not send the whole object as parameter, just send the properties that needs to be updated something like this:
internal void Update(id, parentId, name)
{
...
}
The thing you're really after is Concurrency Handling. When multiple users are editting the same object at the same time, the second user will overwrite the changes of the first user.
Or should I be getting the entity from the database first, and then just setting the property and saving.
Yes, but having a controller method for each property of your object would be very tedious. That's why concurrency handling is the best option.
Also, your domain model should be entirely seperated from your database model. Never use entities in your web application directly. You are already doing so by having the Item entity (database model) and the ItemInfo class (domain model, used to handle the post-request).
Implement concurrency handling
First add a Timestamp column to your entity:
internal class Item
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
[Timestamp]
[ConcurrencyCheck]
public byte[] ConcurrencyStamp { get; set; }
}
Then, at the place where you update your entity:
[Controller]
public class ItemController : Controller
{
private readonly DbContext dbContext;
public ItemController(DbContext dbContext)
{
this.dbContext = dbContext;
}
[HttpPost]
//[Authorize]
//[ValidateAntiForgeryToken]
public async Task<ActionResult<ItemInfo>> Update([FromBody] ItemInfo item)
{
var existingItem = dbContext.Items.SingleOrDefaultAsync(i => i.Id == item.Id);
if (Convert.ToBase64String(existingItem.ConcurrencyStamp) != item.ConcurrencyStamp)
{
var databaseValue = new ItemInfo
{
Id = existingItem.Id,
ParentId = existingItem.ParentId,
Name = existingItem.Name,
};
return StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status409Conflict, databaseValue);
}
// Set new properties
existingItem.Id = item.Id;
existingItem.ParentId = item.ParentId;
existingItem.Name = item.Name;
// Save changes
await dbContext.SaveChangesAsync();
// Now return the updated item
// Now you should map the entity properties back to a new domain model...
var result = new ItemInfo
{
Id = existingItem.Id,
ParentId = existingItem.ParentId,
Name = existingItem.Name,
ConcurrencyStamp = Convert.ToBase64String(existingItem.ConcurrencyStamp),
};
return Ok(item);
}
}
Now when you try to update your item from the client-side, and receive a 409Conflict statuscode, you should decide how you want to handle this. I've chosen to display the database values below the respective input boxes.
You can find an entire implementation of this here

EF Core - Disposable DbContext and Attach() - or - DbContext as member - or - Disconnected Entities

I'm not sure about how to correctly use the DbContext for Entities bound to a WPF DataGrid?
How do I correctly "re-attach" and save changes to the database for all the entities that were loaded to the datagrid during UserControl load?
I was using a DbContext as a member variable and ObservableCollection as DataSource for Datagrids. So everything was fine so far, no need to search for errors in the code below. Just to show what I have done so far.
// Old code - working perfectly as desired
private TestMenuDataContext _Db;
public ObservableCollection<Vendor> Vendors { get; set; }
private void ucGeneralSettings_Loaded(object sender, RoutedEventArgs e) {
//Do not load your data at design time.
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) {
_Db = new TestMenuDataContext();
_Db.Database.EnsureCreated();
Vendors = new ObservableCollection<Vendor>(_Db.Vendors);
Vendors.CollectionChanged += Vendors_CollectionChanged;
vendorDataGrid.ItemsSource = Vendors;
}
}
private void Vendors_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
_Db.Vendors.AddRange(e.NewItems.Cast<Vendor>());
foreach (var vendor in e.NewItems.Cast<Vendor>()) {
vendor.TimeStamp = DateTime.Now;
vendor.UserName = Environment.UserName;
}
break;
case NotifyCollectionChangedAction.Remove:
_Db.Vendors.RemoveRange(e.OldItems.Cast<Vendor>());
break;
}
}
private void SaveSettingsButton_Click(object sender, RoutedEventArgs e) {
var queryDeletedUsedVendor = _Db.TestMenu.Where(t => !Vendors.Any(v => v.Name== t.Vendor));
if (queryDeletedUsedVendor.Any()) {
_AppManager.AddStatusMessage($"Saving settings not possible. Vendor {queryDeletedUsedVendor.FirstOrDefault().Vendor} deleted but it is in use in the Test Menu!", State.Error);
return;
}
try {
_Db.SaveChanges();
_AppManager.AddStatusMessage("Settings saved", State.Ok);
}
catch (Exception ex) {
_AppManager.AddStatusMessage($"Saving data failed {ex.Message}", State.Error);
}
// fire delegate event to inform MainWindow
onDatabaseUpdated?.Invoke(this);
}
private void ucGeneralSettings_Unloaded(object sender, RoutedEventArgs e) {
if (_Db != null)
_Db.Dispose();
}
BUT, currently starting with MVVM and search how to correctly integrate EF Core. Now I have read several times:
Your DbContext lifetime should be limited to the transaction you are
running.
E.g. here:c# entity framework: correct use of DBContext class inside your repository class
So taking this into account and changed the saving code to:
// new code
using (TestMenuDataContext db = new TestMenuDataContext())
{
foreach (Vendor v in Vendors) {
var test = db.Vendors.Attach(v);
bool isAlreadyInside = db.Vendors.Any(v2 => v2.Id == v.Id);
if (!isAlreadyInside)
db.Vendors.Add(v);
}
db.SaveChanges();
Do I really need to loop over all entities, attach every single entity and check manually for deleted or added entities? I don't like to have a DbContext opened every time when CollectionChanged event appears. I can't believe it should be this complicated... So currently I would prefer to go with the DbContext as member variable as used before...
If I'm googling correct the not implemented disconnected entities aren't intended to be used in a WPF app with DB-Server connection, they are meant to be used in n-tier environment. So this is not the topic to search for, correct?
Do I need disconnected entities?
Disconnected Entities on MSDN
Side Note:
in MVVM ObservableCollection.CollectionChanged is supposed to inform View about changes in Model, resp ViewModel. I would not recommend to let View modify ObservableCollection and then use CollectionChanged to reflect the changes in ViewModel. Try to keep ViewModel -> View notification flow, not the other direction.* Every change is done in ViewModel and reflected in the View.
First approach:
basically split your application logic and your data access, which is exactly what viewmodel is for.
public class YourPageViewModel
{
private readonly ObservableCollection<VendorItemVm> _deletedVendors = new ObservableCollection<VendorItemVm>();
public List<VendorItemVm> Vendors { get; } = new List<VendorItemVm>();
void Add()
{
Vendors.Add(new VendorItemVm
{
IsNew = true,
Id = new Guid(),
UserName = "New Vendor",
});
}
void Remove(VendorItemVm vendor)
{
Vendors.Remove(vendor);
_deletedVendors.Add(vendor);
}
async Task Load()
{
using(var db = new DbContext())
{
var vendors = db.Vendors.AsNoTracking().ToList();
foreach(var entity in vendors)
{
Vendors.Add(new VendorItemVm
{
Id = entity.Id,
Name = entity.Name,
});
}
}
}
async Task Save()
{
using (var db = new DbContext())
{
//convert viewmodels to entities
var newVendorsEntities = Vendors
.Where(v => v.IsNew)
.Select(v => new Vendor
{
Id = v.Id,
UserName = v.UserName,
TimeSpan = DateTime.Now,
})
.ToArray();
//add new entities
foreach (var vm in Vendors.Where(v => v.IsNew))
{
var entity = new Vendor
{
Id = vm.Id,
UserName = vm.UserName,
TimeSpan = DateTime.Now,
};
db.Vendors.Add(vendor);
}
//delete removed entities:
foreach(var vm in _deletedVendors)
{
var entity = new Vendor { Id = vm.Id };
db.Vendors.Attach(entity);
db.Ventors.Remove(entity);
db.Vendors.Add(vendor);
}
await db.SaveChangesAsync();
//reset change tracking
foreach (var vm in Vendors) vm.IsNew = false;
_deletedVendors.Clear();
}
}
}
Second approach:
In the previevious example we have basically implemented our own primitive Unit of Work pattern. However, DbContext is already implementation of UoW and change tracking pattern.
We will create instance of DBContext, but we will use it only for tracking Added/Removed entities:
public class YourPageViewModel
{
MyDbContext _myUoW;
public ObservableCollection<Vendor> Vendors { get; } = new ObservableCollection<Vendor>();
void Add()
{
var entity = new Vendor
{
Id = new Guid(),
UserName = "New Vendor",
};
Vendors.Add(entity)
_myUoW.Vendors.Add(entity);
}
void Remove(VendorItemVm vendor)
{
Vendors.Remove(vendor);
_myUoW.Vendors.Attach(entity);
_myUoW.Vendors.Add(entity);
}
async Task Load()
{
using(var db = new MyDbContext())
{
Vendors = db.Vendors.AsNoTracking.ToList();
foreach(var entity in vendors) Vendors.Add(entity);
}
_myUoW = new MyDbContext();
//if you want to track also changes to each vendor entity, use _myUoW to select the entities, so they will be tracked.
//In that case you don't need to attach it to remove
}
async Task Save()
{
//add new entities and delete removed entities
_myUoW.SaveChanges();
//reset change tracking
_myUoW.Dispose();
_myUoW = new MyDbContext();
}
}
I don't like to have a DbContext opened every time when CollectionChanged event appears.
Then don't. Create a single TestMenuDataContext in your view model and use this one as you did before.
So currently I would prefer to go with the DbContext as member variable as used before.
There is nothing stopping you from doing so, is it? Apparently, you do want a single TestMenuDataContext per instance of your view model in this case. Just create a TestMenuDataContext once in your view model, for example in its constructor, and use this one in your CollectionChanged event handler. Or create the context in your save method.
The optimal lifetime of a DbContext may certainly vary depending on your requirements. In general you should use short-lived contexts, but in this case it seems like you do want (and should use) the same context for all changes made to the entity objects in your DataGrid.
The other option would of course be to create the context and attache your entities when the save button is pressed (and not every time the in-memory collection is modified).

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));
}
}

WPF MVVM content of Listbox of an Entity depends on another Listbox of an Entity

Business Rule:
A placement can be filled by an opening where a Candidate that matches the opening can be assign.
An Opening required One Qualification.
A Candidate can have many Qualification and a Qualification may be acquired by many Candidate so Associative Certificate was made to avoid many-to-many Relationship.
Problem:
The Main Window shows Qualification, Candidate and Placement. In order to add placement i click the Add Placement Window which has a DataContext of PlacementModel and a New Window Appears which has 2 Listbox and a Button for Finalizing the Add, user must select an Opening in a listbox and a list of Candidates where there qualification matches appear in another listbox. The binding path for the 2 listbox are Openings and Certificates, respectively.
I want to make the Certificate Listbox updates its list whenever i click an Opening.
Entities are Candidate, Qualification, Placement, Certificate and Opening.
How can I implement this?
ListBoxes Binding Path:
LstOpenings(ItemsSource = Openings, SelectedItem= SelectedOpening)
LstCertificates(ItemsSource = Certificates, SelectedItem = SelectedCertificate)
PlacementModel:
#region Public Interface
public ObservableCollection<OpeningModel> Openings { get; private set; }
public ObservableCollection<CertificateModel> Certificates { get; private set; }
public OpeningModel SelectedOpening { get { return _selectedOpening; } set { _selectedOpening = value; } }
public CertificateModel SelectedCertificate
{
get { return _selectedCertificate; }
set
{ _selectedCertificate = value;
}
}
#endregion
#region Private Helper
private void GetOpenings()
{`enter code here`
var all = _context.Openings.OrderBy(cust => cust.OpeningDescription).ToList()
.Select(
opening =>
new OpeningModel(opening, _context, _openingRepository, _companyRepository,
_qualificationRepository,_certificateRepository));
Openings = new ObservableCollection<OpeningModel>(all);
ICollectionView view = CollectionViewSource.GetDefaultView(Openings);
view.SortDescriptions.Add(new SortDescription( "OpeningDescription",ListSortDirection.Ascending));
}
private void GetCertificates()
{
if (_selectedOpening == null)
{
Certificates = new ObservableCollection<CertificateModel>();
}
else
{
var all =_certificateRepository.GetCertificates().Where(c => c.QualificationCode == _selectedOpening.QualificationCode)
.OrderBy(c => c.Qualification.QualificationDescription)
.ToList().Select(c=>new CertificateModel(c,_context,_certificateRepository,_candidateRepository,_qualificationRepository));
Certificates = new ObservableCollection<CertificateModel>(all);
ICollectionView view = CollectionViewSource.GetDefaultView(Certificates);
view.SortDescriptions.Add(new SortDescription("QualificationDescription", ListSortDirection.Descending));
}
}
#endregion
Add a new property of type OpeningModel named Opening and data bind that to the ListBox.SelectedItem property:
<ListBox ItemsSource="{Binding Openings}" SelectedItem="{Binding Opening}" ... />
Now all you need to do is update the Certificates collection whenever the Opening value is changed. You can do that from the Opening property setter:
public OpeningModel Opening
{
get { return opening; }
set
{
opening = value;
NotifyPropertyChanged("Opening");
UpdateCertificates();
}
}
I'll leave it up to you to complete the UpdateCertificates method, as I'm sure you know how to do that.

Categories

Resources