You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WinUI/WPF应用中实现对象变更时自动更新数据库

问题描述

我有一个名为Clients的ObservableCollection,里面存着Client列表并显示在ListView中。现在添加新客户端时,ListView会自动更新,这点没问题,但不会自动把客户端同步到数据库——这正是我要实现的效果。

目前我是在createClientBtn_Click函数里调用ClientData.AddClientData(client);来添加到数据库,但我希望能像UI自动更新那样,通过绑定的方式自动执行这个操作。

请问怎么在ObservableCollection发生变更时触发数据库操作?


Models

public class Order
{
    public int Id { get; set; }
    public float Amount { get; set; }
    public DateTime Date { get; set; }
    public Client Client {get; set;}
}

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

View Models

public class ViewModelBase : ObservableObject {}

public partial class ClientViewModel : ViewModelBase
{
     public ObservableCollection<Client> Clients { get; set; }        
     public ClientViewModel() {}
}

public partial class OrderViewModel: ViewModelBase
{
    public ObservableCollection<Order> Orders { get; set; }
    public OrderViewModel()  {}
}

View (MainWindow.xaml)

<StackPanel x:Name="selectClientsPanel" HorizontalAlignment="Center">
    <ListView x:Name="clientsList" BorderThickness="1" Width="350" Height="Auto" HorizontalAlignment="Left"
   ItemsSource="{Binding Path=Clients, Mode=TwoWay}">
        <ListView.ItemTemplate>
            <DataTemplate x:DataType="model:Client">
                <TextBlock Text="{x:Bind FirstName}" x:Phase="1" Margin="0,5,0,5"/>
                <TextBlock Text="{x:Bind LastName}" x:Phase="1" Margin="0,5,0,5"/>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView> 
    <StackPanel Margin="30">
                <TextBox x:Name="firstNameBox" Header="Enter first name:" PlaceholderText="First Name" />
                <TextBox Name="lastNameBox" Width="250" Margin="0,0,8,0"
  Header="Enter last name:" PlaceholderText="Last Name"/>
                
                <Button x:Name="createClientBtn" Click="createClientBtn_Click">Create Client</Button>
            </StackPanel>   
</StackPanel>

MainWindow.xaml.cs

public sealed partial class MainWindow : Window
{
    public ClientViewModel ViewModel { get; }
    public MainWindow()
    {
        this.InitializeComponent();
        ViewModel = App.GetService<ClientViewModel>();
        clientsList.ItemsSource = ViewModel.Clients;
    }

     private void createClientBtn_Click(Object sender, RoutedEventArgs e)
     {         
        if (firstNameBox.Text.Length > 0 && lastNameBox.Text.Length > 0)
        {
           Client client=new Client(firstNameBox.Text, lastNameBox.Text);
           ViewModel.Clients.Add(client);
           ClientData.AddClientData(client); // 希望自动执行此操作
        }
     }
}
public class DataContext : DbContext
{
     public DbSet<Client> Clients { get; set; }
     public DbSet<Order> Orders { get; set; }
}

public static class ClientData
{
     public static void AddClientData(Client client)
     {
         using (var db = new DataContext())
         {
             db.Add(client);
             db.SaveChanges();
         }
     }

    public static List<Client> GetClients()
    {
        using (var db = new DataContext())
        {        
            return db.Clients.ToList();
        }
    }
}

public static class OrderData
{
     public static void AddOrderData(Order order)
     {
         using (var db = new DataContext())
         {
             db.Add(order);
             db.SaveChanges();
         }
     }
}

解决方案

方法1:自定义ObservableCollection,重写变更逻辑

继承ObservableCollection,重写新增、删除、替换等方法,在内部自动调用数据库操作:

public class DbObservableCollection<T> : ObservableCollection<T>
{
    protected override void InsertItem(int index, T item)
    {
        base.InsertItem(index, item);
        // 根据类型执行对应数据库操作
        if (item is Client client)
        {
            ClientData.AddClientData(client);
        }
        else if (item is Order order)
        {
            OrderData.AddOrderData(order);
        }
    }

