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); // 希望自动执行此操作 } } }
Database Related Code
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模式:
- 更新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); } } } }
- 更新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>
- 简化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
相关产品推荐
相关产品推荐

