I have a WPF MVVM application with a DataGrid bound to an ObservableCollection returned by DbSet<>.Local. The grid displays content from the database correctly, and changes to the grid change the ObservableCollection, but no changes are saved back to the database.
Context.cs
public class AppContext: DbContext
{
public AppContext() : base("name=DefaultConnection")
{
}
public DbSet<Field> Fields { get; set; }
}
ViewModel.cs
public class EditorViewModel : NotificationObject
{
private ObservableCollection<MyEntity> _myEntities;
private string _message;
public EditorViewModel()
{
var db = new AppContext();
db.MyEntities.Load();
this.MyEntities = db.MyEntities.Local;
}
public ObservableCollection<MyEntity> MyEntities
{
get
{
return _myEntities;
}
set
{
if (_myEntities != value)
{
_myEntities = value;
RaisePropertyChanged("MyEntities");
}
}
}
}
I had thought that changes to the ObservableCollection would automatically write back to the database? Or does SaveChanges need to be called somewhere?
So the answer to this is that using DbSet<>.Local keeps the ObservableCollection in sync with the context, you then just need to call SaveChanges on the context to write back to the database.
http://msdn.microsoft.com/en-gb/data/jj592872.aspx
Related
I have an MVC application that uses Entity Framework 5. In few places I have a code that creates or updates the entities and then have to perform some kind of operations on the updated data. Some of those operations require accessing navigation properties and I can't get them to refresh.
Here's the example (simplified code that I have)
Models
class User : Model
{
public Guid Id { get; set; }
public string Name { get; set; }
}
class Car : Model
{
public Guid Id { get; set; }
public Guid DriverId { get; set; }
public virtual User Driver { get; set; }
[NotMapped]
public string DriverName
{
get { return this.Driver.Name; }
}
}
Controller
public CarController
{
public Create()
{
return this.View();
}
[HttpPost]
public Create(Car car)
{
if (this.ModelState.IsValid)
{
this.Context.Cars.Create(booking);
this.Context.SaveChanges();
// here I need to access some of the resolved nav properties
var test = booking.DriverName;
}
// error handling (I'm removing it in the example as it's not important)
}
}
The example above is for the Create method but I also have the same problem with Update method which is very similar it just takes the object from the context in GET action and stores it using Update method in POST action.
public virtual void Create(TObject obj)
{
return this.DbSet.Add(obj);
}
public virtual void Update(TObject obj)
{
var currentEntry = this.DbSet.Find(obj.Id);
this.Context.Entry(currentEntry).CurrentValues.SetValues(obj);
currentEntry.LastModifiedDate = DateTime.Now;
}
Now I've tried several different approaches that I googled or found on stack but nothing seems to be working for me.
In my latest attempt I've tried forcing a reload after calling SaveChanges method and requerying the data from the database. Here's what I've done.
I've ovewrite the SaveChanges method to refresh object context immediately after save
public int SaveChanges()
{
var rowsNumber = this.Context.SaveChanges();
var objectContext = ((IObjectContextAdapter)this.Context).ObjectContext;
objectContext.Refresh(RefreshMode.StoreWins, this.Context.Bookings);
return rowsNumber;
}
I've tried getting the updated object data by adding this line of code immediately after SaveChanges call in my HTTP Create and Update actions:
car = this.Context.Cars.Find(car.Id);
Unfortunately the navigation property is still null. How can I properly refresh the DbContext immediately after modifying the data?
EDIT
I forgot to originally mention that I know a workaround but it's ugly and I don't like it. Whenever I use navigation property I can check if it's null and if it is I can manually create new DbContext and update the data. But I'd really like to avoid hacks like this.
class Car : Model
{
[NotMapped]
public string DriverName
{
get
{
if (this.Driver == null)
{
using (var context = new DbContext())
{
this.Driver = this.context.Users.Find(this.DriverId);
}
}
return this.Driver.Name;
}
}
}
The problem is probably due to the fact that the item you are adding to the context is not a proxy with all of the necessary components for lazy loading. Even after calling SaveChanges() the item will not be converted into a proxied instance.
I suggest you try using the DbSet.Create() method and copy across all the values from the entity that you receive over the wire:
public virtual TObject Create(TObject obj)
{
var newEntry = this.DbSet.Create();
this.Context.Entry(newEntry).CurrentValues.SetValues(obj);
return newEntry;
}
UPDATE
If SetValues() is giving an issue then I suggest you try automapper to transfer the data from the passed in entity to the created proxy before Adding the new proxy instance to the DbSet. Something like this:
private bool mapCreated = false;
public virtual TObject Create(TObject obj)
{
var newEntry = this.DbSet.Create();
if (!mapCreated)
{
Mapper.CreateMap(obj.GetType(), newEntry.GetType());
mapCreated = true;
}
newEntry = Mapper.Map(obj, newEntry);
this.DbSet.Add(newEntry;
return newEntry;
}
I use next workaround: detach entity and load again
public T Reload<T>(T entity) where T : class, IEntityId
{
((IObjectContextAdapter)_dbContext).ObjectContext.Detach(entity);
return _dbContext.Set<T>().FirstOrDefault(x => x.Id == entity.Id);
}
Hoping someone could clear things up. In the following ViewModel, does using Entity Framework as my model eliminate the need to use [Model] and [[ViewModelToModel(...)] attributes? The code runs the same with or without them, because the binding in the view ignores them and binds to the ObservableCollection.
Comments?
public class MainWindowViewModel : ViewModelBase
{
Models.OneHour_DataEntities ctx;
public MainWindowViewModel()
: base()
{
Save = new Command(OnSaveExecute, OnSaveCanExecute);
ctx = new Models.OneHour_DataEntities();
Customers = new ObservableCollection<Models.Customer>(ctx.Customers);
}
public ObservableCollection<Models.Customer> Customers
{
get { return GetValue<ObservableCollection<Models.Customer>>(CustomersProperty); }
set { SetValue(CustomersProperty, value); }
}
public static readonly PropertyData CustomersProperty = RegisterProperty("Customers", typeof(ObservableCollection<Models.Customer>), null);
public Command Save { get; private set; }
private bool OnSaveCanExecute()
{
return true;
}
private void OnSaveExecute()
{
ctx.SaveChanges();
}
}
Catel uses different interfaces to take advantage of the models. For example, it uses the following interfaces:
IEditableObject => undoing changes to model when user cancels
INotifyPropertyChanged => update view model when model updates
If your entity model implements these interfaces, you can define a property as a model.
In your example however, you use an ObservableCollection (thus a list of models) as a model. That is not supported (or, again, the collection must support IEditableObject and INotifyPropertyChanged).
In MVVM application with clean model (not implementing interfaces like INotifyPropertyChabged), the View Model Contains properties bound to the View and these properties get its values from the model object contained in the view model and should set the value of its properties when view changes one of the controls that are bound to these properties.
the propblem is when the view change; the changes are captured by the bound view model properties but the properties can't set the model object fields, the model doesn't change. I need the model fields to accept setting by the view model properties, then i can persist the updated model into the database taking into account that it is a clean model.
Here part of the view model code
public class SubsystemDetailsViewModel: INotifyPropertyChanged, ISubsystemDetailsViewModel
{
#region Fields
//Properties to which View is bound
private int? _serial;
public int? Serial
{
get { return Subsystem.Serial; }
set
{
//Subsystem.Serial=value;
_serial = value;
OnPropertyChanged("Serial");
}
}
private string _type;
public string Type
{
get { return Subsystem.Type; }
set
{
//Subsystem.Type = value;
_type = value;
OnPropertyChanged("Type");
}
}
//remaining properties ....
#endregion
//Service
private readonly ISubsystemService _subsystemService;
//Reference to the View
public ISubsystemDetailsView View { get; set; }
//Event Aggregator Event
private readonly IEventAggregator eventAggregator;
//Commands
public ICommand ShowTPGCommand { get; set; }
public DelegateCommand UpdateCommand { get; set; }
//
private bool _isDirty;
//Constructor ************************************************************************************************
public SubsystemDetailsViewModel(ISubsystemDetailsView View, ISubsystemService subsystemService, IEventAggregator eventAggregator)
{
_subsystemService = subsystemService;
this.View = View;
View.VM = this;
//EA-3
if (eventAggregator == null) throw new ArgumentNullException("eventAggregator");
this.eventAggregator = eventAggregator;
//Commands
this.ShowTPGCommand = new DelegateCommand<PreCommissioning.Model.Subsystem>(this.ShowTestPacks);
this.UpdateCommand = new DelegateCommand(this.UpdateSubsystem, CanUpdateSubsystem);
}
//****************************************************************************************************************
//ICommand-3 Event Handler
//this handler publish the Payload "SelectedSubsystem" for whoever subscribe to this event
private void ShowTestPacks(PreCommissioning.Model.Subsystem subsystem)
{
eventAggregator.GetEvent<ShowTestPacksEvent>().Publish(SelSubsystem);
}
//===============================================================================================
private void UpdateSubsystem()
{
_subsystemService.SaveChanges(Subsystem);
}
private bool CanUpdateSubsystem()
{
return _isDirty;
}
//*******************************************************************************************
public void SetSelectedSubsystem(PreCommissioning.Model.Subsystem subsystem)
{
this.SelSubsystem = subsystem;
}
//************************************************************************************************************
/// <summary>
/// Active subsystem >> the ItemSource for the View
/// </summary>
private PreCommissioning.Model.Subsystem _subsystem;
public PreCommissioning.Model.Subsystem Subsystem
{
get
{
//return this._subsystem;
GetSubsystem(SelSubsystem.SubsystemNo);
return this._subsystem;
}
set
{
if (_subsystem != value)
{
_subsystem = value;
OnPropertyChanged("Subsystem");
}
}
}
//Call the Service to get the Data form the Database
private void GetSubsystem(string SSNo)
{
this._subsystem = _subsystemService.GetSubsystem(SSNo);
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
_isDirty = true;
UpdateCommand.RaiseCanExecuteChanged();
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
Subsystem is the model object which is populated using GetSubsystem() method. the view model properties like Serial get its value from the model as shown. i tried to set the model properties as shown in the commented out line in set part of the property but no change happen to the Subsystem object, always keep its original values
If GetSubsystem returns a new subsystem every time, that's your problem. In the 'set' for the properties you're binding to the view, you're calling the public property "Subsystem", not the private field you've created. So, every single time you set a property from the view, you are calling Subsystem.get which calls GetSubsystem(SelSubsystem.SubsystemNo);.
I think, in your ViewModel properties', you want to change it to:
//Properties to which View is bound
public int? Serial
{
get { return _subsystem.Serial; }
set
{
_subsystem.Serial=value; // NOTE THE USE OF THE PRIVATE FIELD RATHER THAN THE PROPERTY
OnPropertyChanged("Serial");
}
}
public string Type
{
get { return _subsystem.Type; }
set
{
_subsystem.Type = value; // NOTE THE USE OF THE PRIVATE FIELD RATHER THAN THE PROPERTY
OnPropertyChanged("Type");
}
You need to have a reference in your view-model to the model and the view-model will pass the values to the model. Your view-model will implement INotifyPropertyChanged and will be the datacontext of your view. In your view-model, write your bound properties like this:
private string yourProperty;
public string YourProperty
{
get { return yourProperty; }
set
{
if (value == yourProperty)
return;
yourProperty= value;
YOUR_MODEL_REFERENCE.YourProperty= yourProperty;
this.RaisePropertyChanged(() => this.YourProperty);
}
}
Here is the setup:
I have an autocompletebox that is being populated by the viewmodel which gets data from a WCF service. So it's quite straightforward and simple so far.
Now, I am trying to follow the principles of MVVM by which the viewmodel doesn't know anything about the view itself. Which is good, because I bound the Populating event of the autocomplete box to a method of my viewmodel via triggers and commands.
So the view model is working on fetching the data, while the view is waiting. No problems yet.
Now, the view model got the data, and I passed the collection of results to a property bound to the ItemSource property of the control. Nothing happens on the screen.
I go to MSDN and to find the officially approved way on how this situation is supposed to be handled (http://msdn.microsoft.com/en-us/library/system.windows.controls.autocompletebox.populating(v=vs.95).aspx):
Set the MinimumPrefixLength and MinimumPopulateDelay properties to
values larger than the default to minimize calls to the Web service.
Handle the Populating event and set the PopulatingEventArgs.Cancel
property to true.
Do the necessary processing and set the ItemsSource property to the
desired item collection.
Call the PopulateComplete method to signal the AutoCompleteBox to show
the drop-down.
Now I see a big problem with the last step because I don't know how I can call a method on a view from the view model, provided they don't know (and are not supposed to know!) anything about each other.
So how on earth am I supposed to get that PopulateComplete method of view called from the view model without breaking MVVM principles?
If you use Blend's Interactivity library, one option is an attached Behavior<T> for the AutoCompleteBox:
public class AsyncAutoCompleteBehavior : Behavior<AutoCompleteBox>
{
public static readonly DependencyProperty SearchCommandProperty
= DependencyProperty.Register("SearchCommand", typeof(ICommand),
typeof(AsyncAutoCompleteBehavior), new PropertyMetadata(null));
public ICommand SearchCommand
{
get { return (ICommand)this.GetValue(SearchCommandProperty); }
set { this.SetValue(SearchCommandProperty, value); }
}
protected override void OnAttached()
{
this.AssociatedObject.Populating += this.PopulatingHook;
}
protected override void OnDetaching()
{
this.AssociatedObject.Populating -= this.PopulatingHook;
}
private void PopulatingHook(object sender, PopulatingEventArgs e)
{
var command = this.SearchCommand;
var parameter = new SearchCommandParameter(
() => this.AssociatedObject
.Dispatcher
.BeginInvoke(this.AssociatedObject.PopulateComplete),
e.Parameter);
if (command != null && command.CanExecute(parameter))
{
// Cancel the pop-up, execute our command which calls
// parameter.Complete when it finishes
e.Cancel = true;
this.SearchCommand.Execute(parameter);
}
}
}
Using the following parameter class:
public class SearchCommandParameter
{
public Action Complete
{
get;
private set;
}
public string SearchText
{
get;
private set;
}
public SearchCommandParameter(Action complete, string text)
{
this.Complete = complete;
this.SearchText = text;
}
}
At this point you need to do 2 things:
Wire up the Behavior
<sdk:AutoCompleteBox MinimumPopulateDelay="250" MinimumPrefixLength="2" FilterMode="None">
<i:Interaction.Behaviors>
<b:AsyncAutoCompleteBehavior SearchCommand="{Binding Search}" />
</i:Interaction.Behaviors>
</sdk:AutoCompleteBox>
Create a DelegateCommand which handles your aysnc searching.
public class MyViewModel : ViewModelBase
{
public ICommand Search
{
get;
private set;
}
private void InitializeCommands()
{
this.Search = new DelegateCommand<SearchCommandParamater>(DoSearch);
}
private void DoSearch(SearchCommandParameter parameter)
{
var client = new WebClient();
var uri = new Uri(
#"http://www.example.com/?q="
+ HttpUtility.UrlEncode(parameter.SearchText));
client.DownloadStringCompleted += Downloaded;
client.DownloadStringAsync(uri, parameter);
}
private void Downloaded(object sender, DownloadStringCompletedEventArgs e)
{
// Do Something with 'e.Result'
((SearchCommandParameter)e.UserState).Complete();
}
}
First of all, I have read this post and did not find the answer for my problem.
I am not sure if this is an aggregated Model class or an aggregated ViewModel class, but this is what I have:
In my WPF (with Prism) application, I have a view 'Filter Customers View' that connects to a service and requests a list of 'Customer' objects, based on a filter.
The list that is returned from the service is this :
List<CustomerDTO> FilteredCustomers;
And the CustomerDTO looks like this:
public class CustomerDTO
{
public Guid CustomerId;
public String Name;
public String Address;
public String PhoneNumber;
public OrderInfoDTO LastOrderInformation;
public List<OtherClass> ListOfSomething;
}
And the OrderInfoDTO looks like this:
public class OrderInfoDTO
{
public Guid OrderId;
public DateTime OrderDate;
public int NumberOfProducts;
public double TotalAmountSpent;
}
And the OtherClass looks like this:
public class OtherClass
{
public Guid Id;
public String SomeText;
}
As you can see - the customer might or might not have a 'Last Order',
I would like to wrap the 'CustomerDTO' object in a ViewModel,
so that I can bind it to the view.
This is what I thought of doing :
public class CustomerViewModel : NotificationObject
{
private CustomerDTO _customerDTO;
public CustomerViewModel(CustomerDTO customerDTO)
{
_customerDTO = customerDTO;
}
public Guid CustomerId
{
get { return _customerDTO.CustomerId; }
set { _customerDTO.CustomerId = value; RaisePropertyChanged("CustomerId "); }
}
public String Name
{
get { return _customerDTO.Name; }
set { _customerDTO.Name = value; RaisePropertyChanged("Name"); }
}
public String Address
{
get { return _customerDTO.Address; }
set { _customerDTO.Address = value; RaisePropertyChanged("Address"); }
}
public String PhoneNumber
{
get { return _customerDTO.PhoneNumber; }
set { _customerDTO.PhoneNumber= value; RaisePropertyChanged("PhoneNumber"); }
}
}
.
Questions:
First of all - is 'CustomerDTO' what is known as a Model ? And is 'OrderInfoDTO' also a Model ? and what about 'OtherClass' ?
How do I treat the 'OrderInfoDTO' in my CustomerViewModel class ? Do I create a 'ViewModel' for it also ? where do I create the 'OrderInfoDTO' view-model ??? What happens if now someone updates the customer and sets the 'OrderInfoDTO' value ?
How do I treat the list of 'OtherClass' in my CustomerViewModel class ? Do I create an ObservableCollection for it ? What happens if someone will want to delete an item in it or update an item in it or add an item to it ?
Think about it this way:
The View is your UI that you would bind elements from the View Model to using the {Binding Path=, Mode=TwoWay -- If you want to update based upon the user input
The Model is only the data, this could a record set, file, database records etc. So CustomerDTO and OrderInfoDTO are models.
The View Model is your link between the data (Model) and the UI (View). It will allow to you change the data so it's easier to present on the UI
You would need to use ObservableCollection in all instances where there's a list that could change in the background.
You don't need a view model for OrderInfoDTO unless you need a view to update that data. If you are presenting a CustomerDTO info with OrderInfoDTO in it, then making it a property of the CustomerDTO view model would be fine.