Click here to Skip to main content
15,867,141 members
Please Sign up or sign in to vote.
5.00/5 (2 votes)
See more:
I have an issue in regards to my application, and not selecting an item for it to get updated using EF.

I'm creating an application for a user to enter in their organisation details. Seems simple enough. After data has been entered using a tab control, I have a view that contains a series of data grids, giving the user the ability to update, or insert another piece of data if required or have the ability to delete.

within my view-model I have a series of different things (which may not be the best way of implementing MVVM but trying to get there eventually).

I have my properties implementing INOTIFYPROPERTYCHANGE. Commands to save, update and delete. And my entity methods (CRUD opererations using Entity Framework - Will use a Repository system later on when I learn more about MVVM etc).

So far, I'm having to use the code-behind of the view to load my data which works perfectly (obviously I shouldn't be doing this so any advice to show me otherwise would be much appreciated) like so;

C#
private void Page_Loaded(object sender, RoutedEventArgs e)
    {
        DBEntities context = new DBEntities();

        var orgTypeDetails = (from o in context.OrganisationTypeDetails
                              select o).ToList();

        dgOrgTypeDetail.ItemsSource = orgTypeDetails;
    }

I also have a event-handler which only allows you to select a row and then to update that specific row;

C#
private void btnUpdateOrgTypeDetail_Click(object sender, RoutedEventArgs e)
    {
        OrganisationTypeDetailViewModel org = new OrganisationTypeDetailViewModel();

        OrganisationTypeDetail selected = dgOrgTypeDetail.SelectedItem as OrganisationTypeDetail;

        if (selected == null)
            MessageBox.Show("You must select a 'Type' before updating.");
        else
        {
            OrganisationTypeDetailUpdateView update = new OrganisationTypeDetailUpdateView();

            update.ShowDialog();
    org.UpdateOrganisationTypeDetail(selected);
            Page_Loaded(null, null);
        }
    }

and finally, my method in which updates the table using EF from View-Model;

