2017 年 12 月

第 32 卷,第 12 期

数据点 - 生成用于本地和云数据存储的 UWP 应用

作者 Julie Lerman

Julie Lerman本文是由多篇文章组成的系列文章中的第一篇,为你展示如何在运行通用 Windows 平台 (UWP) 应用程序的设备上存储数据,以及如何将应用程序的数据存储在云中。本文将重点介绍使用 Entity Framework Core (EF Core) 和 SQLite 数据库进行本地存储。随后的文章将增加使用 Azure Functions 和 Azure Cosmos DB 将 UWP 应用程序数据存储和检索到云中的功能介绍。

在 EF Core 的早期阶段,当它仍然被称为 EF7 时,我利用了它的新功能不仅能在完整的 .NET Framework 安装程序上运行,还能在设备上运行。在 EF7 的第一个 Pluralsight 课程中(设计为正在生成的功能预览),我创建了一个称为 Cookie Binge 的小游戏。它在 Windows Phone 8 上运行,并可作为 Windows 8 的 Windows 应用商店应用程序,使用 EF7 和 SQLite 在本地存储数据。该游戏是 EF 团队构建的一个演示应用程序(专注于捕捉独角兽)的衍生产品。你可以在 CookieBinge 游戏中吃饼干(单击饼干即可),猛吃一通后,你需要表明这种疯狂行为是非常值得的,还是对吃掉所有饼干感到内疚。你消费的饼干数量会变成你的分数,你选择的“值得”或“内疚”与游戏历史记录中的分数相关联。这是一个傻瓜游戏,没有真正的目标,只是探索如何将 EF 7/Core 纳入到移动应用程序。

在 2016 年 EF Core 发布时,我对这个游戏进行了改进,使其作为通用 Windows 平台 (UWP) 应用程序运行,你可以在运行 Windows 10 的任何设备上玩这个游戏。最近发布的 EF Core 2.0 现在依赖于 .NET Standard 2.0。最新版本的 UWP 针对刚刚发布的 Windows 10 Fall Creators Update,也依赖于 .NET Standard 2.0,可让你在新一代的 UWP 应用程序中使用 EF Core 2.0。

这意味着现在是时候再次更新 CookieBinge 应用程序了,这次是更新到 EF Core 2.0。唯一的方法就是也针对新版 Windows 10 更新 UWP 应用程序。请记住,我的重点是数据访问,而不是设计应用程序的 UI 以从 Windows 10 的功能中受益。另外,我在本例中不会涉及 EF Core 2.0 的新功能。相反,我会考虑可以将这两种技术结合起来的功能。

我将从头开始构建应用程序,而不是更新现有的应用程序,以便你可以照着做。将游戏更新到 EF Core 2.0 后,我将添加一项新功能以便支持与其他游戏玩家分享分数。为此,除了在游戏设备本地存储分数之外,该应用程序还将利用 Azure Functions 和 Azure Cosmos DB 以使用户可以在全球范围内分享分数。我将在本系列的后续文章中完成这些任务。

你的开发环境

尽管 EF Core 可以在跨平台环境下构建和运行,但 UWP 是一个纯 Windows 平台,所以你需要在 Windows 计算机上完成这项工作。具体而言,你需要安装 Windows 10 Fall Creators Update 和 Visual Studio 2017 版本 15.4(或更高版本)。Visual Studio 不会安装 .NET Standard 2.0 SDK,因此你必须手动安装。可以在 bit.ly/2mJArWx 上找到 .NET Standard 下载链接。你会发现你可以下载运行时或 SDK(其中包括运行时)。选择适合你的计算机的 SDK 下载。

确保为 Visual Studio 安装 UWP 工具,可以通过在 Visual Studio 安装程序中选择 UWP 工作负载完成该任务。如果你没有在该工作负载中启用 Windows 10 SDK 版本 16299,请务必启用。

对早期采用者的提醒