    protected override void RemoveItem(int index)
    {
        var item = this[index];
        base.RemoveItem(index);
        // 扩展:处理删除的数据库操作
        if (item is Client client)
        {
            // ClientData.DeleteClientData(client);
        }
    }

    protected override void SetItem(int index, T item)
    {
        var oldItem = this[index];
        base.SetItem(index, item);
        // 扩展:处理更新的数据库操作
        if (oldItem is Client oldClient && item is Client newClient)
        {
            // ClientData.UpdateClientData(newClient);
        }
    }
}

然后在ViewModel中替换原有集合:

public partial class ClientViewModel : ViewModelBase
{
     public DbObservableCollection<Client> Clients { get; set; }        
     public ClientViewModel() 
     {
         Clients = new DbObservableCollection<Client>();
         // 初始化加载数据库已有数据
         var existingClients = ClientData.GetClients();
         foreach(var client in existingClients)
         {
             Clients.Add(client);
         }
     }
}

方法2:订阅CollectionChanged事件

如果不想自定义集合,直接在ViewModel中订阅集合的变更事件,触发数据库操作:

public partial class ClientViewModel : ViewModelBase
{
     public ObservableCollection<Client> Clients { get; set; }        
     public ClientViewModel() 
     {
         Clients = new ObservableCollection<Client>();
         // 加载现有数据
         var existingClients = ClientData.GetClients();
         foreach(var client in existingClients)
         {
             Clients.Add(client);
         }
         // 订阅集合变更事件
         Clients.CollectionChanged += Clients_CollectionChanged;
     }

     private void Clients_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
     {
         // 处理新增项
         if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
         {
             foreach (Client client in e.NewItems)
             {
                 ClientData.AddClientData(client);
             }
         }
         // 扩展:处理删除、替换操作
         else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
         {
             foreach (Client client in e.OldItems)
             {
                 // ClientData.DeleteClientData(client);
             }
         }
     }
}

额外优化:遵循MVVM规范(移除代码后置事件)

把按钮点击逻辑移到ViewModel中,用命令绑定替代代码后置事件,更符合MVVM模式:

  1. 更新ViewModel,添加命令和属性:
using CommunityToolkit.Mvvm.Input;

public partial class ClientViewModel : ViewModelBase
{
     public ObservableCollection<Client> Clients { get; set; }
     public string FirstName { get; set; }
     public string LastName { get; set; }

     public ICommand CreateClientCommand { get; }
        
     public ClientViewModel() 
     {
         Clients = new ObservableCollection<Client>();
         var existingClients = ClientData.GetClients();
         foreach(var client in existingClients)
         {
             Clients.Add(client);
         }
         Clients.CollectionChanged += Clients_CollectionChanged;
         CreateClientCommand = new RelayCommand(ExecuteCreateClient);
     }

     private void ExecuteCreateClient()
     {
         if (!string.IsNullOrEmpty(FirstName) && !string.IsNullOrEmpty(LastName))
         {
             Client client = new Client { FirstName = FirstName, LastName = LastName };
             Clients.Add(client);
             // 无需手动调用AddClientData,CollectionChanged事件会自动处理
         }
     }

     private void Clients_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
     {
         if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
         {
             foreach (Client client in e.NewItems)
             {
                 ClientData.AddClientData(client);
             }
         }
     }
}
  1. 更新XAML绑定:
<TextBox x:Name="firstNameBox" Header="Enter first name:" PlaceholderText="First Name" 
         Text="{Binding FirstName, Mode=TwoWay}" />
<TextBox Name="lastNameBox" Width="250" Margin="0,0,8,0"
         Header="Enter last name:" PlaceholderText="Last Name"
         Text="{Binding LastName, Mode=TwoWay}"/>

<Button x:Name="createClientBtn" Command="{Binding CreateClientCommand}">Create Client</Button>
  1. 简化MainWindow.xaml.cs:
public sealed partial class MainWindow : Window
{
    public ClientViewModel ViewModel { get; }
    public MainWindow()
    {
        this.InitializeComponent();
        ViewModel = App.GetService<ClientViewModel>();
        this.DataContext = ViewModel;
        // 无需手动设置ItemsSource,XAML绑定已处理
    }
}

内容的提问来源于stack exchange,提问作者Nuvolari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 09:19:59