5

I am trying to Save Data to database.

Suppose I have a Table Named Customers having three fields:

Id
FirstName
LastName

I have created my models using ADO.Net Entity Data Model.

Here is my ViewModel code

public class myViewModel : INotifyPropertyChanged
{
    private string _firstName;
    public string FirstName
    {
        get
        {
            return _firstName;
        }
        set
        {
            _firstName = value;
            OnPropertyChanged("FirstName");
        }
    }

    private string _lastName;
    public string LastName
    {
        get
        {
            return _lastName;
        }
        set
        {
            _lastName = value;
            OnPropertyChanged("LastName");
        }
    }

    protected virtual void OnPropertyChanged(string PropertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(PropertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

}

Here is my MainWindow.xaml file:

<Window x:Class="Lab_Lite.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:Lab_Lite.ViewModels"
        Title="MainWindow" Height="350" Width="525" WindowState="Maximized">

    <Window.DataContext>
        <vm:MainWindowViewModel />
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <TextBlock Grid.Row="0" Grid.Column="0" Text="FirstName" />
        <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=Explicit}" />

        <TextBlock Grid.Row="1" Grid.Column="0" Text="LastName" />
        <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding LastName, Mode=TwoWay, UpdateSourceTrigger=Explicit}" />

        <Button Grid.Row="2" Grid.Column="1" Content="Save" />
    </Grid>
</Window>

I have got two problems here:

1. How my ViewModel knows that FirstName property declared in ViewModel is 
   referenced to FirstName Column in my database?
2. How to save changes to database when UpdateSourceTrigger is set to Explicit?

I think I have found the answer to 2nd question upto some extent using Command. But I dont know if it is right or not as I dont know the answer to my first question.

Update:

Suppose I have two tables like this:

Customer:

CustomerID
Name
GenderID //Foreign Key

Gender:

GenderID
Value

Now what should be the value of CurrentCustomer.Gender in SaveCustomerChanges Method?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Khushi
  • 1,031
  • 4
  • 24
  • 48

1 Answers1

13

I highly recommend you use a ORM like Entity Framework (EF) using database-first since you've already created a database. Entity Framework will create your POCOs (model classes, Plain Old C# Objects) automatically for you and will also create a class named **YourDbName**Context which is your DbContext. At that point your application will create an instance of you context each time you run your app, and you will be able to access your database using the context. EF will also keep track of any changes you make so here is how you problem would be solved:

EF generates POCO:

public partial class Customer
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

In your viewmodel you can retrieve a List<Customer>() from your database.

public List<Customer> Customers { get; set; } //Load from context in constructor, really though you should do it in a service layer
public Customer CurrentCustomer { get; set; } //Current customer you need to edit

So now you have two options in the viewmodel: In order to be natural MVVM you shouldn't bind CurrentCustomer to the view. That doesn't follow the pattern. In order to fix this, you create separate properties, just like you have above, and you can either return CurrentCustomer.PropertyName in the getter and use that in the setter like so:

public string FirstName
{
    get
    {
        return CurrentCustomer.FirstName;
    }
    set
    {
        CurrentCustomer.FirstName = value;
        OnPropertyChanged("FirstName");
    }
}

By doing this you won't have to worry about mapping the viewmodel property to the POCO property, however any change you make in the view will now cause Entity Framework to track the change, in which case all you need to do now in the save method is call dbContext.SaveChanges();.

If you want to be able to do any kind of cancel or undo then keep your properties set up the way you have it now:

private string _firstName;
public string FirstName
{
    get
    {
        return _firstName;
    }
    set
    {
        _firstName = value;
        OnPropertyChanged("FirstName");
    }
}

and in your save method you will have to map the viewmodel properties to the CurrentCustomer's properties will should then cause a change to be tracked:

private void SaveCustomerChanges()
{
   //Could use Automapper to handle mapping for you.
   CurrentCustomer.FirstName = this.FirstName;
   CurrentCustomer.LastName = this.LastName;
   dbContext.SaveChanges(); //dbContext will be named diff for you
}

At this point the changes should have gone through and updated the database tables.

Now if you're not using Entity Framework, you will need to set up some kind of data access layer to which you can pass in a Customer object and then you will need to retrieve that object from your database and update its values.

Check out this as well!

EDIT: To call SaveCustomerChanges() you will need to wire this method up using ICommand.

First create a RelayCommand Class:

public class RelayCommand : ICommand
{
    private Action<object> _action;

    public RelayCommand(Action<object> action)
    {
        _action = action;
    }

    #region ICommand Members

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        _action(parameter);
    }

    #endregion
}

Now in your viewmodel:

public ICommand SaveChangesCommand { get; set; }

In your viewmodel constructor

SaveChangesCommand = new RelayCommand(SaveCustomerChanges);

Then wire up SaveChangesCommand in XAML:

<Button Content="Save" Command="{Binding SaveChangesCommand }" />

Now when you click Save the command should fire the SaveCustomerChanges() method.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
TMan
  • 4,044
  • 18
  • 63
  • 117
  • First of all thanks for answering my question. I have used entity framework in the past and in this sample project also I have used entity framework and I have mentioned it in my question as well that I generate my models using ADO.net Entity Data Model. Now I will have a cancel button as well. So, I will follow your second approach here. Here you didn't mention how to call SaveCustomerChanges when when I click on Save Button. – Khushi Dec 09 '13 at 02:42
  • Need to use ICommand to wire up your save button the method. Take a look above I posted an example. This can also be used for your cancel button as well. – TMan Dec 09 '13 at 03:10
  • Thank you for your time and hard work. This works perfectly well. – Khushi Dec 09 '13 at 03:17
  • 1
    no problem..check out this link, she is very knowledgeable on WPF and has good articles to read: http://rachel53461.wordpress.com/2011/05/08/simplemvvmexample/ – TMan Dec 09 '13 at 03:26
  • I get an error that some arguments to `SaveCustomerChanges` is not valid in this line `SaveChangesCommand = new RelayCommand(SaveCustomerChanges);`. So I added a parameter of type object to the SaveCustomerChanges method that we created earlier. now it works fine. Now I have another problem. I will update my question in a bit. – Khushi Dec 10 '13 at 11:50
  • @Khushi As far as the RelayCommand problem you were having, try using the one in the link I provided by Rachel53461. She has it set up to where you don't need to have a parameter in your command methods. As far as "What should CurrentCustomer.Gender be?" If your schema is set up correctly, Entity Framework should have created a virtual Gender object to where if you were to change the gender of the customer all you would have to do is say "CurrentCustomer.Gender = ViewModelGenderProperty, does this kind of make sense? – TMan Dec 10 '13 at 14:10
  • Also it sounds like Gender should be an enum. Take a look at a question I asked a while back about how to handle enums in database first. http://stackoverflow.com/questions/17731557/enum-support-for-entity-framework-database-first/17733801#17733801 – TMan Dec 10 '13 at 14:12
  • I don't understand what do you mean by `ViewModelGenderProperty`. And Why should I use enums? I mean I have a table for Gender, so I will have a corresponding class which is created by entity framework then what is the need of enum? – Khushi Dec 11 '13 at 02:37
  • if you have a table for gender that will work as well. It just seems like an overkill to have a lookup table for gender. Your either male..or female..at least I think :P – TMan Dec 13 '13 at 03:08