请注意,在写这篇文章时,EF Core 2.0 并不完全与新版本 UWP 一致,尽管 2.0.1 修补程序中即将推出一些修复方法。例如,在 UWP 项目中执行关系查询存在一个现行 bug。我的示例避免了这个问题。你可以在 bit.ly/2xS35Mq 的 EF Core GitHub 存储库中跟踪该问题。

创建新的 UWP 项目

安装了所有适当的工具后,Visual Studio 将在名为 Windows Universal 的部分中显示一组 UWP 项目模板。从这组模板中,我将创建一个新的空白应用程序(通用 Windows)。框架下拉菜单默认为 4.7.1(.1 在图 1 中被截断),我将这个默认值作为我的应用程序的 .NET Framework 的基本版本。我将新项目命名为 CookieBinge2。

从空白应用模板创建 UWP 项目
图 1 从空白应用模板创建 UWP 项目****

系统将提示你选择 UWP 的目标版本和最低版本。请务必为这两个版本选择 Windows 10 Fall Creators Update。当前内部版本号是 16299。

创建项目时,你将看到解决方案资源管理器中的文件和文件夹,如图 2 所示。

最初由模板创建的项目
图 2 最初由模板创建的项目****

由于本专栏专注于数据,而不是 UI 构建,因此我将通过一些捷径获取 UI,然后添加 EF Core 和其数据暂留活动的 API 和代码。

你可以在与本专栏关联的下载内容中找到要复制和粘贴的必要代码。首先,删除新项目的“资产”文件夹下的图像,然后将下载内容中提供的“资产”文件夹中的图像(如图 3 所示)复制到此处。

将复制到“资产”文件夹中的图像
****图 3 将复制到“资产”文件夹中的图像

通过 EF Core 2.0 添加暂留逻辑

如下所示,首先将两个类添加到项目中。

CookieBinge.cs 类的内容并不比 DTO 丰富,只包含需要存储的属性。没有需要执行的真正逻辑。该类如下所示:

public class CookieBinge {
  public Guid Id { get; set; }
  public DateTime TimeOccurred { get; set; }
  public int HowMany { get; set; }
  public bool WorthIt { get; set; }
}

BingeService 类封装了这个游戏确定需要执行的任务,用来封送 UI 和数据存储之间的信息。BingeService 有三种方法:RecordBinge(用于将结果永久保存在本地)、GetRecentBinges(用于检索部分游戏分数)和 ClearHistory(用于清除本地存储中的所有结果)。该类在实现上述方法的逻辑之前如下所示:

public static class BingeService {
    public static void RecordBinge(
      int count, bool worthIt) { }
    public static IEnumerable<CookieBinge>    
      GetRecentBinges(int numberToRetrieve)
    public static void ClearHistory() {  }
  }

为了充实该服务,我需要添加暂留功能。这是 EF Core 2.0 将暴食结果读取和存储到设备的位置。EF Core 为 SQLite 数据库提供了提供程序,可以直接在各种设备上使用,所以我将其用于本演示。

既然我选择了数据存储,就可以将 SQLite 的 EF Core 提供程序添加到我的应用程序中,然后它将提取所依赖的相关 EF Core 包(如需要将包括 SQLite)。EF Core 不再是一个大型程序包。相反,该逻辑被分成不同的程序集,所以你可以在设备(或服务器或部署它的任何位置)上最小化其占用空间,仅包括你的应用程序需要的部分逻辑。

在 Visual Studio 2017 中,可以使用 NuGet 包管理器将 NuGet 包添加到项目中(以可视方式或通过 install-package 等 PowerShell 命令)。我将使用 PowerShell 命令,这意味着会打开包管理器控制台(工具 | 管理 NuGet 程序包 | 包管理器控制台)。由于我的解决方案中只有一个项目,因此控制台会将 CookieBinge2 视为默认项目,如图 4 所示。在那里,我可以安装 SQLite 提供程序包。

在包管理器控制台窗口中安装 SQLite 包
图 4 在包管理器控制台窗口中安装 SQLite 包****

