Xamarin.Forms MVVM How to load bindable Picker with data from async process in ViewModel? - mvvm

In my ViewModel i want o load the Picker source RegionName data from an Azure Region table. I extract data from table in an async method but the Picker displays an empty List even after ObservableCollection or List has changed or crashes.
When using PropertyChanged on the ListRegion list itself the app crashes.
In my models:
public class Region
{
public string Id { get; set; }
public string RegionName { get; set; }
}
In my RegisterPage.xaml:
<Picker SelectedIndex="{Binding RegionsSelectedIndex, Mode=TwoWay}"
ItemsSource="{Binding Regions}"
Margin="0,15,0,0"
Title="Select a region">
</Picker>
in my RegisterPage.cs:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class RegisterPage : ContentPage
{
RegisterViewModel registerVM;
public RegisterPage ()
{
InitializeComponent ();
registerVM = new RegisterViewModel();
this.BindingContext = registerVM;
}
protected override void OnAppearing()
{
base.OnAppearing();
}
in my RegisterPageViewModel:
public class RegisterViewModel: INotifyPropertyChanged
{
ApiServices _apiServices = new ApiServices();
public RegisterViewModel()
{
initializePickerAsync();
}
async private void initializePickerAsync()
{
try
{
var regionsList = await App.MobileService.GetTable<Models.Region>().ToListAsync();
List<string> regionsStringList = new List<string>();
foreach (Models.Region reg in regionsList)
{
regionsStringList.Add(reg.RegionName);
}
Regions = regionsStringList;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
/*
private RegisterViewModel (ObservableCollection<Models.Region> regionData)
{
ObservableCollection<string> regionDataAsStringList = new ObservableCollection<string>();
foreach (Models.Region r in regionData)
{
regionDataAsStringList.Add(r.RegionName);
}
this.Regions = regionDataAsStringList;
}
async public static Task<RegisterViewModel> BuildViewModelAsync()
{
ObservableCollection<Models.Region> tmpRegionData = new ObservableCollection<Models.Region>(await App.MobileService.GetTable<Models.Region>().ToListAsync());
return new RegisterViewModel(tmpRegionData);
}
*/
int regionsSelectedIndex = 0;
public int RegionsSelectedIndex
{
get
{
return regionsSelectedIndex;
}
set
{
if (regionsSelectedIndex != value)
{
regionsSelectedIndex = value;
OnPropertyChanged(nameof(RegionsSelectedIndex));
if (regionsSelectedIndex >= 0)
{
Region = Regions[regionsSelectedIndex];
}
}
}
}
// public ObservableCollection<Region> Regions {get;set}
public List<string> Regions
{
get
{
return Regions;
}
set
{
if (Regions != value)
{
Regions = value;
OnPropertyChanged(nameof(Regions));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

you seem to be doing a lot of unnecessary creating and assigning of different lists of data. You should be able to create your ObservableCollection ONCE and then add your data to it, something like this
in your ViewModel
ObservableCollection<Region> Regions { get; set; }
public RegisterViewModel()
{
Regions = new ObservableCollection<Region>();
}
public async void GetData()
{
var regionsList = await App.MobileService.GetTable<Models.Region>().ToListAsync();
foreach (Models.Region reg in regionsList)
{
Regions.Add(reg);
}
}
in your page
protected async override void OnAppearing()
{
base.OnAppearing();
await registerVM.GetData();
}

Related

How to data bind class A{prop masterProp} to a class B{ bound prop to class A masterProp}

I am learning Xamarin forms.
I wanted to be able to bind some ViewModel to some values in a DataManager class (Singleton).
Say the Singleton is a BleManager and all ViewModel need to use it to get or set some information to or from the BLE device.
I Know how to bind my VM to the XAML view code.
But now I need to be able to get the viewModels to update local Data when the BLEManager updates some info, like battery level.
so for example (semi sudo-code).
public class BLEInterface: INotifyPropertyChanged
{
protected virtual void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public async Task<float> GetDeviceName()
{
await Task.Delay(1000);
return float.random(0f:100f)
}
}
public sealed class BLEManager: INotifyPropertyChanged
{
private static BLEManager shared;
private static object objectLockCheck = new Object();
private BLEInterface BleModel { get; set; }
private float batteryLevel;
public float BatteryLevel {
get => batteryLevel;
set {
batteryLevel = value;
OnNotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnNotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private BLEManager()
{
}
public async Task ConnectToBLE()
{
await Task.Delay(1000);
BleModel = new TestModel();
}
public async void GetBatteryLevel()
{
BatteryLevel = await bleModel.GetDeviceName();
}
public static BLEManager Shared
{
get
{
if(shared == null)
{
lock (objectLockCheck)
{
if(shared == null)
{
shared = new BLEManager();
}
}
}
return shared;
}
}
}
The Part I need to know is how can my viewModel hook on changes from the BleManager battery level property.

Xamarin forms - Cannot get object from REST API to xaml page

I am developing an Xamarin.Forms app in VS 2019. My REST API is hosted on GoDaddy.
When I call the api I get back my json converted object fine in my viewmodel. But the object is null
from my xaml page. See this code:
public class NewOrderViewModel : BaseViewModel
{
public NewOrderDetails NewOrderDetails { get; set; }
public ICommand OkCommand { get; private set;}
public ICommand CancelCommand { get; private set; }
readonly IPageService _pageService;
public NewOrderViewModel(IPageService pageService, int custId)
{
_pageService = pageService;
OkCommand = new Command(NewOrder);
CancelCommand = new Command(CancelOrder);
NewOrderDetails = new NewOrderDetails();
LoadNewOrderDetails(custId);
}
private async void LoadNewOrderDetails(int custId)
{
using (var client = new HttpClient(new System.Net.Http.HttpClientHandler()))
{
var response = await client.GetStringAsync("http://api.lates.com.au/api/Customers/" + custId.ToString());
var customer = JsonConvert.DeserializeObject<Customer>(response);
await _pageService.DisplayAlert("Value", customer.CustomerName, "OK"); //This confirms the correct customer is returned.
NewOrderDetails.CustomerName = customer.CustomerName;
foreach (var cd in customer.CustomerDepartments)
{
NewOrderDetails.CustomerDepartments.Add(cd);
}
NewOrderDetails.OrderDate = DateTime.Today;
NewOrderDetails.DeliveryDate = DateTime.Today;
NewOrderDetails.CustomerId = custId;
}
}
private void NewOrder()
{
_pageService.PopAsync();
_pageService.PushModalAsync(new CustomerOrder());
}
private void CancelOrder()
{
_pageService.PopAsync();
}
}
public partial class NewOrder : ContentPage
{
public NewOrder()
{
InitializeComponent();
imgAddIcon.Source = FileImageSource.FromFile("AddDocument64By64.png");
}
protected override void OnAppearing()
{
BindingContext = new NewOrderViewModel(new PageService(), 1);
//If i put a break point here the NewOrderDetails property of NewOrderViewModel is null - WHY???
}
}
It seems to be something to do with asynchronous timing. Let me know if you need more info.
Malcolm
If i put a break point here the NewOrderDetails property of
NewOrderViewModel is null - WHY???
At that time your break point hit, the data in NewOrderDetails has not be set because the httpRequest is still requesting and you have to await the request finish to get the data from Api.
To solve your problem, you have to implement INotifyPropertyChanged in both NewOrderDetails and NewOrderViewModel to notify the View update value after you get the data from Api. I will give you some code snippets:
In NewOrderDetails :
public class NewOrderDetails : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public NewOrderDetails()
{
}
public string CustomerName
{
set
{
if (customerName != value)
{
customerName = value;
OnPropertyChanged("CustomerName");
}
}
get
{
return customerName;
}
}
string customerName { get; set; }
}
In NewOrderViewModel :
public class NewOrderViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public NewOrderDetails NewOrderDetaila
{
set
{
if (newOrderDetails != value)
{
newOrderDetails = value;
OnPropertyChanged("NewOrderDetaila");
}
}
get
{
return newOrderDetails;
}
}
NewOrderDetails newOrderDetails { get; set; }
public NewOrderViewModel( int custId)
{
NewOrderDetaila = new NewOrderDetails();
LoadNewOrderDetails(custId);
}
private async void LoadNewOrderDetails(int custId)
{
//...
NewOrderDetaila.CustomerName = "133";
//...
}
}
And in Xaml binding:
<Label Text="{Binding NewOrderDetaila.CustomerName}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
Try and let me know if it works for you.
One problem in your code is here:
using (var client = new HttpClient(new System.Net.Http.HttpClientHandler()))
{
var response = await client.GetStringAsync("http://api.lates.com.au/api/Customers/" + custId.ToString());
var customer = JsonConvert.DeserializeObject<Customer>(response);
await _pageService.DisplayAlert("Value", customer.CustomerName, "OK"); //This confirms the correct customer is returned.
NewOrderDetails.CustomerName = customer.CustomerName;
foreach (var cd in customer.CustomerDepartments)
{
NewOrderDetails.CustomerDepartments.Add(cd);
}
NewOrderDetails.OrderDate = DateTime.Today;
NewOrderDetails.DeliveryDate = DateTime.Today;
NewOrderDetails.CustomerId = custId;
}
HttpClient should be defined as static class, and reused during your application lifetime. Disposing and recreating HttpClient leads to socket errors. Your code is causing multiple requests. I suggest also move this method to Task, that returns the object.
Example method:
internal class SendData
{
private static HttpClient client = new HttpClient();
internal static async Task<string> MakeServerRequest(string url, string content)
{
try
{
var request = new StringContent(content, Encoding.UTF8, "application/json");
var result = await client.PostAsync(url, request);
var response = await result.Content.ReadAsStringAsync();
return response;
}
catch (Exception ex)
{
YOUR ADDITIONAL LOGIC HERE
return null;
}
}
}
This will return JSON string that you can serialize to object, and do whatever your app requires.

Zxing.Net.Mobile Forms and MVVM Xaml Not Scanning

New to Xamarin development! Using Visual Studio 2017 and all the latest installs and updates for my dev environment.
I have my app "shell" working in that it will run, navigate, crud to local db, and sync to rest services. So, the base of my app is sound. I am trying to integrate the ZXing barcode scanner into my app. Most of what help I can find relates to raw forms or code behind xaml. I am using views and view models and I don't know how to translate the information I am finding to this model.
I currently have the xaml view showing the "camera viewer" when a button is clicked so I can see a barcode using the camera. However, there is no "red line" in the camera view and subsequently, there are no events being fired to let me know what is going on. I really am confused as I am so new to Xamarin. I don't know where to start.
XAML View page
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
xmlns:fe="clr-namespace:FreshEssentials;assembly=FreshEssentials"
xmlns:forms="clr-namespace:ZXing.Net.Mobile.Forms;assembly=ZXing.Net.Mobile.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="Views.InventoryPage1"
Title="Inventory Page">
<StackLayout Spacing="5" Padding="10,10,10,0">
<!--<fe:BindablePicker ItemsSource="{Binding Areas}" SelectedItem="{Binding SelectedArea}" DisplayProperty="AreaName" Title="Select Your Area" />
<fe:BindablePicker ItemsSource="{Binding Reasons}" SelectedItem="{Binding SelectedReason}"
DisplayProperty="Description" Title="Select a Reason" />
<Label Text="Scan"/>
<Entry Text="{Binding Barcode}"/>
<Label Text="Quantity"/>
<Entry Text="{Binding Quantity}"/>
<Button Text="Save" Style="{StaticResource Button_Primary}" Command="{Binding SaveCommand}" />-->
<forms:ZXingScannerView WidthRequest="100" HeightRequest="100" IsScanning="{Binding IsScanning}" IsAnalyzing="{Binding IsAnalyzing}" Result="{Binding Result, Mode=TwoWay}" ScanResultCommand="{Binding QRScanResultCommand}" ></forms:ZXingScannerView>
</StackLayout>
</ContentPage>
ViewModel
public class InventoryPage1ViewModel : BindableBase, INavigationAware
{
private readonly IPageDialogService _pageDialogService;
private bool _isAnalyzing = true;
private bool _isScanning = true;
public ZXing.Result Result { get; set; }
public List<Area> Areas { get; private set; }
public Area SelectedArea { get; set; }
public List<Reason> Reasons { get; private set; }
public Reason SelectedReason { get; set; }
public int Quantity { get; set; }
public string Barcode { get; set; }
public DelegateCommand SaveCommand => new DelegateCommand(PerformSave);
public DelegateCommand QRScanResultCommand => new DelegateCommand(QRCommand);
private readonly IAreaService _areaService;
private readonly IScanService _scanService;
private readonly IReasonService _reasonService;
public InventoryPage1ViewModel(IAreaService areaService, IScanService scanService, IReasonService reasonService, IPageDialogService pageDialogService)
{
_pageDialogService = pageDialogService;
_reasonService = reasonService;
_scanService = scanService;
_areaService = areaService;
Areas = _areaService.GetAll();
Reasons = _reasonService.GetAll();
}
public bool IsScanning
{
get
{
return _isScanning;
}
set
{
_isScanning = value;
RaisePropertyChanged();
}
}
public bool IsAnalyzing
{
get
{
return _isAnalyzing;
}
set
{
_isAnalyzing = value;
RaisePropertyChanged();
}
}
private void QRCommand()
{
int x = 1;
}
private async void PerformSave()
{
var scan = new Scan()
{
AreaId = SelectedArea.Id,
InsertDateTime = DateTime.Now,
ReasonId = SelectedReason.Id,
ScanItem = Barcode,
ScanQty = Quantity,
IsUploaded = false
};
// Save it to the DB here.
var retVal = _scanService.Insert(scan);
if (retVal)
{
await _pageDialogService.DisplayAlertAsync("Saved", "Scan saved successfully.", "OK");
}
else
{
// TODO: Inform the user something went wrong.
}
}
int _index;
public int SelectIndex
{
get
{
return _index;
}
set
{
_index = value;
RaisePropertyChanged("SelectIndex");
}
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
}
public void OnNavigatingTo(NavigationParameters parameters)
{
}
}
MainActivity
public class MainActivity :
global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.tabs;
ToolbarResource = Resource.Layout.toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
ZXing.Net.Mobile.Forms.Android.Platform.Init();
LoadApplication(new App(new AndroidInitializer()));
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
ZXing.Net.Mobile.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
public class AndroidInitializer : IPlatformInitializer
{
public void RegisterTypes(IUnityContainer container)
{
container.RegisterType<IConnectionFactory, ConnectionFactory>();
}
}
UPDATE
I have a working view model now thanks to #Krzysztof over at Xamarin.Forms.
New ViewModel
public class InventoryPage1ViewModel : BindableBase, INavigationAware
{
private readonly IPageDialogService _pageDialogService;
public List<Area> Areas { get; private set; }
public Area SelectedArea { get; set; }
public List<Reason> Reasons { get; private set; }
public Reason SelectedReason { get; set; }
public int Quantity { get; set; }
public DelegateCommand SaveCommand => new DelegateCommand(PerformSave);
private readonly IAreaService _areaService;
private readonly IScanService _scanService;
private readonly IReasonService _reasonService;
public InventoryPage1ViewModel(IAreaService areaService, IScanService scanService, IReasonService reasonService, IPageDialogService pageDialogService)
{
_pageDialogService = pageDialogService;
_reasonService = reasonService;
_scanService = scanService;
_areaService = areaService;
Areas = _areaService.GetAll();
Reasons = _reasonService.GetAll();
}
public ZXing.Result Result { get; set; }
private string barcode = string.Empty;
public string Barcode
{
get
{
return barcode;
}
set
{
barcode = value;
RaisePropertyChanged();
}
}
private bool isAnalyzing = true;
public bool IsAnalyzing
{
get { return this.isAnalyzing; }
set
{
if (!bool.Equals(this.isAnalyzing, value))
{
this.isAnalyzing = value;
RaisePropertyChanged(nameof(IsAnalyzing));
}
}
}
private bool isScanning = true;
public bool IsScanning
{
get { return this.isScanning; }
set
{
if (!bool.Equals(this.isScanning, value))
{
this.isScanning = value;
RaisePropertyChanged(nameof(IsScanning));
}
}
}
public Command QRScanResultCommand
{
get
{
return new Command(() =>
{
IsAnalyzing = false;
IsScanning = false;
Device.BeginInvokeOnMainThread(async () =>
{
Barcode = Result.Text;
await _pageDialogService.DisplayAlertAsync("Scanned Item", Result.Text, "Ok");
});
IsAnalyzing = true;
IsScanning = true;
});
}
}
private async void PerformSave()
{
var scan = new Scan()
{
AreaId = SelectedArea.Id,
InsertDateTime = DateTime.Now,
ReasonId = SelectedReason.Id,
ScanItem = Barcode,
ScanQty = Quantity,
IsUploaded = false
};
// Save it to the DB here.
var retVal = _scanService.Insert(scan);
if (retVal)
{
await _pageDialogService.DisplayAlertAsync("Saved", "Scan saved successfully.", "OK");
}
else
{
// TODO: Inform the user something went wrong.
}
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
}
public void OnNavigatingTo(NavigationParameters parameters)
{
}
}

Prism proper handling of viewmodel collections

I'm using the prism framework for my Xamarin.Forms application.
This is a common scenario, but it caused me headache.
MainPage
- MainPageViewModel
- ObserveableCollection<SomePageViewModel>
public class MainPageViewModel : BaseViewModel
{
private ObservableCollection<SomePageViewModel> viewModels;
public MainPageViewModel(INavigationService navigationService) : base(navigationService)
{
SomePageSelectedCommand = DelegateCommand.FromAsyncHandler(NavigateToSomePage);
}
public ICommand SomePageSelectedCommand { get; private set; }
public ObservableCollection<SomePageViewModel> ViewModels
{
get { return viewModels; }
set { SetProperty(ref viewModels, value); }
}
private async Task NavigateToSomePage(SomePageViewModel viewModel)
{
var navParams = new NavigationParameters
{
{viewModel.typeof(SomePageViewModel).Name, viewModel}
};
await Navigation.NavigateAsync(NavigationConstants.SomePageUri, navParams, false);
}
}
public class SomePageViewModel : BaseViewModel
{
protected SomeModel someModel;
public SomePageViewModel(INavigationService navigationService) : base(navigationService)
{
someModel = new SomeModel();
EditCommand = DelegateCommand.FromAsyncHandler(Edit);
}
public ICommand EditCommand { get; private set; }
public string Name
{
get { return SomeModel.Name; }
set { SetProperty(ref SomeModel.Name, value); }
}
public string Description
{
get { return SomeModel.Description; }
set { SetProperty(ref SomeModel.Description, value); }
}
public override void OnNavigatedTo(NavigationParameters parameters)
{
if (parameters.ContainsKey(typeof(SomePageViewModel).Name))
{
var viewModel = (SomePageViewModel)parameters[typeof(SomePageViewModel).Name];
Name = viewModel.Name;
Description = viewModel.Name;
}
}
private async Task Edit()
{
var navParams = new NavigationParameters
{
{viewModel.typeof(SomePageViewModel).Name, this}
};
await Navigation.NavigateAsync(NavigationConstants.SomePageEditUri, navParams, false);
}
}
public class SomePageEditViewModel : BaseViewModel
{
public SomePageEditViewModel(INavigationService navigationService) : base(navigationService)
{
SaveCommand = DelegateCommand.FromAsyncHandler(Save);
}
public ICommand SaveCommand { get; private set; }
private async Task Save()
{
App.ContentService.Save(someModel);
await Navigation.GoBackAsync();
}
}
So lets navigate from the MainPage to a SomePage. We want to edit it so we navigate to SomePageEdit afterwards and save finally.
What is a proper way to make the changes visible to the SomePage and the MainPage according mvvm/prsim? For the first one I could pass the changes as NavigationParameter into GoBackAsync. But what about the MainPage?
Well it appears you have a bit of a design problem. To properly architect your app you want something closer to:
Model
public class TodoItem : ObservableObject
{
private string _name;
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
private bool _done;
public bool Done
{
get { return _done; }
set { SetProperty(ref _done, value); }
}
}
Model Collection Page ViewModel
public class TodoItemListPageViewModel : BaseViewModel, INavigationAware
{
private INavigationService _navigationService { get; }
public TodoItemListViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
TodoItems = new ObservableRangeCollection<TodoItem>();
AddTodoItemCommand = new DelegateCommand(OnAddTodoItemCommandExecuted);
EditTodoItemCommand = new DelegateCommand<TodoItem>(OnEditTodoItemCommandExecuted);
}
public ObservableRangeCollection<TodoItem> TodoItems { get; }
public DelegateCommand AddTodoItemCommand { get; }
public DelegateCommand<TodoItem> EditTodoItemCommand { get; }
public void OnNavigatingTo(NavigationParameters parameters)
{
// Initialize your collection
}
public void OnNavigatedTo(NavigationParameters parameters)
{
if(parameters.GetValue<NavigationMode>(KnownNavigationParameters.NavigationMode) == NavigationMode.Back)
{
// Option 1
// Fetch an updated list of TodoItems from your data source
TodoItems.ReplaceRange(updatedTodoItems);
// Option 2
// Replace the updated item or add a new item
}
}
Edit Model Page ViewModel
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
private async void OnAddTodoItemCommandExecuted() =>
await _navigationService.NavigateAsync("AddTodoItemPage");
private async void OnEditTodoItemCommandExecuted(TodoItem item) =>
await _navigationService.NavigateAsync("EditTodoItemPage", new NavigationParameters { { "item", item } });
}
public class EditTodoItemPageViewModel : BaseViewModel
{
private INavigationService _navigationService { get; }
public EditTodoItemPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
SaveCommand = new DelegateCommand(OnSaveCommandExecuted, () => IsNotBusy)
.ObservesProperty(() => IsBusy);
}
private TodoItem _model;
public TodoItem Model
{
get { return _model; }
set { SetProperty(ref _model, value); }
}
public DelegateCommand SaveCommand { get; }
public void OnNavigatingTo(NavigationParameters parameters)
{
Model = parameters.GetValue<TodoItem>("item");
}
private async void OnSaveCommandExecuted()
{
IsBusy = true;
// Persist any changes
// Option 1
await _navigationService.GoBackAsync();
// Option 2
await _navigationService.GoBackAsync(new NavigationParameters { { "updatedItem", Model } });
IsBusy = false;
}
}
The Why...
Your ObservableCollection should be where T : TModel not where T : TViewModel. Another issue you would have immediately is that the INavigationService is dependent on knowing what Page you're navigating to/from. So you cannot follow the pattern you're doing there.
Now a couple of notes here.
You'll notice this sample is actually using some helpers from the MvvmHelpers library. The BaseViewModel class from that library gives you the IsBusy/IsNotBusy property as well as a Title property and the ObservableRangeCollection.
ObservableRangeCollection vs ObservableCollection
The ObservableRangeCollection gives you a little better performance particularly when working with larger datasets. You may have noticed the Option 1 where we simply get the updated dataset and replace the entire dataset. This is where the ObservableRangeCollection really shines in my opinion since you're able to ensure you have an up to date dataset while minimizing the notifications to the UI resulting in fewer CPU cycles taken up.
Models, Views, ViewModels
I do not mean for this to an authoritative answer but to at least provide food for thought. From a high level overview of MVVM patterns you generally are working with a View which provides the UX, a ViewModel which provides the business logic for who/what/why/when/where/etc, and a Model which is the data we want to work with. In some cases it can become necessary to introduce a DTO which further abstracts our raw data from the Model we want to work with as a logical unit.

Using EF4 MVC2, Repository and Unit of Work Patterns, Issue with updates to data

I am using MVC2 with Entity Framework 4 and am trying to implement a Repository and UnitofWork pattern. My Adds and Deletes work fine, however when the edit is called _context.SaveChanges() does save the new changes to the database. I have stepped through the code in debug and see the new values are in the object being passed to the Edit function in the controller, but when the commit is called, nothing is updated.See my code below: Thanks for your help.
Here is my IRepository
namespace EventScheduling.DataModel.Custom
{
public interface IRepository<T>
{
void Add(T newEntity);
void Remove(T entity);
IQueryable<T> Find(Expression<Func<T, bool>> predicate);
IQueryable<T> FindAll();
}
}
SQLRepository Implementation
namespace EventScheduling.DataModel.Custom
{
public class SQLRepository<T>:IRepository<T> where T : class
{
protected ObjectSet<T> _objectSet;
public SQLRepository(ObjectContext context)
{
_objectSet = context.CreateObjectSet<T>();
}
public IQueryable<T> Find(Expression<Func<T, bool>> predicate){
return _objectSet.Where(predicate);
}
public void Add(T newEntity)
{
_objectSet.AddObject(newEntity);
}
public void Remove(T entity)
{
_objectSet.DeleteObject(entity);
}
public IQueryable<T> FindAll()
{
return _objectSet;
}
}
}
Unit of Work Implementation
namespace EventScheduling.DataModel.Custom
{
public interface IUnitOfWork
{
IRepository<utility_event> utility_event { get; }
IRepository<event_activity> event_activity { get; }
IRepository<employee> employee{ get; }
IRepository<activity_resource> activity_resource { get; }
IRepository<Elmah_Error> Elmah_Error { get; }
IRepository< location> location { get; }
IRepository<location_station> location_station { get; }
IRepository<registration_type> registration_type { get; }
IRepository< resource> resource { get; }
IRepository<shift> shift { get; }
IRepository<shift_person> shift_person{ get; }
IRepository<event_type> event_type { get; }
IRepository<status> status { get; }
void Commit();
}
}
SqlUnitOfWork Implementation
namespace EventScheduling.DataModel.Custom
{
public class SqlUnitOfWork: IUnitOfWork
{
readonly ObjectContext _context;
const String ConnectionStringName = "EventEntities";
public SqlUnitOfWork()
{
var connectionString = ConfigurationManager.ConnectionStrings[ConnectionStringName].ConnectionString;
_context = new ObjectContext(connectionString);
_context.ContextOptions.LazyLoadingEnabled = true;
}
public IRepository<utility_event> utility_event
{
get {
if (_utilityEvent == null)
{
_utilityEvent = new SQLRepository<utility_event>(_context);
}
return _utilityEvent;
}
}
public IRepository<event_activity> event_activity
{
get
{
if (_eventActivities == null)
{
_eventActivities = new SQLRepository<event_activity>(_context);
}
return _eventActivities;
}
}
public IRepository<employee> employee
{
get
{
if (_employees == null)
{
_employees = new SQLRepository<employee>(_context);
}
return _employees;
}
}
public IRepository<activity_resource> activity_resource
{
get
{
if (_activityResources == null)
{
_activityResources = new SQLRepository<activity_resource>(_context);
}
return _activityResources;
}
}
public IRepository<location> location
{
get
{
if (_locations == null)
{
_locations = new SQLRepository<location>(_context);
}
return _locations;
}
}
public IRepository<location_station> location_station
{
get
{
if (_locationStations == null)
{
_locationStations = new SQLRepository<location_station>(_context);
}
return _locationStations;
}
}
public IRepository<registration_type> registration_type
{
get
{
if (_registrationTypes == null)
{
_registrationTypes = new SQLRepository<registration_type>(_context);
}
return _registrationTypes;
}
}
public IRepository<resource> resource
{
get
{
if (_resources == null)
{
_resources = new SQLRepository<resource>(_context);
}
return _resources;
}
}
public IRepository<shift> shift
{
get
{
if (_shifts == null)
{
_shifts = new SQLRepository<shift>(_context);
}
return _shifts;
}
}
public IRepository<shift_person> shift_person
{
get
{
if (_shiftPersons == null)
{
_shiftPersons = new SQLRepository<shift_person>(_context);
}
return _shiftPersons;
}
}
public IRepository<Elmah_Error> Elmah_Error
{
get
{
if (_ElmahError == null)
{
_ElmahError = new SQLRepository<Elmah_Error>(_context);
}
return _ElmahError;
}
}
public IRepository<event_type> event_type
{
get
{
if (_eventTypes == null)
{
_eventTypes = new SQLRepository<event_type>(_context);
}
return _eventTypes;
}
}
public IRepository<status> status
{
get
{
if (_status == null)
{
_status = new SQLRepository<status>(_context);
}
return _status;
}
}
public void Commit()
{
_context.SaveChanges();
}
SQLRepository<utility_event> _utilityEvent = null;
SQLRepository<event_activity> _eventActivities = null;
SQLRepository<employee> _employees = null;
SQLRepository<activity_resource> _activityResources = null;
SQLRepository<Elmah_Error> _ElmahError = null;
SQLRepository<location> _locations = null;
SQLRepository<location_station> _locationStations = null;
SQLRepository<registration_type> _registrationTypes = null;
SQLRepository<resource> _resources = null;
SQLRepository<shift> _shifts = null;
SQLRepository<shift_person> _shiftPersons = null;
SQLRepository<event_type> _eventTypes = null;
SQLRepository<status> _status = null;
}
}
Controller Edit Implementation
public ActionResult Edit(int id, shift e)
{
if (!ModelState.IsValid)
{
return View(e);
//return to view
}
else
{
e.shift_begin = (DateTime)e.shift_date.Value.Add(e.shift_begin.Value.TimeOfDay);
e.shift_end = (DateTime)e.shift_date.Value.Add(e.shift_end.Value.TimeOfDay);
_unitOfWork.Commit();
return RedirectToAction("Details", "EventActivity", new { id = e.activity_id });
}
}
When you want to update entity in EF you must first Attach entity to ObjectContext instance or instance of related ObjectSet and use ObjectStateManager.ChangeObjectState to set state of entity to EntityState.Modified.
Other possibility is to load entity first from database and merge changes directly into this entity.
Check link in comment for examples or try to use search box. This question is one of the most frequent in EF tag.