教程:创建客户数据库应用程序
本教程创建一个用于管理客户列表的简单应用。 在此过程中,引入了对 UWP 中企业应用基本概念的选择。 将了解如何执行以下操作:
- 针对本地 SQL 数据库执行创建、读取、更新和删除操作。
- 添加一个数据网格,用于在 UI 中显示和编辑客户数据。
- 使用基本窗体布局将 UI 元素排列在一起。
本教程的起点是使用最小 UI 和功能的单页应用,该应用基于简化版本的客户订单数据库示例应用。 它是使用 C# 和 XAML 编写的,因此,我们希望你已经大致熟悉这两种语言。
先决条件
克隆/下载存储库后,可以使用 Visual Studio 打开 CustomerDatabaseTutorial.sln 来编辑项目。
注意
本教程基于最近更新的“客户订单数据库”示例,以便利用 WinUI 和 Windows 应用 SDK。 在本教程和代码更新之前,这两个示例之间存在差异。
第 1 部分:感兴趣的代码
如果在打开应用后立即运行它,将在空白屏幕顶部看到几个按钮。 虽然不可见,但应用已经包含了一个预配的本地 SQLite 数据库,其中包含几个测试客户。 从这里开始,你将首先实现 UI 控件来显示这些客户,然后继续添加针对数据库的操作。 在开始之前,你将在这里操作。
视图
CustomerListPage.xaml 是应用视图,它为本教程中的单个页面定义 UI。 每当需要在 UI 中添加或更改视觉对象时,都可以在这个文件中执行此操作。 本教程将指导你添加以下元素:
- 用于显示和编辑客户的 RadDataGrid。
- 用于设置新客户初始值的 StackPanel。
ViewModels
ViewModels\CustomerListPageViewModel.cs 是应用基本逻辑所在的位置。 在视图中执行的每个用户操作将被传递到此文件进行处理。 在本教程中,你将添加一些新代码并实现以下方法:
- CreateNewCustomerAsync,它初始化新的 CustomerViewModel 对象。
- DeleteNewCustomerAsync,它在 UI 中显示新客户之前删除该客户。
- DeleteAndUpdateAsync,用于处理“删除”按钮的逻辑。
- GetCustomerListAsync,可从数据库中检索客户列表。
- SaveInitialChangesAsync,它将新客户信息添加到数据库中。
- UpdateCustomersAsync,它将刷新 UI,以反映添加或删除的所有客户。
CustomerViewModel 是客户信息包装器,用于跟踪最近是否修改了此信息。 你无需向此类添加任何内容,但你在其他地方添加的一些代码将引用它。
有关如何构造示例的详细信息,请参阅应用结构概述。
第 2 部分:添加 DataGrid
在开始对客户数据进行操作之前,需要添加一个 UI 控件来显示这些客户。 为此,我们将使用预置的第三方 RadDataGrid 控件。 此项目已包含 Telerik.UI.for.UniversalWindowsPlatform NuGet 包。 让我们向项目添加网格。
从解决方案资源管理器中打开 Views\CustomerListPage.xaml。 在页标记中添加以下代码行,以声明指向包含数据网格的 Telerik 命名空间的映射。
xmlns:telerikGrid="using:Telerik.UI.Xaml.Controls.Grid"
在视图的主 RelativePanel 中的 CommandBar 下方,添加一个 RadDataGrid 控件,其中包含一些基本配置选项:
<Grid x:Name="CustomerListRoot" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <RelativePanel> <CommandBar x:Name="mainCommandBar" HorizontalAlignment="Stretch" Background="AliceBlue"> <!--CommandBar content--> </CommandBar> <telerikGrid:RadDataGrid x:Name="DataGrid" BorderThickness="0" ColumnDataOperationsMode="Flyout" GridLinesVisibility="None" GroupPanelPosition="Left" RelativePanel.AlignLeftWithPanel="True" RelativePanel.AlignRightWithPanel="True" RelativePanel.Below="mainCommandBar" /> </RelativePanel> </Grid>
你已经添加了数据网格,但需要显示数据。 向其添加以下代码行:
ItemsSource="{x:Bind ViewModel.Customers}" UserEditMode="Inline"
定义要显示的数据源后,RadDataGrid 将为你处理大部分 UI 逻辑。 但是,如果你运行的是你的项目,则在显示时仍看不到任何数据。 这是因为 ViewModel 尚未加载数据。
第 3 部分:读取客户
初始化后,ViewModels\CustomerListPageViewModel.cs 将调用 GetCustomerListAsync 方法。 该方法需要从该教程附带的 SQLite 数据库中检索测试客户数据。
在 ViewModels\CustomerListPageViewModel.cs 中,将 GetCustomerListAsync 方法更新为以下代码:
public async Task GetCustomerListAsync() { var customers = await App.Repository.Customers.GetAsync(); if (customers == null) { return; } await DispatcherHelper.ExecuteOnUIThreadAsync(() => { Customers.Clear(); foreach (var c in customers) { Customers.Add(new CustomerViewModel(c)); } }); }
在加载 ViewModel 时调用 GetCustomerListAsync 方法,但在此步骤之前,它不执行任何操作。 在这里,我们添加了对 Repository/SqlCustomerRepository 中 GetAsync 方法的调用。 这允许它与存储库联系以检索客户对象的可枚举集合。 然后,将它们分析为单独的对象,随后将它们添加到内部 ObservableCollection 中,以便可以显示和编辑它们。
运行应用 - 现在会看到显示客户列表的数据网格。
第 4 部分:编辑客户
你可以通过双击数据网格中的条目来对其进行编辑,但需要确保在 UI 中进行的任何更改也会在代码隐藏中应用于客户集合。 这意味着必须实现双向数据绑定。 如果需要相关详细信息,请查看我们的数据绑定简介。
首先,声明 ViewModels\CustomerListPageViewModel.cs 实现 INotifyPropertyChanged 接口:
public class CustomerListPageViewModel : INotifyPropertyChanged
然后,在类的主体中,添加以下事件和方法:
public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
利用 OnPropertyChanged 方法,你可以轻松地让资源库引发 PropertyChanged 事件,这对于双向数据绑定是必需的。
通过此函数调用更新 SelectedCustomer 的资源库:
public CustomerViewModel SelectedCustomer { get => _selectedCustomer; set { if (_selectedCustomer != value) { _selectedCustomer = value; OnPropertyChanged(); } } }
在 Views\CustomerListPage.xaml 中,将 SelectedCustomer 属性添加到数据网格。
SelectedItem="{x:Bind ViewModel.SelectedCustomer, Mode=TwoWay}"
这会将数据网格中用户的选择与代码隐藏中的相应 Customer 对象关联。 双向绑定模式允许在 UI 中进行的更改反映在该对象上。
运行应用。 现在,可以看到网格中显示的客户,并通过 UI 对基础数据进行更改。
第 5 部分:更新客户
现在你可以查看和编辑客户,你需要将更改推送到数据库,并提取其他人所做的任何更新。
返回到 ViewModels\CustomerListPageViewModel.cs,并导航到 UpdateCustomersAsync 方法。 将其更新为以下代码,以将更改推送到数据库并检索任何新信息:
public async Task UpdateCustomersAsync() { foreach (var modifiedCustomer in Customers .Where(x => x.IsModified).Select(x => x.Model)) { await App.Repository.Customers.UpsertAsync(modifiedCustomer); } await GetCustomerListAsync(); }
此代码使用 ViewModels\CustomerViewModel.cs 的 IsModified 属性,每当客户发生更改时,它都会自动更新。 这样可以避免不必要的调用,并且仅将更改从已更新的客户推送到数据库。
第 6 部分:创建新客户
添加新客户是一个挑战,因为如果你在为其属性提供值之前将客户添加到 UI 中,则客户将显示为空白行。 这并不是问题,但在这里,我们可以更轻松地设置客户的初始值。 在本教程中,我们将添加一个简单的可折叠面板,但如果要添加更多信息,可以创建一个单独的页面来实现此目的。
更新代码隐藏
向 ViewModels\CustomerListPageViewModel.cs 添加新的私有字段和公共属性。 这将用于控制面板是否可见。
private bool _addingNewCustomer = false; public bool AddingNewCustomer { get => _addingNewCustomer; set { if (_addingNewCustomer != value) { _addingNewCustomer = value; OnPropertyChanged(); } } }
将一个新的公共属性添加到 ViewModel,它是 AddingNewCustomer 值的逆值。 这将用于在面板可见时禁用常规命令栏按钮。
public bool EnableCommandBar => !AddingNewCustomer;
现在,你需要一种方法来显示可折叠面板,并创建要在其中进行编辑的客户。
向 ViewModel 添加新的私有字段和公共属性,以保存新创建的客户。
private CustomerViewModel _newCustomer; public CustomerViewModel NewCustomer { get => _newCustomer; set { if (_newCustomer != value) { _newCustomer = value; OnPropertyChanged(); } } }
更新 CreateNewCustomerAsync 方法以创建新客户,将其添加到存储库,并设置为所选客户:
public async Task CreateNewCustomerAsync() { CustomerViewModel newCustomer = new CustomerViewModel(new Models.Customer()); NewCustomer = newCustomer; await App.Repository.Customers.UpsertAsync(NewCustomer.Model); AddingNewCustomer = true; }
更新 SaveInitialChangesAsync 方法,将新创建的客户添加到存储库,更新 UI,然后关闭面板。
public async Task SaveInitialChangesAsync() { await App.Repository.Customers.UpsertAsync(NewCustomer.Model); await UpdateCustomersAsync(); AddingNewCustomer = false; }
将以下代码行添加为 AddingNewCustomer 资源库中的最后一行:
OnPropertyChanged(nameof(EnableCommandBar));
这将确保每当更改 AddingNewCustomer 时 EnableCommandBar 都会自动更新。
更新 UI
导航回 Views\CustomerListPage.xaml,并在 CommandBar 和数据网格之间添加具有以下属性的 StackPanel:
<StackPanel x:Name="newCustomerPanel" Orientation="Horizontal" x:Load="{x:Bind ViewModel.AddingNewCustomer, Mode=OneWay}" RelativePanel.Below="mainCommandBar"> </StackPanel>
x:Load 属性确保在添加新客户时仅显示此面板。
对数据网格的位置进行以下更改,以确保它在新面板出现时向下移动:
RelativePanel.Below="newCustomerPanel"
使用四个 TextBox 控件更新堆栈面板。 它们将绑定到新客户的各个属性,并允许在将其添加到数据网格之前编辑值。
<StackPanel x:Name="newCustomerPanel" Orientation="Horizontal" x:Load="{x:Bind ViewModel.AddingNewCustomer, Mode=OneWay}" RelativePanel.Below="mainCommandBar"> <TextBox Header="First name" PlaceholderText="First" Margin="8,8,16,8" MinWidth="120" Text="{x:Bind ViewModel.NewCustomer.FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> <TextBox Header="Last name" PlaceholderText="Last" Margin="0,8,16,8" MinWidth="120" Text="{x:Bind ViewModel.NewCustomer.LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> <TextBox Header="Address" PlaceholderText="1234 Address St, Redmond WA 00000" Margin="0,8,16,8" MinWidth="280" Text="{x:Bind ViewModel.NewCustomer.Address, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> <TextBox Header="Company" PlaceholderText="Company" Margin="0,8,16,8" MinWidth="120" Text="{x:Bind ViewModel.NewCustomer.Company, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> </StackPanel>
将一个简单的按钮添加到新堆栈面板,以保存新创建的客户:
<StackPanel> <!--Text boxes from step 3--> <AppBarButton x:Name="SaveNewCustomer" Click="{x:Bind ViewModel.SaveInitialChangesAsync}" Icon="Save"/> </StackPanel>
更新 CommandBar,以便在堆栈面板可见时禁用常规的“创建”、“删除”和“更新”按钮:
<CommandBar x:Name="mainCommandBar" HorizontalAlignment="Stretch" IsEnabled="{x:Bind ViewModel.EnableCommandBar, Mode=OneWay}" Background="AliceBlue"> <!--App bar buttons--> </CommandBar>
运行应用。 现在可以创建客户,并在堆栈面板中输入数据。
第 7 部分:删除客户
删除客户是需要实现的最后一个基本操作。 删除在数据网格中选定的客户时,需要立即调用 UpdateCustomersAsync 以更新 UI。 但是,如果要删除刚创建的客户,则无需调用该方法。
导航到 ViewModels\CustomerListPageViewModel.cs,并更新 DeleteAndUpdateAsync 方法:
public async void DeleteAndUpdateAsync() { if (SelectedCustomer != null) { await App.Repository.Customers.DeleteAsync(_selectedCustomer.Model.Id); } await UpdateCustomersAsync(); }
在 Views\CustomerListPage.xaml 中,更新堆栈面板以添加新客户,以便它包含第二个按钮:
<StackPanel> <!--Text boxes for adding a new customer--> <AppBarButton x:Name="DeleteNewCustomer" Click="{x:Bind ViewModel.DeleteNewCustomerAsync}" Icon="Cancel"/> <AppBarButton x:Name="SaveNewCustomer" Click="{x:Bind ViewModel.SaveInitialChangesAsync}" Icon="Save"/> </StackPanel>
在 ViewModels\CustomerListPageViewModel.cs 中,更新 DeleteNewCustomerAsync 方法以删除新客户:
public async Task DeleteNewCustomerAsync() { if (NewCustomer != null) { await App.Repository.Customers.DeleteAsync(_newCustomer.Model.Id); AddingNewCustomer = false; } }
运行应用。 现在可以在数据网格或堆栈面板中删除客户。
结束语
祝贺你! 完成所有这些操作后,应用现在具有各种本地数据库操作。 可以在 UI 内创建、读取、更新和删除客户,这些更改将保存到数据库,并在应用的不同启动过程中保留。
完成操作后,请考虑以下事项:
- 如果尚未准备好,请查看应用结构概述,详细了解应用这样构建的原因。
- 浏览完整的客户订单数据库示例,查看本教程所基于的应用。
或者如果你准备好迎接挑战,你可以继续…
进一步操作:连接到远程数据库
我们提供了如何针对本地 SQLite 数据库实现这些调用的分步演练。 但是,如果要使用远程数据库呢?
如果要尝试此操作,则需要自己的 Azure Active Directory (AAD) 帐户以及托管你自己数据源的能力。
需要添加身份验证、用于处理 REST 调用的函数,然后创建要与之交互的远程数据库。 完整客户订单数据库示例中提供了代码,可供每个必要的操作引用。
设置和配置
示例自述文件中详细说明了连接到自己的远程数据库所需的步骤。 你将需要执行以下操作:
- 向 Constants.cs 提供 Azure 帐户客户端 ID。
- 向 Constants.cs 提供远程数据库的 URL。
- 向 Constants.cs 提供数据库的连接字符串。
- 将应用与 Microsoft Store 关联。
- 将服务项目复制到应用,并将其部署到 Azure。
身份验证
需要创建一个按钮来启动身份验证序列,并创建一个弹出窗口或单独的页面来收集用户信息。 创建后,需要提供请求用户信息的代码,并使用它获取访问令牌。 客户订单数据库示例将 Microsoft Graph 调用与 WebAccountManager 库包装在一起,以获取令牌并处理对 AAD 帐户的身份验证。
- 身份验证逻辑在 AuthenticationViewModel.cs 中实现。
- 身份验证过程显示在自定义 AuthenticationControl.xaml 控件中。
REST 调用
为了实现 REST 调用,无需修改本教程中添加的任何代码。 相反,需要执行以下操作:
- 创建 ICustomerRepository 和 ITutorialRepository 接口的新实现,通过 REST(而不是 SQLite)实现一组相同的函数。 需要序列化并反序列化 JSON,并可以在需要时将 REST 调用包装在单独的 HttpHelper 类中。 有关详细信息,请参阅完整示例。
- 在 App.xaml.cs 中,创建一个新函数来初始化 REST 存储库,并在初始化应用时调用它,而不是 SqliteDatabase。 同样,请参阅完整示例。
完成上述所有三个步骤后,应能够通过应用对 AAD 帐户进行身份验证。 对远程数据库的 REST 调用将替换本地 SQLite 调用,但用户体验应相同。 如果需要,可以添加设置页,允许用户在两者之间动态切换。