安装完成后,你将在解决方案资源管理器的项目引用中看到此程序包。如果你出于好奇强制 Visual Studio 显示所有文件,展开 obj 文件夹,然后打开 project.assets.json 文件,你将能够看到 SQLite 提供程序提取的依赖项,例如 Microsoft.EntityFrameworkCore、Microsoft.EntityFrameworkCore.Relational 等。现在 EF Core 和 SQLite 提供程序已就绪,你可以创建 DbContext。添加一个名为 BingeContext.cs 的新文件,并复制图 5 中列出的代码。

图 5 通过 EF Core 管理暂留的 DbContext 类

using System;
using System.IO;
using Microsoft.EntityFrameworkCore;
using Windows.Storage;
namespace CookieBinge2 {
  public class BingeContext:DbContext {
    public DbSet<CookieBinge> Binges { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder options){
      var dbPath = "CookieBinge.db";
      try {
        dbPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, dbPath);
      }
      catch (InvalidOperationException){
        #TODO handle this exception
      }
      options.UseSqlite($"Data source={dbPath}");    }
  }
}

原因是此 DbContext 位于 UWP 项目中,我正在利用可用的资源,如 System.IO 和 Windows.Storage API。本文的下载内容将反映这种模式。相比之下,我的 GitHub 帐户中另一个版本的应用程序 (bit.ly/2liqFw5) 在单独的 .NET 标准类库项目中具有后端逻辑和 EF Core,并使用依存关系注入提供上下文的文件路径。如果你打算使用 EF Core 迁移改进模型和数据库架构,则需要使用这个分离的体系结构。

上下文有一个与 CookieBinge 数据交互的 DbSet。OnConfiguring 方法中的代码让 EF Core 知道它将使用 SQLite 提供程序,并指定一个本地文件路径来在运行该应用程序的设备上存储数据。

现在,上下文就在那里,我可以充实 BingeService 方法。RecordBinge 将接收从 UI 发送的值,构建一个 CookieBinge 对象并将其传递给 BingeContext 以存储到本地数据库中:

public static void RecordBinge(int count, bool worthIt) {
  var binge = new CookieBinge {
                HowMany = count,
                WorthIt = worthIt,
                TimeOccurred = DateTime.Now};
  using (var context = new BingeContext()) {
    context.Binges.Add(binge);
    context.SaveChanges();
  }
}

GetRecentBinges 方法将使用 BingeContext 从本地存储的数据库中检索数据,并将其传递回 UI,该 UI 发出请求并有一个显示数据的面板:

public static IEnumerable<CookieBinge> GetRecentBinges(
      int numberToRetrieve) {
   using (var context = new BingeContext()) {
     return context.Binges
               .OrderByDescending(b => b.TimeOccurred)
               .Take(numberToRetrieve).ToList();
   }
}

BingeService 中的最后一种方法 ClearHistory 会将本地数据库完全清空,以防你不想备受过度放纵的折磨:

