I'm pretty new to generics, and MVVM as well. Working on this for a project at school, and my problem might be that I'm trying to impress a bit much. I think I may have coded myself into a hole, but I'm hoping you all can help me get out of it.
I've created a dbcontext, a dataservice, and now I'm trying to get them to play nicely in my viewmodel - I'm getting nowhere.
DbContextFactory:
public class TimeKeeprDbContextFactory : IDesignTimeDbContextFactory<TimeKeeprDbContext>
{
public TimeKeeprDbContext CreateDbContext(string[] args = null)
{
var options = new DbContextOptionsBuilder<TimeKeeprDbContext>();
options.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=TimeKeeprDB;Trusted_Connection=True;");
return new TimeKeeprDbContext(options.Options);
}
}
DbContext:
public class TimeKeeprDbContext : DbContext
{
public TimeKeeprDbContext(DbContextOptions options) : base(options) { }
public DbSet<User> Users { get; set; }
public DbSet<Event> Events { get; set; }
}
DataService:
public class DataService<T> : IDataService<T> where T: DomainObject
{
private readonly TimeKeeprDbContextFactory _contextFactory;
public DataService(TimeKeeprDbContextFactory contextFactory)
{
_contextFactory = contextFactory;
}
public async Task<T> Create(T entity)
{
using TimeKeeprDbContext context = _contextFactory.CreateDbContext();
EntityEntry<T> createdResult = await context.Set<T>().AddAsync(entity);
await context.SaveChangesAsync();
return createdResult.Entity;
} [...]
BaseViewModel:
public class BaseViewModel : INotifyPropertyChanged
{
#region WindowProperties
[.....]
#endregion
#region PropertyChanged
protected void OnPropertyChanged<T>(Expression<Func<T>> action)
{
var propertyName = GetPropertyName(action);
OnPropertyChanged(propertyName);
}
private static string GetPropertyName<T>(Expression<Func<T>> action)
{
var expression = (MemberExpression)action.Body;
var propertyName = expression.Member.Name;
return propertyName;
}
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
ViewModel:
public class MainViewModel : BaseViewModel
{
private string _firstname;
private string _lastname;
public string FirstName
{
get => _firstname;
set
{
_firstname = value;
OnPropertyChanged(() => FirstName);
}
}
public string LastName
{
get => _lastname;
set
{
_lastname = value;
OnPropertyChanged(() => LastName);
}
}
public ICommand CreateCommand { get { return new BaseCommand(Click); } }
private void Click()
{
User user = new User()
{
FirstName = _firstname,
LastName = _lastname
};
//call Create<T> from DataService
ShowMessageBox($"{user.FirstName} {user.LastName}"); //just to make sure the object is ok
}
}
I've tried about a million things, but I just can't seem to wrap my head around what I need to do in order to call 'Create(user)' from DataService in the viewmodel. Can someone maybe point me in the right direction?
PS I've googled until my fingers bled, but it hasn't helped that much, unfortunately.
Hard to tell because I don't know all the frameworks or nuget packages you are using. But from what I can see, you just need to instantiate your DataService somewhere in your MainViewModel, then call its Create method where you've placed your comment //call Create<T> from DataService.
//Instantiate your DataService somewhere
var service = new DataService<User>(new TimeKeeprDbContextFactory());
//call Create<T> from DataService
service.Create(user);
If you see a squigly line under service.Create(user) indicating that this call is not awaited, you can fix that by making your function async/await like this.
private async void Click()
{
User user = new User()
{
FirstName = _firstname,
LastName = _lastname
};
//Instantiate your DataService somewhere
var service = new DataService<User>(new TimeKeeprDbContextFactory());
//call Create<T> from DataService
await service.Create(user);
ShowMessageBox($"{user.FirstName} {user.LastName}"); //just to make sure the object is ok}```
}
Related
I am working on WPF (MVVM) ..
using this tutorial :
https://www.tutorialspoint.com/mvvm/mvvm_validations.htm
when trying to implement "AddEditCustomerViewModel"
I received two errors for this class:
namespace MVVMHierarchiesDemo.ViewModel
{
class AddEditCustomerViewModel : BindableBase
{
public AddEditCustomerViewModel()
{
CancelCommand = new MyICommand(OnCancel);
SaveCommand = new MyICommand(OnSave, CanSave);
}
private bool _EditMode;
public bool EditMode
{
get { return _EditMode; }
set { SetProperty(ref _EditMode, value); }
}
private SimpleEditableCustomer _Customer;
public SimpleEditableCustomer Customer
{
get { return _Customer; }
set { SetProperty(ref _Customer, value); }
}
private Customer _editingCustomer = null;
public void SetCustomer(Customer cust)
{
_editingCustomer = cust;
if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged;
Customer = new SimpleEditableCustomer();
Customer.ErrorsChanged += RaiseCanExecuteChanged;
CopyCustomer(cust, Customer);
}
private void RaiseCanExecuteChanged(object sender, EventArgs e)
{
SaveCommand.RaiseCanExecuteChanged();
}
public MyICommand CancelCommand { get; private set; }
public MyICommand SaveCommand { get; private set; }
public event Action Done = delegate { };
private void OnCancel()
{
Done();
}
private async void OnSave()
{
Done();
}
private bool CanSave()
{
return !Customer.HasErrors;
}
}
}
First one;on this lines :
CancelCommand = new MyICommand(OnCancel);
SaveCommand = new MyICommand(OnSave, CanSave);
an error appear on MyICommand constructor as in :
Error CS0305 Using the generic type 'MyICommand' requires 1 type
arguments
Second one , on this line :
CopyCustomer(cust, Customer);
an error appear for CopyCustomer function as in :
Error CS0103 The name 'CopyCustomer' does not exist in the current
context
the implementation of MyICommand as in :
public class MyICommand<T> : ICommand
{
Action<T> _TargetExecuteMethod;
Func<T, bool> _TargetCanExecuteMethod;
public MyICommand(Action<T> executeMethod)
{
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
{
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members
bool ICommand.CanExecute(object parameter)
{
if (_TargetCanExecuteMethod != null)
{
T tparm = (T)parameter;
return _TargetCanExecuteMethod(tparm);
}
if (_TargetExecuteMethod != null)
{
return true;
}
return false;
}
// Beware - should use weak references if command instance lifetime is
//longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter)
{
if (_TargetExecuteMethod != null)
{
_TargetExecuteMethod((T)parameter);
}
}
#endregion
}
}
to see the whole Project folder ; please see this link :
https://drive.google.com/drive/folders/1jFDU6vDMW_TO0J88NT53oLVWVYbAYyTv?usp=sharing
I'm having a problem with using MVVM for a Xamarin project.
I can not refresh the user interface if one of my objects in my viewModel is updated (after a PUT request, for example).
Let me explain :
My model :
public class MyObject
{
public string Id { get; private set; }
public string Name { get; private set; }
}
My viewmodel :
public class MyViewModel : BaseViewModel
{
public MyObject MyObject { get; private set; }
public string IdMvvm
{
set
{
if (this.MyObject.Id != value)
{
MyObject.Id = value;
OnPropertyChanged(nameof(IdMvvm));
}
}
get { return MyObject.Id; }
}
public string NameMvvm
{
set
{
if (this.MyObject.Name != value)
{
MyObject.Name = value;
OnPropertyChanged(nameof(NameMvvm));
}
}
get { return MyObject.Name; }
}
}
BaseViewModel implements INotifyPropertyChanged
public class BaseViewModel : INotifyPropertyChanged
{
public string PageTitle { get; protected set; }
LayoutViewModel() {}
// MVVM ----------------------------------------------------------------------------------------
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(backingField, value))
return;
backingField = value;
OnPropertyChanged(propertyName);
}
MyViewModel is defined as BindingContext for my page
My properties IdMvvm and NameMvvm are bind in Entry in my page in xaml
When I modify an Entry then the value is raised but if my MyModel object changes value, for example update (click on a button) then the value of the different Entry is not updated
Can you help me please? Because it seems that I missed something ...
If you need more explanation, tell me to know
Sorry if my english is not good
It is because when you change the model, your view is not aware about the change. Update your code so that you explicitly notify property changes when your model changes.
public class MyViewModel : BaseViewModel
{
private MyObject _myObject;
public MyObject MyObject
{
get { return _myObject; }
private set { _myObject = value; NotifyModelChange(); }
}
public string IdMvvm
{
set
{
if (this.MyObject.Id != value)
{
MyObject.Id = value;
OnPropertyChanged(nameof(IdMvvm));
}
}
get { return MyObject.Id; }
}
public string NameMvvm
{
set
{
if (this.MyObject.Name != value)
{
MyObject.Name = value;
OnPropertyChanged(nameof(NameMvvm));
}
}
get { return MyObject.Name; }
}
private void NotifyModelChange()
{
OnPropertyChanged(nameof(IdMvvm));
OnPropertyChanged(nameof(NameMvvm));
}
}
I'm making a Windows store app using the MVVM pattern. When i'm loading my dictionary from a JSON file and binding it to the listview, then it won't update everytime i switch view. Sometimes the listview is updating, sometimes not. Some suggestions for what goes wrong?
class KundePersistency
public static async Task<List<Kunde>> LoadKunderFromJsonAsync()
{
string kundeJsonString = await DeserializekunderFileAsync(JsonFileKunder);
if (kundeJsonString != null)
return (List<Kunde>)JsonConvert.DeserializeObject(kundeJsonString, typeof(List<Kunde>));
return null;
}
private static async Task<string> DeserializekunderFileAsync(string fileName)
{
try
{
StorageFile localFile = await ApplicationData.Current.LocalFolder.GetFileAsync(fileName);
return await FileIO.ReadTextAsync(localFile);
}
catch (FileNotFoundException)
{
return null;
}
}
Class BookingSystem (ViewModel)
private async void LoadKunder()
{
_id = 1;
var loadedKunder = await KundePersistency.LoadKunderFromJsonAsync();
if (loadedKunder != null)
{
KundeRegister.KundeMedId.Clear();
foreach (var kunde in loadedKunder)
{
KundeRegister.KundeMedId.Add(_id++, kunde);
}
}
}
Class KundeRegister (Model)
public class KundeRegister : INotifyPropertyChanged
{
public Dictionary<int, Kunde> KundeMedId { get; set; }
public KundeRegister()
{
KundeMedId = new Dictionary<int, Kunde>();
KundeMedId.Add(Kunde.Id,new Kunde("bob","bob","bob","bobbobbo","bob","bob"));
KundeMedId.Add(Kunde.Id, new Kunde("bob", "bob", "bob", "bobbobbo", "bob", "bob"));
}
public void AddKunde(string username, string password,string adresse,string email, string name, string tlf)
{
KundeMedId.Add(Kunde.Id, new Kunde(adresse, email, name, tlf, username, password));
OnPropertyChanged();
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
You need to User ObeservableCollection for your as Dictionary does not Implement INotifyPropertyChanged By Default
private ObservableCollection<NavigationItem> _loadedKunder ;
public ObservableCollection<NavigationItem> loadedKunder
{
get { return _loadedKunder ; }
set
{
if (value != _loadedKunder )
{
_loadedKunder = value;
NotifyPropertyChanged("loadedKunder");
}
}
}
I'm querying a repository for a list of Customer objects, but I've came across an error when calling ToObservableCollection() on the returned list.
The specific error is when I call the QueryDataFromPersistence() is:
'System.Collections.Generic.List<MongoDBApp.Models.CustomerModel>' does not contain a definition for 'ToObservableCollection' and no extension method 'ToObservableCollection' accepting a first argument of type 'System.Collections.Generic.List<MongoDBApp.Models.CustomerModel>' could be found
From Googling the error, it stated that this is an error, due to there being a mismatch in the assigned list and the returned list. But from checking both my CustomerRepositury and the MainViewModel, both lists are the same.
I also checked that both the Customer model and the MainViewModel, implement INotifyPropertyChanged which they do.
Does anyone know how to debug this further?
In the MainModel, the list is defined as follows and the QueryDataFromPersistence() is called:
public ObservableCollection<CustomerModel> Customers
private void QueryDataFromPersistence()
{
Customers = _customerDataService.GetAllCustomers().ToObservableCollection();
}
In the DataService class, the GetAllCustomers() is called:
ICustomerRepository repository;
public List<CustomerModel> GetAllCustomers()
{
return repository.GetCustomers();
}
The after, in the DataRepository class, GetCustomer() is called:
private static List<CustomerModel> customers = new List<CustomerModel>();
public List<CustomerModel> GetCustomers()
{
if (customers == null)
LoadCustomers();
return customers;
}
private void LoadCustomers()
{
var client = new MongoClient(connectionString);
var database = client.GetDatabase("orders");
//Get a handle on the customers collection:
var collection = database.GetCollection<CustomerModel>("customers");
try
{
customers = collection.Find(new BsonDocument()).ToListAsync().GetAwaiter().GetResult();
}
catch (MongoException ex)
{
//Log exception here:
MessageBox.Show("A connection error occurred: " + ex.Message, "Connection Exception", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
And the Customer Model class:
public class CustomerModel : INotifyPropertyChanged
{
private ObjectId id;
private string firstName;
private string lastName;
private string email;
[BsonElement]
ObservableCollection<CustomerModel> customers { get; set; }
/// <summary>
/// This attribute is used to map the Id property to the ObjectId in the collection
/// </summary>
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("firstName")]
public string FirstName
{
get
{
return firstName;
}
set
{
firstName = value;
RaisePropertyChanged("FirstName");
}
}
[BsonElement("lastName")]
public string LastName
{
get
{
return lastName;
}
set
{
lastName = value;
RaisePropertyChanged("LastName");
}
}
[BsonElement("email")]
public string Email
{
get
{
return email;
}
set
{
email = value;
RaisePropertyChanged("Email");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You're getting the error because ToObservableCollection does not currently exist as an extension method of IEnumerable<T>.
You can either return an ObservableCollection using the following syntax:
private void QueryDataFromPersistence()
{
Customers = new ObservableCollection<CustomerModel>(_customerDataService.GetAllCustomers());
}
Or even better you can write an extension method to encapsulate this:
public static ObservableCollection<T> ToObservableCollection<T>(this IEnumerable<T> enumerableResult)
{
return new ObservableCollection<T>(enumerableResult);
}
Then you can call it like you were originally.
Seems like:
private void QueryDataFromPersistence()
{
Customers = _customerDataService.GetAllCustomers().ToObservableCollection();
}
Should be
private void QueryDataFromPersistence()
{
List<CustomerModel> listc = _customerDataService.GetAllCustomers();
Customers = new ObservableCollection(listc);
}
I have a Linq DataContext as a database for the application. I have set up the MVVM pattern and am able to insert new records into the database. However when I load these records and try update them a new instance of the record is being created in the background and being updated with the property changes. So when they UI is invoking the save command the originally loaded instance of the record has no changes and is not saved.
from what I can tell this is the sequence of events
Load Instance 1
Start updating property
NotifyPropertyChanging is called
New instance2 is loaded
New Instance2 is updated
Invoke save changes from UI for Instance 1
No changes are made because Instance 1 has not been updated
Below is the code I have:
/* This is the Entity */
[Table]
public class User : IDisposable, INotifyPropertyChanged, INotifyPropertyChanging
{
private MyDataContext context;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangingEventHandler PropertyChanging;
private void NotifyPropertyChanging(String propertyName)
{
PropertyChangingEventHandler handler = PropertyChanging;
if (null != handler)
{
handler(this, new PropertyChangingEventArgs(propertyName));
}
}
public void Dispose()
{
context.Dispose();
}
private Guid _id;
[Column(IsPrimaryKey = true, IsDbGenerated = false, DbType = "UNIQUEIDENTIFIER NOT NULL", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public Guid Id
{
get { return _id; }
set
{
if (_id != value)
{
NotifyPropertyChanging("Id");
_id = value;
NotifyPropertyChanged("Id");
}
}
}
private string _name;
[Column(CanBeNull = false)]
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
NotifyPropertyChanging("Name"); // This line creates the new entity
_name = value;
NotifyPropertyChanged("Name");
}
}
}
public User()
{
this.context = MyDataContext.GetContext();
}
public override void SaveChanges()
{
if (_id == Guid.Empty)
{
this.Id = Guid.NewGuid();
context.Users.InsertOnSubmit(this);
context.SubmitChanges();
}
else
{
context.SubmitChanges();
}
}
public static User NewInstance()
{
return new User
{
Name = String.Empty
};
}
}
/* This is the data context */
public class MyDataContext : DataContext
{
// Specify the connection string as a static, used in main page and app.xaml.
public static string ConnectionString = "Data Source=isostore:/MyApp.sdf;Password=pwd";
public MyDataContext(string connectionString) : base(connectionString) { }
public static MyDataContext GetContext()
{
var context = new MyDataContext(ConnectionString);
return context;
}
public Table<User> Users;
}
/* This is the View Model */
public sealed class UserViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private User _user;
public UserViewModel(Guid id)
{
using (MyDataContext context = new MyDataContext(MyDataContext.ConnectionString))
{
_User = context.User.First(u => u.Id == id);
}
}
public UserViewModel(User user)
{
_user = user;
}
public UserViewModel()
{
_user = User.NewInstance();
}
public string Name
{
get { return _user.Name; }
set
{
_user.Name = value;
NotifyPropertyChanged("Name");
}
}
private ICommand _saveCommand;
public ICommand SaveCommand
{
get
{
return _saveCommand ?? (_saveCommand = new GenericCommand(() =>
{
_user.SaveChanges();
}, true));
}
}
}
In your MyDataContext I would think you want the below instead, a basic singleton concept so that you're working on the same object and thus saving the same object with changes.
private static DataContext context = null;
public static MyDataContext GetContext()
{
if(context == null)
context = new MyDataContext(ConnectionString);
return context;
}
edit- Note, this can have a major impact on your application in the big picture. May need to redesign when a new one is created, and if/when you should specifically set it to null.