Create two-way relationship list properties on class (Employee-Job) - c#

Currently I have two classes, one is Employee, the other is Job.
Job has a binding list of Employees and both are a subclass of INotifyPropertyChanged
At this stage, Employee also has a binding list of Jobs.
Each time I create a job, I add employee's to it. Then for those employees I add the jobs.
This is where things go pear shaped, the situation becomes infinitely recursive.
It seems wrong for each object to have a binding list of their occupancy. Perhaps I should just pass some sort of reference, Like in the case of the Jobs list of employees I could pass their name, or vice versa.
Any suggestions are welcome.
here is my current code: (NOTE: This version is attempting to using AddingNew events. Which I think I'm doing incorrectly)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace Assignment_3
{
public class Employee : INotifyPropertyChanged
{
static readonly object _object = new object();
private string name_;
private string returnString_;
private Occupation occupation_;
private decimal rate_;
private BindingList<Job> employeeJobs_;
public event AddingNewEventHandler PropertyChanged;
public BindingList<Job> employeeJobs
{
get
{
return employeeJobs_;
}
set
{
employeeJobs_ = value;
makeJobList();
}
}
public void makeJobList()
{
//PropertyChanged(jobList, new PropertyChangedEventArgs("jobList"));
returnString_ = "";
foreach (Job currentJob in employeeJobs_)
returnString_ += currentJob.jobNumber.ToString() + " ,";
}
public Employee(BindingList<Job> employeeJobs, string name, Occupation occupation, decimal rate)
{
employeeJobs_ = employeeJobs;
employeeJobs_.AddingNew += new AddingNewEventHandler(employeeJobs__ListChanged);
name_ = name;
occupation_ = occupation;
rate_ = rate;
makeJobList();
}
private void employeeJobs__ListChanged(object sender, AddingNewEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
makeJobList();
}
public string name
{
get
{
return name_;
}
set
{
name_ = value;
}
}
public Occupation occupation
{
get
{
return occupation_;
}
set
{
occupation_ = value;
}
}
public decimal rate
{
get
{
return rate_;
}
set
{
rate_ = value;
}
}
public string jobList
{
get
{
makeJobList();
return returnString_;
}
}
}
}
The Job code is pretty much the same.
private void createNewEmployeeButton__Click(object sender, EventArgs e)
{
BindingList<Job> selectedJobs = ExtensionMethods.MakeBinding<Job>(jobsForNewEmployeeBox_.SelectedRows);
MessageBox.Show(selectedJobs.Count.ToString());
Employee newJoe = new Employee(selectedJobs, nameTextBox.Text, (Occupation)occupationComboBox.SelectedItem, rateNumericUpDown.Value);
foreach (Job job in selectedJobs)
{
job.employees.Add(newJoe);
job.makeEmployeeList();
}
employeeList_.Add(newJoe);
this.Close();
}

Why don't you factor out the relationship to its own class? Instead of having an instance of a list of Employees in the Job and vice versa, you'd have only one list. This one list can never go out of sync - a problem that you inevitably have when you have separate lists on all Jobs and Employees objects.
EmployeeJobAssignment class
This class contains a reference to an Employee and a Job object and depicts the relationship between them.
EmployeeJobAssignmentCollection class
This class publishes an ObservableList<EmployeeJobAssignment> as a property and also offers methods that allow to change the registered relationship, i.e.:
SetEmployeeJobs(employee, collection of jobs): removes all assignments for an employee and adds the new assignments.
SetJobEmployees(job, collection of employees): removes all assignments for a job and adds the new assignments.
Of course, you can define other methods so like AddEmployeeToJob that only asserts that a specific relationship is present and adds it if required.
The ObservableList<EmployeeAssignment> that is published by the class asserts that other classes can register to be notified when changes to the relationships are made.
Employee/Job classes
When you instantiate an Employee or a Job object, you hand it a reference of the EmployeeJobAssignmentCollection object. The newly instantiated objects subscribe to the CollectionChanged event of the ObservableCollection<EmployeeAssignment>. This way, they are notified abount changes.
The classes keep their collection properties of Jobs or Employees respectively and ask the EmployeeAssignmentCollection to return the relevant objects based on their own id.
Once the object is notified about a change in the relations, they also signal that there was a change in their collection of Jobs or Employees. You might want to implement a mechanism that verifies whether the change is relevant to a specific Employee or Job in order to avoid unnecessary events.
UPDATE: In order to signal the change to the bindings of Jobs/Employees, the easy way would be to raise a PropertyChanged event for the collection properties. As an alternative, you could also return a collection that implements INotifyCollectionChanged. This can be an ObservableCollection<Job> or ObservableCollection<Employee> respectively. Downside is, that you'd need to sync these collections whenever a change in the EmployeejobAssignmentCollection occurs. You can also implement a "fake" collection that implements INotifyCollectionChanged and passes through the Jobs/Employees based on the contents of the EmployeeJobAssignmentCollection that are relevant for an Employee or Job respectively. The effort for this would be higher as you'd need to be able to discern whether items have been added or removed. END OF UPDATE
If the classes need to change the Jobs or Employees, they call the SetEmployeeJobs or SetJobEmployees methods. The related objects are notified through the events. As they also rely on the EmployeeJobAssignmentCollection, they do not need to change a list and by that trigger CollectionChanged events by themselves.
Also you need to make sure that the Jobs and Employees unregister their handlers by implementing IDisposable.
Putting it all together
When you set up your data, you also create and set up the EmployeeAssignmentCollection. It should at least contain the relationships of the relevant Jobs and Employees.
Next, you create the instances of Jobs and Employees and provide the EmployeeAssignmentCollection.
Use the objects as before.
Dispose of the objects as soon as you don't need them anymore.
Hope this rough outline of the model helps. If not, let me know in the comments.

Related

Working with an ObservableCollection based on an Entity Framework one to many property

Working with WPF in MVVM. I have a ViewModel with a CurrentItem property. This is an Item object which is pulled directly from Entity Framework. Item has a collection of Property objects.
public virtual ICollection<Property> Properties { get; set; }
In the View, I need users to be able to add and remove objects from this collection. To do that, I need to create an ObservableCollection<Property>, which we'll call ItemProperties.
There are various ways to do this. The most obvious is to add a ObservableCollection<Property> property on the ViewModel. Then populate this in the constructor, like so:
ItemProperties = new ObservableCollection<Property>(CurrentItem.Properties);
It's also possible to create an ObservableCollection wrapper that sits over the top of the real collection:
public ObservableCollection<Property> ItemProperties
{
get
{
return new ObservableCollection<Property>(CurrentItem.Properties);
}
set
{
CurrentItem.Properties = value.ToList();
OnPropertyChanged("ItemProperties");
}
}
Which has its own problems. You can't just Add() to this collection, since it'll get first, meaning the collection remains unchanged. So you'd either have to spin up a new collection, add to that, and then assign its value to the property or raise the OnPropertyChanged event outside the property. Either of which also sounds like a maintenance issue.
Is there a more effective way of doing this, which allows you to access the EF property list directly?
On this you have advantage of decoupling between data layer and Presentation , No need to spin up the collection.
Try a LoadedEvent to load data from the server.
Sample event is below
private ObservableCollection<Property> _itemProperties;
public ObservableCollection<Property> ItemProperties
{
get { return _itemProperties; }
set
{
_itemProperties= value;
RaisePropertyChanged(() => ItemProperties);
}
}
The loaded event
var result= await Task.Run(() => MyBusiness.GetMyData());
//Map to the viewModel if necessary
ItemProperties = result;
Add to the collection
var isSuccess = await Task.Run(()=>MyBusiness.Insert(x));
if(isSuccess)
{
ItemProperties.Add(x);
}
If you have access to your DbContext in your ViewModel class, you can use DbSet<TEntity>.Local property which it will give you an ObservableCollection<TEntity> that contains all Unchanged, Modified and Added objects that are currently tracked by the DbContext for the given DbSet, but first you need to filter to load into memory only the PropertyItems that belong to your CurrentItem.
public class YourViewModel
{
private context=new YourContext();
public YourViewModel()
{
context.ItemProperties.Where(ip=>ip.ItemId==CurrentItem.Id).Load();
ItemProperties=context.ItemProperties.Local;
}
private ObservableCollection<Property> _itemProperties;
public ObservableCollection<Property> ItemProperties
{
get { return _itemProperties; }
set
{
_itemProperties= value;
OnPropertyChanged("ItemProperties");
}
}
public void SaveItemProperties()
{
context.SaveChanges();
}
}
To save the changes the only you need to do is create, for example, a command that calls the SaveItemProperties method. Also, it could be a good idea disable lazy loading to not load twice the ItemProperties related to your CurrentItem.
If you need to understand more about how this works you can read this article.
either way is good. But what you need to do is to define an handler to the event CollectionChanged present in the Observable Collection. Your underlying entity must have a default constructor too. So when the new item will be created in the grid, that event will be raised.
_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e){if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
}
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add
}
First of all, I don't think creating an ObservableCollection for every get is a good idea. Instead I would cache it in a field. Second, for the cached instance, you will probably want to subscribe to CollectionChanged event in which you will changes will be persisted to the underlying collection.

