教程:创建客户数据库应用程序

本教程创建一个用于管理客户列表的简单应用。 在此过程中,引入了对 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 包。 让我们向项目添加网格。

  1. 从解决方案资源管理器中打开 Views\CustomerListPage.xaml。 在页标记中添加以下代码行,以声明指向包含数据网格的 Telerik 命名空间的映射。

        xmlns:telerikGrid="using:Telerik.UI.Xaml.Controls.Grid"
    
  2. 在视图的主 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>
    
  3. 你已经添加了数据网格,但需要显示数据。 向其添加以下代码行:

    ItemsSource="{x:Bind ViewModel.Customers}"
    UserEditMode="Inline"
    

    定义要显示的数据源后,RadDataGrid 将为你处理大部分 UI 逻辑。 但是,如果你运行的是你的项目,则在显示时仍看不到任何数据。 这是因为 ViewModel 尚未加载数据。

空白应用,无客户

第 3 部分:读取客户

初始化后,ViewModels\CustomerListPageViewModel.cs 将调用 GetCustomerListAsync 方法。 该方法需要从该教程附带的 SQLite 数据库中检索测试客户数据。

  1. 在 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/SqlCustomerRepositoryGetAsync 方法的调用。 这允许它与存储库联系以检索客户对象的可枚举集合。 然后,将它们分析为单独的对象,随后将它们添加到内部 ObservableCollection 中,以便可以显示和编辑它们。

  2. 运行应用 - 现在会看到显示客户列表的数据网格。

初始客户列表

第 4 部分:编辑客户

你可以通过双击数据网格中的条目来对其进行编辑,但需要确保在 UI 中进行的任何更改也会在代码隐藏中应用于客户集合。 这意味着必须实现双向数据绑定。 如果需要相关详细信息,请查看我们的数据绑定简介

  1. 首先,声明 ViewModels\CustomerListPageViewModel.cs 实现 INotifyPropertyChanged 接口:

    public class CustomerListPageViewModel : INotifyPropertyChanged
    
  2. 然后,在类的主体中,添加以下事件和方法:

    public event PropertyChangedEventHandler PropertyChanged;
    
    public void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    

    利用 OnPropertyChanged 方法,你可以轻松地让资源库引发 PropertyChanged 事件,这对于双向数据绑定是必需的。

  3. 通过此函数调用更新 SelectedCustomer 的资源库:

    public CustomerViewModel SelectedCustomer
    {
        get => _selectedCustomer;
        set
        {
            if (_selectedCustomer != value)
            {
                _selectedCustomer = value;
                OnPropertyChanged();
            }
        }
    }
    
  4. 在 Views\CustomerListPage.xaml 中,将 SelectedCustomer 属性添加到数据网格。

    SelectedItem="{x:Bind ViewModel.SelectedCustomer, Mode=TwoWay}"
    

    这会将数据网格中用户的选择与代码隐藏中的相应 Customer 对象关联。 双向绑定模式允许在 UI 中进行的更改反映在该对象上。

  5. 运行应用。 现在,可以看到网格中显示的客户,并通过 UI 对基础数据进行更改。

在数据网格中编辑客户

第 5 部分:更新客户

现在你可以查看和编辑客户,你需要将更改推送到数据库,并提取其他人所做的任何更新。

  1. 返回到 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.csIsModified 属性,每当客户发生更改时,它都会自动更新。 这样可以避免不必要的调用,并且仅将更改从已更新的客户推送到数据库。

第 6 部分:创建新客户

添加新客户是一个挑战,因为如果你在为其属性提供值之前将客户添加到 UI 中,则客户将显示为空白行。 这并不是问题,但在这里,我们可以更轻松地设置客户的初始值。 在本教程中,我们将添加一个简单的可折叠面板,但如果要添加更多信息,可以创建一个单独的页面来实现此目的。

更新代码隐藏

  1. 向 ViewModels\CustomerListPageViewModel.cs 添加新的私有字段和公共属性。 这将用于控制面板是否可见。

    private bool _addingNewCustomer = false;
    
    public bool AddingNewCustomer
    {
        get => _addingNewCustomer;
        set
        {
            if (_addingNewCustomer != value)
            {
                _addingNewCustomer = value;
                OnPropertyChanged();
            }
        }
    }
    
  2. 将一个新的公共属性添加到 ViewModel,它是 AddingNewCustomer 值的逆值。 这将用于在面板可见时禁用常规命令栏按钮。

    public bool EnableCommandBar => !AddingNewCustomer;
    

    现在,你需要一种方法来显示可折叠面板,并创建要在其中进行编辑的客户。

  3. 向 ViewModel 添加新的私有字段和公共属性,以保存新创建的客户。

    private CustomerViewModel _newCustomer;
    
    public CustomerViewModel NewCustomer
    {
        get => _newCustomer;
        set
        {
            if (_newCustomer != value)
            {
                _newCustomer = value;
                OnPropertyChanged();
            }
        }
    }
    
  4. 更新 CreateNewCustomerAsync 方法以创建新客户,将其添加到存储库,并设置为所选客户:

    public async Task CreateNewCustomerAsync()
    {
        CustomerViewModel newCustomer = new CustomerViewModel(new Models.Customer());
        NewCustomer = newCustomer;
        await App.Repository.Customers.UpsertAsync(NewCustomer.Model);
        AddingNewCustomer = true;
    }
    
  5. 更新 SaveInitialChangesAsync 方法,将新创建的客户添加到存储库,更新 UI,然后关闭面板。

    public async Task SaveInitialChangesAsync()
    {
        await App.Repository.Customers.UpsertAsync(NewCustomer.Model);
        await UpdateCustomersAsync();
        AddingNewCustomer = false;
    }
    
  6. 将以下代码行添加为 AddingNewCustomer 资源库中的最后一行:

    OnPropertyChanged(nameof(EnableCommandBar));
    

    这将确保每当更改 AddingNewCustomerEnableCommandBar 都会自动更新。

