7

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?

Bob Tway
  • 9,301
  • 17
  • 80
  • 162
  • If you want the user to be able to add and remove it is likely that you do not want to use an observable collection. – Chris Oct 12 '15 at 09:07
  • @Chris I was under the impression an ObservableCollection was necessary if I wanted to use two-way binding so that the View responded to the user's actions? A quick test suggests this is the case. – Bob Tway Oct 12 '15 at 09:15
  • in order to get the view to respond you need to implement INotifyPropertyChanged which observable collection does by default, a custom control would be required if you went down this route but it would be more suited for your needs – Chris Oct 12 '15 at 09:41

4 Answers4

4

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.

ocuenca
  • 38,548
  • 11
  • 89
  • 102
3

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);
}
Eldho
  • 7,795
  • 5
  • 40
  • 77
  • 2
    This is effectively what I did. I'm just uncomfortable about that fact that you have to Add() to your ViewModel property rather than the original collection itself. It feels fragile. – Bob Tway Oct 12 '15 at 09:57
0

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.

Primary Key
  • 1,237
  • 9
  • 14
0

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 
        }
Franck Ngako
  • 163
  • 6