Does an "Atomic Move" operation exist for C# collections?

Lets say I have a ViewModel in a WPF application with an ObservableCollection. This collection is synchronized with a Model collection so that updating either one updates the other. The model has a business rule that states it must contain 2 Thing's at all times, but the order in the collection can be changed. The user will see a list that has two Thing's in it, and has buttons they can click to move a Thing up or down. The model class subscribes to it's collection's change handler and throws an exception if there are ever less than 2 Things in the collection.
Something like this (very simplified, edge cases not handled etc)
public class ViewModel
{
private Model _model;
public ObservableCollection<Thing> Things { get; set; }
public Thing SelectedThing { get; set; }
public ViewModel(Model model)
{
_model = model;
Things = new ObservableCollection<Thing>(_model.Things);
// Some other code that ensures the model and viewmodel collections stay in sync
// ...
}
public void MoveUp()
{
var selected = SelectedThing;
int i = Things.IndexOf(selected);
Things.Remove(selected); // Throws exception
Things.Insert(i - 1, selected);
}
}
public class Model
{
public ObservableCollection<Thing> Things { get; set; }
public Model()
{
Things = new ObservableCollection
{
new Thing(),
new Thing()
};
Things.CollectionChanged += CollectionChangedHandler;
}
private void CollectionChangedHandler(object sender, CollectionChangedEventArgs e)
{
if(e.Action == CollectionChangedAction.Remove)
{
if(Things.Count < 2)
{
throw new YouCantDoThatException();
}
}
}
}
Basically, when a user clicks "Move Up", the selected item is temporarily removed from the collection and inserted above where it was before. This is an invalid state for the model though, and I can't change the model because it's an API for a lot of other things. Is there a way in C# to do an "Atomic Move" operation that will allow me to move the item without first removing it?
I see two options:
Use the "Move" function provided by ObservableCollection. The disadvantage is that your code is in a state with just one item (albeit for a very short amount of time), but your exception will not throw since CollectionChanged isn't raised until the move is complete.
Reverse your logic, and perform the insert before the remove. You could do this with the InsertItem(index, item) function, followed by the RemoveAt(index) command.
In general, there is no such thing as an "Atomic" move because of how "swap" operations work. You need to assign the "first" value to a temp value, assign the second to the first, and then assign the temp value to the second. Certain assembly operations can make this "Atomic" in the purest sense, but that won't help with NotifyCollectionChanged.
As an aside, you can derive from ObservableCollection and implement your own move algorithm, which you shouldn't need to do unless you need Move to have additional functionality. (MSDN).

