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

本教程创建用于管理客户列表的简单应用。 为此,它会为 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 。 在 Page 标签中添加以下代码行,以声明一个映射到包含数据网格的 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 方法的调用。 这样,它就可以联系存储库来检索 Customer 对象的可枚举集合。 然后,它会将它们分析为单个对象,然后将其添加到其内部 ObservableCollection 中,以便可以显示和编辑它们。

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

客户的初始列表

第 4 部分:编辑客户

可以通过双击来编辑数据网格中的条目,但需要确保在用户界面中所做的任何更改也会同步到后台代码中的客户集合。 这意味着必须实现双向数据绑定。 如果您想了解更多信息,请查看我们的数据绑定简介。

  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 方法使 setter 可以轻松地引发 PropertyChanged 事件,这是双向数据绑定所必需的。

  3. 使用这个函数调用来更新 SelectedCustomer 的 setter:

    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 对象相关联。 TwoWay 绑定模式允许 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 添加新的公共属性,这是 AddNewCustomer 值的反函数。 当面板可见时,这将用于禁用常规命令栏按钮。

    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. 将以下代码行添加为 setter 中 AddingNewCustomer的最后一行:

    OnPropertyChanged(nameof(EnableCommandBar));
    

    每当 AddingNewCustomer 发生更改时,EnableCommandBar 都会被自动更新。

更新 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 调用的函数,然后创建要与之交互的远程数据库。 完整的 客户订单数据库示例 中有代码可以参考,以用于每个必要的操作。

设置和配置

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

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

身份验证

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

REST 调用

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

  • 创建 ICustomerRepositoryITutorialRepository 接口的新实现,通过 REST 而不是 SQLite 实现相同的函数集。 你需要序列化和反序列化 JSON,如果需要,还可以将 REST 调用包装在一个单独的 HttpHelper 类中。 请参阅完整示例 以获取具体内容。
  • App.xaml.cs中,创建一个新函数来初始化 REST 存储库,并在初始化应用时调用它而不是 SqliteDatabase 。 同样,请参阅 的完整示例

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