C#
public void UpdateOrganisationTypeDetail(OrganisationTypeDetail orgTypeDetail)
    {
        using (DBEntities context = new DBEntities())
        {
            //var orgTD = context.OrganisationTypeDetails.Where(otd => otd.OrganisationTypeDetailID == OrganisationTypeDetailID).FirstOrDefault();

            var orgTD = (from a in context.OrganisationTypeDetails
                         where a.OrganisationTypeDetailID == OrganisationTypeDetailID
                         select a).FirstOrDefault();

            orgTD.OrganisationTypeDetailID = OrganisationTypeDetailID;
            orgTD.OrganisationTypeID = OrganisationTypeID;
            orgTD.Title = Title;
            orgTD.FirstName = FirstName;
            orgTD.Surname = Surname;
            orgTD.Position = Position;
            orgTD.DateOfBirth = DateOfBirth;
            orgTD.Address = Address;
            orgTD.Country = Country;
            orgTD.Postcode = Postcode;
            orgTD.PhoneNumber = PhoneNumber;
            orgTD.MobileNumber = MobileNumber;
            orgTD.FaxNumber = FaxNumber;
            orgTD.Email = Email;
            orgTD.NINumber = NINumber;

            context.OrganisationTypeDetails.ApplyCurrentValues(orgTD);
            context.SaveChanges();

            MessageBox.Show("Updated Organisation Type Details");
        }

When executing this application, it produces a Null Reference exception.

In my properties, to ensure that it doesn't crash, I have had to set programmatically the ID's; public int _OrganisationTypeDetailID=17; But I want to be able to select a row at my choice, rather then only having the option to get the row from 17.

I've tried using both Linq and Lamba expressions but neither seem to work.

Sorry if there's a lot to take in from reading this and would happy add more code or explanation if required :).


------- EDIT-------

Ive changed the way the code loads the data
View-Model;
XML
public List<OrganisationTypeDetail> GetOrganisationTypeDetail
        {
            get
            {
                using (DBEntities context = new DBEntities())
                {
                    var query = from e in context.OrganisationTypeDetails
                                select e;
                    return query.ToList<OrganisationTypeDetail>();
                }
            }
        }


Code-behind:
C#
private void Page_Loaded(object sender, RoutedEventArgs e)
        {
dgOrgTypeDetail.DataContext = new OrganisationTypeDetailViewModel();
}
Posted
Updated 28-Nov-12 5:03am
v4
Comments
bbirajdar 28-Nov-12 8:03am    
which line ?
gregsagan 28-Nov-12 9:38am    
orgTD.OrganisationTypeDetailID = OrganisationTypeDetailID;

ok, so let's start from the beginning:

To get a brief overview of MVVM and it´s keyplayers, i think this article [^]is a good start.
I also recommend this article for a solid base-implementation for INotifyPropertyChanged
[^]
if you are (already) familiar with the basic concepts of DataContext, Binding and Commands, i'll show you how you could implement thos concepts for your solution (using the SelectedItem-approach).
see the bottom of this solution for a complete code-sample, which also covers the ICollectionView-approach.

let´s say you have the following DataGrid defined in "MainV.xaml"

XAML
<datagrid>
    ItemsSource="{Binding Data}"
    SelectedItem="{Binding SelectedItem}" />

</datagrid>


WPF would try to resolve a Data-Property and a SelectedItem-Property on the object that is stored in the DataGrid´s DataContextProperty (and also observes for value-changes through INotifyPropertyChanged).

Let's take a look at the constructor of MainV:

C#
public MainV() {
    InitializeComponent();

    var vm = new MainVM();
    DataContext = vm;

    vm.Load(); 
}


and the definition of MainVM:

C#
    public class MainVM : INotifyPropertyChanged {

	private IEnumerable<dataitem> _Data = null;
	public IEnumerable<dataitem> Data {
	    get {
	        return _Data;
	    }
	    private set {
	        _Data = value;
	        NotifyPropertyChanged("Data");
	    }
	}

	internal void Load() {
	//load dummy data synchronously; 
	//in a real worldapplication this should be done asynchronously 
	//e.g. using the new .net 4.5 async/await keywords
	Data = (from i in Enumerable.Range(1, 100)
		select new DataItem() {
		    Id = i,
		    Name = "Name " + i.ToString()
		}).ToArray();
	}

	private DataItem _SelectedItem;
        public DataItem SelectedItem {
            get {
                return _SelectedItem;
            }
            set {
                _SelectedItem = value;
                NotifyPropertyChanged("SelectedItem");
            }
        }

}


Once Load has finished and the queried data is assigned to the Data-Property, the PropertyChanged-Event is raised, which causes the Binding to it (ItemsSource="{Binding Data}") to re-evaluate.
if a user then clicks on a record, the DataGrid.SelectedItem gets changed. Cause we are binding this property to our ViewModels SelectedItem-Property, this change will automatically be propagated into our ViewModel.

In order to eliminate the Click-eventhandler you would probably use some kind of DelegateCommand (there are lot´s of posts on this topic on the web)...

MainV.xaml:
XAML
<button>
    Content="Show and switch with slected item"
    Command="{Binding ShowAndSwitchWithSelectedItemCommand}" />
</button>


MainVM:
C#
private readonly DelegateCommand _ShowAndSwitchWithSelectedItemCommand;
public ICommand ShowAndSwitchWithSelectedItemCommand {
    get {
        return _ShowAndSwitchWithSelectedItemCommand;
    }
}

public MainVM() {
	_ShowAndSwitchWithSelectedItemCommand = new DelegateCommand(
		p => {
			MessageBox.Show(SelectedItem.Name);
			var newItem = (from item in Data
					where item.Id == 17
					select item).FirstOrDefault();
			SelectedItem = newItem;
		}, 
		p => {
			return SelectedItem != null;
		}
	);
}


Sure, this sample has much optimization potential, but i hope it helps you to find your way into MVVM a little bit better.


Complete code-sample:

MainV.xaml:
XAML
<window>
    x:Class="Earloc.MVVMSample.MainV"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Main View"
    Height="350"
    Width="525">
    <stackpanel>
        <stackpanel>
            Orientation="Horizontal">
            <stackpanel>
                <stackpanel>
                    Orientation="Horizontal">
                    <textblock>
                        Text="selected id:" />
                    <textblock>
                        Text="{Binding SelectedItem.Id}" />
                
                <combobox>
                    ItemsSource="{Binding Data}"
                    SelectedItem="{Binding SelectedItem}"
                    DisplayMemberPath="Name" />
                <button>
                    Content="Show and switch with slected item"
                    Command="{Binding ShowAndSwitchWithSelectedItemCommand}" />
                <datagrid>
                    ItemsSource="{Binding Data}"
                    SelectedItem="{Binding SelectedItem}" />
            
            <stackpanel>
                <stackpanel>
                    Orientation="Horizontal">
                    <textblock>
                        Text="selected id:" />
                    <textblock>
                        Text="{Binding Data/Id}" />
                    <!-- Slash (/) in binding does CollectionViewSource.GetDefaultView(Data).CurrentItem-->
                
                <combobox>
                    IsSynchronizedWithCurrentItem="True"
                    ItemsSource="{Binding Data}"
                    DisplayMemberPath="Name" />
                <button>
                    Content="Show and switch with default collection view"
                    Command="{Binding ShowAndSwitchWithCollectionView}"></button>
                <datagrid>
                    IsSynchronizedWithCurrentItem="True"
                    ItemsSource="{Binding Data}"
                     />
            
        </stackpanel>
    </stackpanel>
</window>


MainV.cs:
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Earloc.MVVMSample {
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainV : Window {
        public MainV() {
            InitializeComponent();

            var vm = new MainVM();
            DataContext = vm;

            vm.Load();
        }
    }
}


MainVM.cs (due to lazyness multiple classes in here):
C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;

namespace Earloc.MVVMSample {

    public class DataItem {
        public string Name { get; set; }
        public int Id { get; set; }
    }

    public class MainVM : INotifyPropertyChanged {

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string AIPropertyName) {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(AIPropertyName));
        }

        private IEnumerable<dataitem> _Data = null;
        public IEnumerable<dataitem> Data {
            get {
                return _Data;
            }
            private set {
                _Data = value;
                NotifyPropertyChanged("Data");
            }
        }

        private ICollectionView _collectionView;

        internal void Load() {
            //load dummy data synchronously; 
            //in a real worldapplication this should be done asynchronously 
            //e.g. using the new .net 4.5 async/await keywords
            Data = (from i in Enumerable.Range(1, 100)
                    select new DataItem() {
                        Id = i,
                        Name = "Name " + i.ToString()
                    }).ToArray();

            _collectionView = CollectionViewSource.GetDefaultView(Data);
            _collectionView.CurrentChanged += OnCurrentChanged; //should be detached when VM is discarded
        }

        private void OnCurrentChanged(object sender, EventArgs e) {
            _ShowAndSwitchWithCollectionView.NotifyCanExecuteChanged();
        }

        private DataItem _SelectedItem;
        public DataItem SelectedItem {
            get {
                return _SelectedItem;
            }
            set {
                _SelectedItem = value;
                NotifyPropertyChanged("SelectedItem");
                _ShowAndSwitchWithSelectedItemCommand.NotifyCanExecuteChanged();
            }
        }

        private readonly DelegateCommand _ShowAndSwitchWithSelectedItemCommand;
        public ICommand ShowAndSwitchWithSelectedItemCommand {
            get {
                return _ShowAndSwitchWithSelectedItemCommand;
            }
        }

        private readonly DelegateCommand _ShowAndSwitchWithCollectionView;
        public ICommand ShowAndSwitchWithCollectionView {
            get {
                return _ShowAndSwitchWithCollectionView;
            }
        }

        public MainVM() {
            _ShowAndSwitchWithSelectedItemCommand = new DelegateCommand(
                p => {
                    MessageBox.Show(SelectedItem.Name);
                    var newItem = (from item in Data
                                   where item.Id == 17
                                   select item).FirstOrDefault();
                    SelectedItem = newItem;
                }, 
                p => {
                    return SelectedItem != null;
                }
            );

            _ShowAndSwitchWithCollectionView = new DelegateCommand(
                p => {
                    var selectedItem = _collectionView.CurrentItem as DataItem;
                    if (selectedItem == null)
                        return;
                    MessageBox.Show(selectedItem.Name);

                    var newItem = (from item in Data
                                   where item.Id == 17
                                   select item).FirstOrDefault();

                    _collectionView.MoveCurrentTo(newItem);
                },
                p => {
                    return _collectionView.CurrentItem != null;
                }
            );
        }
    }

    public class DelegateCommand : ICommand {

        private readonly Action<object>     _Execute;
        private readonly Func<object,> _CanExecute;

        public DelegateCommand(Action<object> AIExecute, Func<object,> AICanExecute) {
            if (AIExecute == null)
                throw new ArgumentNullException("AIExecute");

            _Execute    = AIExecute;
            _CanExecute = AICanExecute;
        }

        public DelegateCommand(Action<object> AIExecute) 
            :this (AIExecute, null) {
        }

        public bool CanExecute(object AIParameter) {
            if (_CanExecute == null)
                return true;

            return _CanExecute(AIParameter);
        }

        public event EventHandler CanExecuteChanged;

        internal void NotifyCanExecuteChanged() {
            var handler = CanExecuteChanged;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }

        public void Execute(object AIParameter) {
            _Execute(AIParameter);
        }
    }

}
 
