WPF Automatic binding when an EF's entity values change

c# data-binding entity-framework entity-framework-core wpf

Question

I am new to Entity Framework and I'm trying to learn it.

I was trying to modify an exercise found on the official documentation: I would like to have a list of fathers and a list of sons. Every son must have a father selected from a combobox menu.

Now I can do that but, if I add a father, I don't see it in the father list nor in the combobox. If I add a son I don't see the son in the son's list.

If I close and reopen the program I correctly see fathers and sons previously added.

How I can automatically upgrade data in combobox and in the listview?

I don't like to call a function to refresh, I would like to automatically make the refresh when changing something in the database.

My project is made of 3 files:

MainWindow.xaml

<Window x:Class="EF7Fam.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:EF7Fam"
        mc:Ignorable="d"
        Loaded="Page_Loaded"
        Title="MainWindow" Height="350" Width="550">

    <Grid>
        <StackPanel Orientation="Horizontal">
            <StackPanel Width="263" Margin="3 0 3 0">
                <TextBox Name="NewFT"></TextBox>
                <Button Click="Add_Click">Add</Button>
                <ListView Name="Fathers">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </StackPanel>
            <StackPanel Width="263" Margin="3 0 3 0">
                <TextBox Name="NewSN"></TextBox>
                <ComboBox Name="FT" DisplayMemberPath="Name" SelectedValuePath="FTId"/>
                <Button Click="Add_SN_Click">Add</Button>
                <ListView Name="Sons">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

MainWindow.xaml.cs

using Microsoft.Data.Entity;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
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 EF7Fam
{
    /// <summary>
    /// Logica di interazione per MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            using (var db = new Family())
            {
                db.Database.Migrate();
            }
        }

        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            using (var db = new Family())
            {
                Fathers.ItemsSource = db.Fathers.ToList();
                Sons.ItemsSource = db.Sons.ToList();
                FT.ItemsSource = db.Fathers.ToList();
            }
        }

        private void Add_Click(object sender, RoutedEventArgs e)
        {
            using (var db = new Family())
            {
                var ft = new Father { Name = NewFT.Text };
                db.Fathers.Add(ft);
                db.SaveChanges();
            }
        }

        private void Add_SN_Click(object sender, RoutedEventArgs e) 
        {
            using (var db = new Family())
            {
                var sn = new Son { Name = NewSN.Text, FTId = Int32.Parse(FT.SelectedValue.ToString()) };
                db.Sons.Add(sn);
                db.SaveChanges();
            }
        }
    }
}

Model.cs

using System;
using Microsoft.Data.Entity;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Runtime.CompilerServices;

namespace EF7Fam
{

    public class Family : DbContext
    {
        public DbSet<Father> Fathers { get; set; }
        public DbSet<Son> Sons { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite($"Filename=Family.db");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Father>()
                .Property(b => b.Name)
                .IsRequired();
        }
    }

    public class Father : INotifyPropertyChanged
    {
        [Key]
        public int FTId { get; set; }
        public string Name { get; set; }

        public List<Son> Sons { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }


    public class Son : INotifyPropertyChanged
    {
        [Key]
        public int SNId { get; set; }
        public string Name { get; set; }

        public int FTId { get; set; }
        public Father Father { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

I looked many times on this site and in the Web, but without finding a solution.

I will be very grateful if someone can help me to let me know what I doing wrong,

Thanks, bye,

Enrico

Popular Answer

To bound your controls to the DbSets you need to do this:

private void Page_Loaded(object sender, RoutedEventArgs e)
{
        using (var db = new Family())
        {
            db.Fathers.Load();
            Fathers.ItemsSource = db.Fathers.Local;
            db.Sons.Load();
            Sons.ItemsSource = db.Sons.Local;
            FT.ItemsSource = db.Fathers.Local;
        }
}

Calling Load method you are going to load the existing objects into memory and DbSet<TEntity>.Local property will give you an ObservableCollection<TEntity> that contains all Unchanged, Modified and Added objects that are currently tracked by the DbContext for the given DbSet. Adding or Removing from the ObservableCollection will also perform the corresponding Add/Remove on the DbSet.

This way if you need to save changes after perform all the operations that you need in your view, you can define a command or override the click event of a button in your view and call the SaveChanges method of your context:

  private void SaveChanges_Click(object sender, RoutedEventArgs e) 
  {
        using (var db = new Family())
        {
            db.SaveChanges();
        }
  }

Update

Now I saw that you are using EF7, well the true is I'm not aware about all the changes in this version, but I can tell you that there are a lot, maybe this property is not still implemented. I found a question asking about that and is not answered yet.

Digging more in documentation I think I found a solution but the true is I don't like it, but it could work until the Load property shows up. According to the EF documentation you can do this:

private void Add_Click(object sender, RoutedEventArgs e)
{
        using (var db = new Family())
        {
            var ft = new Father { Name = NewFT.Text };
            db.Fathers.Add(ft);
            db.SaveChanges();
            Fathers.ItemsSource = db.Fathers.ToList();
        }
 }

If I find a better solution I'll let you know.



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why