public static void ClearHistory() {
  using (var context = new BingeContext(){
    context.Database.EnsureDeleted();
    context.Database.EnsureCreated();
  }
}

这样做可以照顾游戏逻辑的后端。

UI 及其逻辑

前端由 UI (MainPage.xaml)、UI 逻辑 (MainPage.xaml.cs) 和 BingeViewModel.cs 文件组成。在我在后端创建的功能感兴趣的这些类中存在大量方法。在 MainPage.xaml.cs 中,有两种调入 BingeService 类的方法。ReloadHistory 方法调用 GetRecentBinges 方法并将结果绑定到 UI 上的列表:

private void ReloadHistory() {
  BingeList.ItemsSource = BingeService.GetRecentBinges(5);
}

ClearHistory 方法调用服务方法以清除本地数据库中的所有数据,然后强制 UI 重载显示的历史记录(现在将显示为空):

private void ClearHistory_Click(object sender, RoutedEventArgs e) {
  BingeService.ClearHistory();
  ReloadHistory();
}

UI 中还有一些方法可以响应用户单击“值得”和“不值得”按钮来存储结果,但这些方法不直接调用服务方法。而是调用 BingeViewModel 方法 StoreBinge,然后由该方法调用服务方法来存储数据。StoreBinge 方法首先调用服务方法,从 UI 传递数据:

BingeService.RecordBinge(_clickCount, worthIt);

StoreBinge 还在 UI 中执行一些额外的魔法来清理刚刚完成的暴食,并为新的暴食做好准备。你可以在下载内容中看到这些额外的代码。

这种分离问题的方法可以将数据从一个文件传递到另一个文件,使得以后此应用程序的维护变得更简单。对我来说幸运的是,在我将应用程序从 EF7 转换到 EF Core 1、再到 EF Core 2 的过程中,代码更改一直都很少。

还有最后一个任务要执行,即触发应用程序以确保存在本地数据库文件。在 App.xaml 文件中,在 this.Suspending += OnSuspending 之后将以下代码添加到构造函数方法中:

using (var context = new BingeContext()) {
  context.Database.EnsureCreated();

在 GitHub 上可以使用迁移的独立体系结构中,你将在 Ensure­Created 之后看到对 Migrate 命令的调用,以确保应用了任何模型更改。

运行应用

在 Visual Studio 中,可以定位本地计算机或模拟器(打开模拟本地计算机的单独窗口)以运行或调试应用。bit.ly/2p8yRMf 中也提供了用于 HoloLens 的模拟器。我将在本地计算机上运行我的应用。****图 6 显示了游戏界面边缘的计算机桌面背景提示。每单击一次饼干,就会在鼠标指针处显示单词“Nom!”,并增加“已吃饼干”计数。吃饱后,用户单击冷蓝色图标或死绝图标即可完成游戏。图 7 显示了“分数”页面,其中显示了最近五次分数以及“清除历史记录”按钮。该 UI 正是数据极客可以一起攻击的目标。要获得适当的 UWP UI 设计指导,可以在 bit.ly/2gMgE74 上找到快速入门的好方法。

狂吃饼干,迄今为止吃了 6 块
图 6 狂吃饼干,迄今为止吃了 6 块****

通过 EF Core 存储在本地数据库中的历史记录
图 7 通过 EF Core 存储在本地数据库中的历史记录

即将推出:通过 Azure Functions 和 Cosmos DB 与其他玩家分享

如果不介意我的小游戏设计过于简单,那么应注意本次课程重点在于 EF Core 现在可以直接在设备上工作,因为它现在依赖于 .NET 标准,而不是完整的 .NET Framework,而且 UWP 支持 .NET Standard。这意味着你现在可以在各种 Windows 10 设备上的 UWP 应用程序(例如 HoloLens、Xbox、Windows Mixed Reality 等)中使用 EF Core 2。

我已经使用 EF Core 2.0 将数据以本地方式存储在安装了 CookieBinge 游戏的设备上。在下一篇专栏文章中,我准备将游戏绑定到 Web 上并为其添加一套新功能。使用 Azure Functions,应用程序能够将分数发送到 Azure Cosmos DB 数据库,允许用户比较他们在各种 UWP 设备上玩游戏所得的分数,以及将他们吃掉的饼干与全球其他人吃掉的饼干进行比较。我需要使用 Azure Functions 和 Azure Cosmos DB,因为我的 Cookie Binge 游戏将主宰全球,需要点击按钮进行缩放。


Julie Lerman住在佛蒙特州的丘陵地区,担任 Microsoft 区域主管、Microsoft MVP、软件团队导师和顾问。可以在全球的用户组和会议中看到她对数据访问和其他主题的介绍。她的博客地址是 datafarm.com/blog。她是《Entity Framework 编程》及其 Code First 和 DbContext 版本(全部由 O’Reilly Media 出版)的作者。通过 Twitter 关注她:@julielerman 并在 juliel.me/PS-Videos 上观看其 Pluralsight 课程。

衷心感谢以下 Microsoft 技术专家对本文的审阅:Clint Rutkas


在 MSDN 杂志论坛讨论这篇文章