Share this answer
 
v4
Comments
gregsagan 29-Nov-12 3:49am    
Oh wow, thanks for this! I appreciate the effort of example you've supplied :). Very grateful! Thank you very much, ill give it a go.
i guess
C#
var orgTD = (from a in context.OrganisationTypeDetails
                         where a.OrganisationTypeDetailID == OrganisationTypeDetailID
                         select a).FirstOrDefault();


returns null - resulting in the NullReferenceException at the following line.

where does OrganisationTypeDetailID gets its value from? you said, you had to set it to a specific value through the debugger in order to prevent the crash...so you have to set it to the id of the selected record.

try setting the IsSynchronizedWithCurrentItem[^]-Property of your DataGrid to true. Then, you can obtain an ICollectionView[^] and obtain the selected record from it using the call
C#
var collectionView = CollectionViewSource.GetDefaultView(dgOrgTypeDetail.ItemsSource);
var selected = collectionView.CurrentItem;


(i'm using dgOrgTypeDetail.ItemsSource as the source cause your code does not indicate that your are storing your datasource anywhere else within your ViewModel)

keep in mind, that using ICollectionView does not support multi-selection, as it only keeps track of the "current" item - even when multiple records are selected, only the last selected one is tracked.

btw: if you hav to provide glue code in order to get to know when to load your data, don´t load the data in your view...Set your ViewModel as DataContext and call a load in your ViewModel which will load data and store it in a property of your ViewModel. The View then binds to this property.
e.G:
C#
private void Page_Loaded(object sender, RoutedEventArgs e) {
 var vm = new OrganisationTypeViewModel (); //new class responsible for loading data, etc.
  vm.Load(); //may be asynchronous.
 DataContext = vm;
}


XML
<datagrid issynchronizedwithcurrentitem="true" itemssource="{Binding Data}" />


alternatively you could bind the SelectedItem-Property of the DataGrid to a ViewModel Property and obtain the selected record this way, but i used ICollectionView a lot these days and think it´s quite handy.

With this approach you have minimalistic codebehind in your view!
 
Share this answer
 
v2
Comments
gregsagan 28-Nov-12 9:52am    
Thanks for the reply, much appreciated.

It doesn't get its value from any where which seems to be the problem unless i give it a value programmatically.

Can you show me or give me an example of how to do the SelectedItem? I have seen it quite a few places but don't fully understand how it works. I'm new to WPF, MVVM and entity frameworks, sorry if I'm being a nuisance! Cheers :)
earloc 28-Nov-12 10:18am    
Give me some time and i'll see if can wire up a sample for you. I'm a little bit busy right now...;)
gregsagan 28-Nov-12 10:25am    
I'm in no rush what so ever, so any help is very much appreciated my friend! :)

Just that this issue with the update method hasn't been solved for a week so I'm getting rather aggravated that what I'm doing isn't right.
earloc 28-Nov-12 17:47pm    
Hey!
I provided a second solution...hope it clarifies things up! ;)

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900