[英]Entity Framework with Prism MVVM: How to get an auto incremented id to a CollectionViewSource?
我是 EF 新手,正在尝试使用 Prism MVVM 运行 EF Core Getting Started with WPF教程。
我目前遇到了一个丑陋的解决方案,以便在按下 Save 按钮后将插入项目的自动递增 id (sqlite) 反映回 DataGrid。 更新:我后来发现以这种方式完成所有排序和过滤都会丢失。
在非 mvvm 教程中,这是通过调用productsDataGrid.Items.Refresh()
来完成的。 这很好用:
private void Button_Click(object sender, RoutedEventArgs e)
{
_context.SaveChanges();
productsDataGrid.Items.Refresh();
}
目前对我有用
的唯一解决方案
(更新:请参阅下文以获得更好的解决方案。)是将 ObservableCollection 设置为 null,然后在我的Save()
function 中调用context.SaveChanges()
后将其重新分配给数据库上下文。
这是工作代码(丢弃过滤和排序):
主窗口.xaml
<Window x:Class="EfTestPrism.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:EfTestPrism"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:MainWindowViewModel, IsDesignTimeCreatable=True}"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<CollectionViewSource x:Key="CategoryViewSource"
Source="{Binding CategoriesCollection}"/>
</Window.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{Binding WindowCloseCommand, Mode=OneTime}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid Grid.Row="0"
AutoGenerateColumns="False"
RowDetailsVisibilityMode="VisibleWhenSelected"
ItemsSource="{Binding
Source={StaticResource CategoryViewSource}}">
<DataGrid.Columns>
<DataGridTextColumn Header="Category Id"
Width="SizeToHeader"
IsReadOnly="True"
Binding="{Binding CategoryId}"/>
<DataGridTextColumn Header="Name"
Width="*"
Binding="{Binding Name}"/>
</DataGrid.Columns>
</DataGrid>
<Button Grid.Row="1"
Content="Save"
Command="{Binding SaveCommand}"/>
</Grid>
</Window>
MainWindow.xaml.cs:
namespace EfTestPrism;
public partial class MainWindow
{
public MainWindow() {
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
MainWindowViewModel.cs
using System.Collections.ObjectModel;
using System.Windows.Input;
using Microsoft.EntityFrameworkCore;
using Prism.Commands;
using Prism.Mvvm;
namespace EfTestPrism;
public class MainWindowViewModel : BindableBase
{
public MainWindowViewModel() {
context.Database.EnsureCreated();
context.Categories!.Load();
CategoriesCollection = context.Categories!.Local.ToObservableCollection();
}
private readonly ProductContext context = new ();
private ObservableCollection<Category> ? categoriesCollection;
public ObservableCollection<Category> ? CategoriesCollection {
get => categoriesCollection!;
set => SetProperty(ref categoriesCollection, value);
}
public ICommand SaveCommand => new DelegateCommand(Save);
private void Save() {
context.SaveChanges();
/* I don't like the following but it works.
I tried different things here, see below. */
CategoriesCollection = null;
CategoriesCollection = context.Categories!.Local.ToObservableCollection();
}
public ICommand WindowCloseCommand => new DelegateCommand(WindowClose);
private void WindowClose() {
context.Dispose();
}
}
ProductContext.cs
using Microsoft.EntityFrameworkCore;
namespace EfTestPrism;
public class ProductContext : DbContext
{
public DbSet<Category> ? Categories { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options) {
options.UseSqlite("Data Source=products.db");
options.UseLazyLoadingProxies();
}
}
类别.cs
namespace EfTestPrism;
public class Category // I tried making this a BindableBase and...
{
public int CategoryId { get; set; } // ...using SetProperty without success
public string Name { get; set; }
}
ViewModel::Save() function:
RaisePropertyChanged(nameof(CategoriesCollection)
.
foreach (var item in CategoriesCollection) {
RaisePropertyChanged(nameof(item.CategoryId));
RaisePropertyChanged(nameof(item));
}
.
foreach (var item in oc) {
var temp = item.CategoryId;
item.CategoryId = 0;
item.CategoryId = temp;
}
主窗口.xaml:
UpdateSourceTrigger
。 我可以看到集合发生了变化。 当我删除 DataGrids CategoryId 列上的IsReadonly="True"
时,只要在保存后双击它,该值就会更新(我不知道 UI 是刚刚更新还是它实际上与数据库同步)。
类似于categoryDataGrid.Items.Refresh();
更新 DataGrid 的正确 mvvm 方法是什么? 在教程的Button_Click
function 中的_context.SaveChanges()
之后调用?
以下工作并继续排序和过滤。 我不太介意背后的代码,因为它与视图严格相关,我认为这是可以接受的。
优点:无需手动执行将项目删除和添加回集合,即最少的工作代码(如果没有更好的解决方案)。
缺点:视图 model 必须调用委托。 所以它实际上必须预料到它使用的视图可能想要提供回调。
对上述代码的更改:
MainWindow.xaml:将x:Name
添加到DataGrid
以使其可从后面的代码访问:
[...]
<DataGrid Grid.Row="0"
x:Name="CategoriesDataGrid"
AutoGenerateColumns="False"
[...]
将delegate
添加到 MainWindowViewModel.cs 并在Save()
中调用它:
[...]
public delegate void Callback();
public class MainWindowViewModel : BindableBase
{
public MainWindowViewModel(Callback ? refreshView = null) {
RefreshView = refreshView;
[...]
private readonly Callback ? RefreshView;
[...]
private void Save() {
context.SaveChanges();
RefreshView?.Invoke();
}
[...]
在MainWindow.xaml.cs
中实现并提供RefreshView
方法:
namespace EfTestPrism;
public partial class MainWindow
{
public MainWindow() {
InitializeComponent();
DataContext = new MainWindowViewModel(RefreshView);
}
private void RefreshView() {
CategoriesDataGrid.Items.Refresh();
}
}
除非 EF 为您设置 memory 中现有项目的属性,否则您需要从数据库中加载新的 id 并自行更新Category
项目的CategoryId
属性。
最简单的方法是在调用SaveChanges()
后基本上执行与构造函数中相同的操作,例如将集合属性设置为包含数据库中新项目的新集合:
private void Save() {
context.SaveChanges();
CategoriesCollection = new ObservableCollection<Category>(context.Categories);
}
然后你应该得到一个包含新的更新Category
项目的新集合。
或者,您可以Clear()
现有集合中的项目,然后将数据库中的新项目添加到其中:
private void Save()
{
context.SaveChanges();
CategoriesCollection.Clear();
foreach (var category in context.Categories.ToArray())
CategoriesCollection.Add(category);
}
您想要的是 map 您的实体( Category
)以查看模型(从BindableBase
派生)并在实体更改时手动更新视图模型(包括在实体集合更改时更新视图 model 集合)。
这样,当您更新项目时,数据网格会保持其排序、过滤、滚动位置等。
RaisePropertyChanged(nameof(CategoryId))
仅在从类别内部调用时起作用 - 它在其实例上引发PropertyChanged
事件 - 即MainWindowViewModel
并解释为什么数据网格不关心并且不更新其单元格。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.