performance impact ->modifying an object in Observable Collection vs Removing and adding a new object?

I have an observable collection of a particular type T. Now I have a listener set to third party service and I am getting an object T which is actually a modification to an existing object in that observable collection.
What should I do?
1>Find the object that has been modified and then one by one change all the properties to the newly obtained value
OR
2>just delete the object from the list and add the obtained object
Note:
I can find the object from the collection by LINQ query using an Id property of class T and it is unique for each object. The question is after I find the object will I remove it and add the new one which has the same ID, or do I modify the existing one?
The first solution will be the fastest.
Adding and removing an object from an ObservableCollection is pretty slow. The remove-and-add operation will raise 2 CollectionChanged events.
The problem with the first solution can be the searching.
You can use a more sophisticated search algorithm or you can use a dictionary to keep your items indexed by id.
For example:
class ComplexObj //Maybe should implement INotifyPropertyChanged
{
public int Id{get;set;}
public string SomeProperty{get;set;}
}
Dictionary<int, ComplexObj> lookup = new Dictionary<int, ComplexObj>();
ObservableCollection<ComplexObj> myCollection = new ObservableCollection<ComplexObj>();
When you add an item to the collection make sure to add it in the dictionary too:
public void AddNewObj(ComplexObj obj)
{
lookup.Add(obj.Id, obj);
myCollection.Add(obj);
}
Then when you need to update a specific object:
public void Update(ComplexObj obj)
{
lookup[obj.Id].SomeProperty = obj.SomeProperty;
}
This answer is in request to comment by OP.
You could implement a class, that supports bulk updates. To do that you need to provide method(s) that set the necessary values by accessing the underlying fields directly instead of going through the properties. Once all the values are set, you trigger notification for the entire object. Here's a simple class to illustrate the idea:
class BindableTypeThatSupportsBulkUpdate : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _Text1;
public string Text1
{
get { return _Text1; }
set { _Text1 = value; NotifyPropertyChanged(); }
}
private string _Text2;
public string Text2
{
get { return _Text2; }
set { _Text2 = value; NotifyPropertyChanged(); }
}
private void NotifyPropertyChanged([CallerMemberName]string name = "")
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public void BulkUpdate(string t1, string t2)
{
_Text1 = t1;
_Text2 = t2;
// Passing String.Empty as the name causes notification to fire for all the properties
NotifyPropertyChanged(String.Empty);
}
}
This is simplified, but you probably get the idea anyway.
The BulkUpdate method sets two fields and then triggers notification on the entire object. I.e. you'll only get a single update even if you update multiple values.
Latest Updates(Thanks to #Alberto and #BlackBear for giving me directions):
I tested with XamGrid and observable collection of class T which has 10 properties binding to grid.
Each property raises a notification only if its value changes. Also used dictionary approach of #Alberto.
Deleting and Addding approach takes about 3x to 4x time where as Modification Approach takes x time. However this holds good when your actual object and modified object has only difference in 2 to 3 properties(that means not all properties of actual object are different from the properties of modified object, just 2 properties are different, so raising just two notifications for property change.)
If your modified object has about 5 or 6 properties raising the notification then the performance of modification becomes same as removing and adding a row.
So bottom line is more the difference between actual and modified object more the time taken to modify it, and at a point of time you may think of dropping the actual object and adding the modified object.
For me standard modified object has a difference of 2 to 3 properties from the actual object and performance will be better for Modification of existing object.

Implementing ICollectionViewLiveShaping

How is ICollectionViewLiveShaping implemented for the purpose of filtering? Is it something like:
public ICollectionView WorkersEmployed { get; set; }
WorkersEmployed = new CollectionViewSource { Source = GameContainer.Game.Workers }.View;
I'm not using GetDefaultView because I need multiple instances of filters on this collection. If it matters, GameContainer.Game.Workers is an ObservableCollection.
ApplyFilter(WorkersEmployed);
private void ApplyFilter(ICollectionView collectionView)
{
collectionView.Filter = IsWorkerEmployed;
}
public bool IsWorkerEmployed(object item)
{
Worker w = item as Worker;
return w.EmployerID == this.ID;
}
This all works, but of course it must be manually refreshed, which is why I'm trying to use ICollectionViewLiveShaping. How does live filtering working?
Update: It appears that the only way to add a property to ICollectionViewLiveShaping's LiveFilteringProperties collection is via a string. Given that limitation, is it even possible to filter by properties in another class (Workers' EmployerID in this case)?
Is what I'm trying to do in this situation is even a viable option?
All you need to do is add a property in LiveFilteringProperties for which you want the filter to call on property change and set IsLiveFiltering to true for your collection to enable live filtering.
Make sure PropertyChanged event gets raised whenever EmployerID property changes i.e. your Worker class should implement INotifyPropertyChangedEvent.
This will work then -
public ICollectionViewLiveShaping WorkersEmployed { get; set; }
ICollectionView workersCV = new CollectionViewSource
{ Source = GameContainer.Game.Workers }.View;
ApplyFilter(workersCV);
WorkersEmployed = workersCV as ICollectionViewLiveShaping;
if (WorkersEmployed.CanChangeLiveFiltering)
{
WorkersEmployed.LiveFilteringProperties.Add("EmployerID");
WorkersEmployed.IsLiveFiltering = true;
}
We are using WPF + MVVM + Visual Studio 2017.
We want to convert this to add live filtering:
public ObservableCollection<RowViewModel> Rows { get; set; }
The method below has two key advantages:
It's designed to work efficiently with the WPF runtime to minimise on-screen rendering using bulk updates.
So it's fast.
And because the boilerplate code is listed below, it's easier to follow compared to any other docs you will find on the web.
Please let me know if this worked for you, any issues and I'll update the instructions to make easier.
And the steps:
Step 1: Non-notifying Collection Wrapper
Create a special ObservableCollection that does not fire update events. This is a one-off. We want to fire the update bulk update event ourselves, which is faster.
public class NonNotifyingObservableCollection<T> : ObservableCollection<T>
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { /* Do nothing */ }
}
Step 2: Convert to NonNotifyingObservableCollection
Convert to a private variable which uses this new collection.
private NonNotifyingObservableCollection<RowViewModel> rows;
// ... and in constructor
rows = new NonNotifyingObservableCollection<RowViewModel>();
Step 3: Add Wrapper
Add these variables:
private ICollectionView rowsView;
public ICollectionViewLiveShaping RowsLiveView { get; set; }
And in the Initialise() call after the ViewModel is constructed (or perhaps in the constructor):
// Call on the dispatcher.
dispatcher.InvokeAsync(() =>
{
this.rowsView = CollectionViewSource.GetDefaultView(this.rows);
this.rowsView.Filter = o =>
{
// This condition must be true for the row to be visible on the grid.
return ((RowViewModel)o).IsVisible == true;
};
this.RowsLiveView = (ICollectionViewLiveShaping)this.rowsView;
this.RowsLiveView.IsLiveFiltering = true;
// For completeness. Changing these properties fires a change notification (although
// we bypass this and manually call a bulk update using Refresh() for speed).
this.RowsLiveView.LiveFilteringProperties.Add("IsVisible");
});
Step 4: Add items
Now we add items to the backing collection, then call .Refresh() to refresh the view:
this.rowsView.Add(new RowViewModel( /* Set properties here. */ ));
We then bind the grid to RowsLiveView, (instead of binding to Rows in the original code).
Step 5: Update live filtering
Now we can update the IsVisible property, then call .Refresh() to redraw the grid.
rows[0].IsVisible=false;
this.rowsView.Refresh(); // Hides the first row.
Update
Update: This answer could be simplified. The whole point of ICollectionViewLiveShaping is to autorefresh without the need to call .Refresh(). Given that we have a NonNotifyingObservableCollection and we are manually controlling everything with a .Refresh(), could remove public ICollectionViewLiveShaping RowsLiveView { get; set; } and, directly to RowsView (make it a property with { get; set; }, and use normal ObservableCollection<>. In other words - ICollectionViewLiveShaping is great for a small amount of rows (e.g. <100), but for anything more, ICollectionView in combination with a bulk update and a manual Refresh() is better from a speed point of view.
I experimented with this and it looks like it is not designed for what you (and me) want: Automatic filtering when you change filtering conditions. It filters automatically when some properties of DataGrid's item source changes, but not when filter conditions change - you must call ICollectionViewSource.Refresh manually.

MVVM property depends on a graph of objects

I am working with WPF+MVVM.
I have a VM which contains a Customer property. The Customer has an ObservableCollection of Orders. Each Order has an ObservableCollection of Items. Each Items has a Price.
Now, I have the following property on my VM:
public double TotalPrice
{
return Customer.Orders.Sum(x => x.Items.Sum(y => y.Price));
}
The problem is whenever a change occurs at any point in this graph of objects - the UI should be notified that TotalPrice had changed - but it doesn't...
For example if the Customer will be altered from A to B, or an order will be added, or an item will be deleted, or an item's price will be altered etc.
Does anyone has an elegant solution for this?
Thanks.
Have you supported INotifyPropertyChanged / INotifyCollectionChanged interfaces in ViewModels? You should be able trigger any property manually, for instance in setter of any property you can trigger OnPropertyChanged("TotalPrice") so UI bindings for TotalPrice would be updated as well.
To handle dependent objects changes you can provide events or something like that so ViewModel would be able to subscribe and handle underlying object changes, for instance you have some service which is in chanrge of reloading of the Orders from a database, so as soo as new changes come you would update UI as well. In this case OrdersService should expose event OrdersUpdated and ViewModel can subscribe for this event and in trigger PropertyChanged events for affected properties.
Let's consider some case as an example, for instance Order price has been changed. Who is in charge of this changes? Is this done via UI by an user?
You can find here an interesting post written by me few days ago and it talks exactly about this problem (and its solution...)
You might implement "accumulator" properties which store the sum of values in a collection of objects. Listen for changes to those values and update the accumulator appropriately.
(BTW - I forgot to mention that this one is really just for situations where the value is expensive to calculate. Otherwise Sll's answer is definitely the way to go.)
Something like this, with the boring stuff left out:
class BasketOfGoods : INotifyPropertyChanged
{
ObservableCollection<Good> contents = new ObservableCollection<Good>();
public decimal Total
{
get { /* getter code */ }
set { /*setter code */ }
}
public BasketOfGoods()
{
contents.CollectionChanged += contents_CollectionChanged;
}
void contents_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
foreach (var newGood in e.NewItems) ((Good)newGood).PropertyChanged += BasketOfGoods_PropertyChanged;
}
void BasketOfGoods_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Price") Total = contents.Select(x => x.Price).Sum();
}
}
class Good : INotifyPropertyChanged
{
public decimal Price
{
{
get { /* getter code */ }
set { /*setter code */ }
}
}
I think my latest WPF endeavor MadProps handles this scenario pretty well. Take a look at this example master-detail scenario. As long as there a path from the Item being edited to the VM (for example, VM.TheCustomer.SelectedOrder.SelectedItem or simply VM.SelectedItem), the PropChanged event will propogate up to the VM and you can write:
public readonly IProp<Customer> TheCustomer;
public readonly IProp<double> TotalPrice;
protected override void OnPropChanged(PropChangedEventArgs args)
{
if (args.Prop.IsAny(TheCustomer, Item.Schema.Price))
{
TotalPrice.Value = TheCustomer.Value.Orders
.Sum(order => order.Items.Sum(item => item.Price.Value));
}
}
Regarding adding and removing Items or Orders, I would just put an explicit update in the ICommand code, something like VM.UpdateTotalPrice();. Managing subscriptions and unsubscriptions to ObservableCollections and the items in them can be tricky.

Categories