更新 UI

  1. 导航回 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 属性确保在添加新客户时仅显示此面板。

  2. 对数据网格的位置进行以下更改,以确保它在新面板出现时向下移动:

    RelativePanel.Below="newCustomerPanel"
    
  3. 使用四个 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>
    
  4. 将一个简单的按钮添加到新堆栈面板,以保存新创建的客户:

    <StackPanel>
        <!--Text boxes from step 3-->
        <AppBarButton
            x:Name="SaveNewCustomer"
            Click="{x:Bind ViewModel.SaveInitialChangesAsync}"
            Icon="Save"/>
    </StackPanel>
    
  5. 更新 CommandBar,以便在堆栈面板可见时禁用常规的“创建”、“删除”和“更新”按钮:

    <CommandBar
        x:Name="mainCommandBar"
        HorizontalAlignment="Stretch"
        IsEnabled="{x:Bind ViewModel.EnableCommandBar, Mode=OneWay}"
        Background="AliceBlue">
        <!--App bar buttons-->
    </CommandBar>
    
  6. 运行应用。 现在可以创建客户,并在堆栈面板中输入数据。

创建新客户

第 7 部分:删除客户

删除客户是需要实现的最后一个基本操作。 删除在数据网格中选定的客户时,需要立即调用 UpdateCustomersAsync 以更新 UI。 但是,如果要删除刚创建的客户,则无需调用该方法。

  1. 导航到 ViewModels\CustomerListPageViewModel.cs,并更新 DeleteAndUpdateAsync 方法:

    public async void DeleteAndUpdateAsync()
    {
        if (SelectedCustomer != null)
        {
            await App.Repository.Customers.DeleteAsync(_selectedCustomer.Model.Id);
        }
        await UpdateCustomersAsync();
    }
    
  2. 在 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>
    
  3. 在 ViewModels\CustomerListPageViewModel.cs 中,更新 DeleteNewCustomerAsync 方法以删除新客户:

    public async Task DeleteNewCustomerAsync()
    {
        if (NewCustomer != null)
        {
            await App.Repository.Customers.DeleteAsync(_newCustomer.Model.Id);
            AddingNewCustomer = false;
        }
    }
    
  4. 运行应用。 现在可以在数据网格或堆栈面板中删除客户。

删除新客户

结束语

祝贺你! 完成所有这些操作后,应用现在具有各种本地数据库操作。 可以在 UI 内创建、读取、更新和删除客户,这些更改将保存到数据库,并在应用的不同启动过程中保留。

完成操作后,请考虑以下事项:

或者如果你准备好迎接挑战,你可以继续…

进一步操作:连接到远程数据库

我们提供了如何针对本地 SQLite 数据库实现这些调用的分步演练。 但是,如果要使用远程数据库呢?

如果要尝试此操作,则需要自己的 Azure Active Directory (AAD) 帐户以及托管你自己数据源的能力。

需要添加身份验证、用于处理 REST 调用的函数,然后创建要与之交互的远程数据库。 完整客户订单数据库示例中提供了代码,可供每个必要的操作引用。

设置和配置

示例自述文件中详细说明了连接到自己的远程数据库所需的步骤。 你将需要执行以下操作:

  • Constants.cs 提供 Azure 帐户客户端 ID。
  • Constants.cs 提供远程数据库的 URL。
  • Constants.cs 提供数据库的连接字符串。
  • 将应用与 Microsoft Store 关联。
  • 服务项目复制到应用,并将其部署到 Azure。

身份验证

需要创建一个按钮来启动身份验证序列,并创建一个弹出窗口或单独的页面来收集用户信息。 创建后,需要提供请求用户信息的代码,并使用它获取访问令牌。 客户订单数据库示例将 Microsoft Graph 调用与 WebAccountManager 库包装在一起,以获取令牌并处理对 AAD 帐户的身份验证。

REST 调用

为了实现 REST 调用,无需修改本教程中添加的任何代码。 相反,需要执行以下操作:

  • 创建 ICustomerRepository 和 ITutorialRepository 接口的新实现,通过 REST(而不是 SQLite)实现一组相同的函数。 需要序列化并反序列化 JSON,并可以在需要时将 REST 调用包装在单独的 HttpHelper 类中。 有关详细信息,请参阅完整示例
  • 在 App.xaml.cs 中,创建一个新函数来初始化 REST 存储库,并在初始化应用时调用它,而不是 SqliteDatabase。 同样,请参阅完整示例

完成上述所有三个步骤后,应能够通过应用对 AAD 帐户进行身份验证。 对远程数据库的 REST 调用将替换本地 SQLite 调用,但用户体验应相同。 如果需要,可以添加设置页,允许用户在两者之间动态切换。