Windows Phone 7

针对 Windows Phone 7 上的独立存储的 Sterling

Jeremy Likness

下载代码示例

Windows Phone 7 的发布为大约一百万名 Silverlight 开发人员提供了几乎在一夜之间成为移动编程员的机会。

Windows Phone 7 的应用程序是使用相同的语言(C# 或 Visual Basic)、在一个几乎与 Silverlight 3 的浏览器版本相同的框架中编写的,该框架的功能包括使用 XAML 设计屏幕布局以及通过 Expression Blend 编辑屏幕。 但是,专为手机而开发带来了独有的挑战,这些挑战包括在用户切换应用程序时需要进行特殊管理(称为“逻辑删除”),以及仅为状态管理提供了有限的支持。

Sterling 是一个基于独立存储的开源数据库项目,可帮助您管理 Windows Phone 7 应用程序中的本地对象和资源,以及简化逻辑删除过程。 这种面向对象的数据库的设计初衷是成为一种轻便、快速且易于使用的数据库,从而解决持久化、高速缓存和状态管理等问题。 这是一种非侵入性数据库,可与您现有的类型定义一同使用,而无需对这些定义进行更改或映射。

在本文中,Windows Phone 7 开发人员将学习如何以最少的工作量利用 Sterling 库在手机本地持久化和查询数据,还将学习一项用于当应用程序被停用后管理逻辑删除期间的状态的简单策略。

逻辑删除基本知识

浏览器和手机中的 Silverlight 在一个特殊的“安全沙盒”中运行,该“安全沙盒”将主机系统与应用程序的运行时环境隔离开来。 手机环境比较复杂,因为平台上可同时存在多个应用程序。 虽然手机的基础操作系统支持多任务处理,但第三方应用程序无权访问该层。 应用程序可以“在前台”运行,但是可能会被快速交换出,以便为其他应用程序、来电或“Back”(后退)按钮和搜索等硬件功能“让路”。 当应用程序被停用后,可能会被“终止”,如果用户后退浏览,就会恢复应用程序。 此过程即为逻辑删除。

Windows Phone 7 的“Mango”更新通过提供“快速应用程序切换”来限制逻辑删除方案。应用程序将不会再自动被逻辑删除。 虽然 Windows Phone 7 开发人员工具中已有此功能,但务必要认识到此功能是不会清除逻辑删除方案的。 其他正在运行的应用程序和可用内存等因素会影响应用程序是否会被逻辑删除。

逻辑删除的问题在于:恢复应用程序后,将创建新的页面实例并将其作为该应用程序的一部分。 因此,当此事件发生时,用户执行的任何操作(如从选取列表中选择项目或输入文本)都将丢失。 当应用程序返回时,全靠开发人员保留此状态并将其恢复,以便为用户提供无缝体验。 不妨想象一下一位典型用户在遇到以下情况时的困惑:正在填写表格时,单击“Search”(搜索)来查询某一个词,但返回的结果却是应用程序导航到了另一个空白页面!

在手机上保存状态

幸运的是,Windows Phone 7 提供了一些用于保存状态的机制。 要使用操作系统中的应用程序,您需要熟悉这些方法。 提供的选择包括 SQL CE(带有 Mango 更新)、页面状态词典、应用程序状态词典和独立存储。 我将重点介绍最后一种选择,即独立存储;而快速回顾前三种选择将有助于您了解为什么 Sterling 在手机上非常有用。

SQL CE Mango 更新会提供 SQL CE,这是当前流行的 SQL Server 数据库的一个精简版。 此数据库与其他所列选择的不同之处在于:SQL 是一个关系数据库。 它依赖于必须从您的类和控件进行映射的特殊表格式。 面向对象的数据库可以采用现有的类结构(即使它们包含嵌套的类、列表和其他类型),并在没有额外映射或修改的情况下,序列化这些类结构。

页面状态 每一个 Page 对象都提供一个 State 属性,该属性提供一个由键名称和相关对象定义的词典。 可以使用任何键或对象,但对象必须是可序列化的,然后仅执行浅(顶级)序列化。 此外,页面状态最多仅允许每页 2MB 数据(整个应用程序 4MB),且只能在对页面启动 OnNavigatedTo 方法之后、启动 OnNavigatedFrom 方法之前使用。 这就将实际用途局限于简单的值类型。 实际上,它只能在页面导航方法中起作用,这使得它与 Model-View-ViewModel (MVVM) 等模式相比相形见绌,MVVM 模式通过单独的 ViewModel 对视图状态进行同步。

应用程序状态 它也是一个词典。 与页面状态一样,应用程序状态使用字符串键和对象值,且对象必须是可序列化的。 进行逻辑删除时,手机上的应用程序对象调用 Deactivated 事件,应用程序从逻辑删除状态返回后,则调用 Activated 事件。 可以在激活和停用之间的任何时间访问应用程序状态。 访问应用程序状态时使用静态的 PhoneApplicationService 类(有一个 Current 属性,该属性引用应用程序范围内的一个实例)。 关于应用程序状态词典的大小,没有规定限制,但有时尝试存储太多项目会导致抛出未处理的 COM 异常。

独立存储 它是到目前为止用于保留状态的最灵活的选择。 独立存储不是手机所特有的,事实上,它的工作方式与在 Silverlight 和 Microsoft .NET Framework 运行时中几乎没什么差别。 独立存储在主机文件系统中提供了一个抽象层,因此,您不是处理直接文件存储,而是与一个间接机制交接,该机制在独立沙盒中提供文件夹和文件。 在 Windows Phone 7 中,沙盒独立于您的手机应用程序的等级。 您可以从相关应用程序中的任何位置访问存储,但不能从手机上的任何其他应用程序访问。

独立存储还有一些强大的优势。 独立存储除了提供类似于上述页面和应用程序设置的设置词典外,还允许您使用文件夹和文件来组织数据。 实际上,在独立存储中,可以创建并访问任何类型(XML、二进制或文本)的文件。 因为手机上独立存储的大小没有限额,所以实际上您只受手机内存大小和可用存储的限制。 独立存储的唯一缺点是:与在活动内存中存储列表的其他方法相比,写入存储和从存储检索的过程稍慢一些。

序列化选项

独立存储的另一个好处是:您可以选择多个序列化策略。 与那些允许您分配键和对象的设置不同,独立存储机制提供了一个文件流,您可以使用文本、XML、JSON 甚至是二进制代码来写入此文件流。 这便可以对许多类型的对象进行简单、直接的序列化,包括可以处理复杂对象图表并序列化您所运行实例的子对象和孙子对象等的“深”序列化。

JSON 和 XML JSON 和 XML 是 Silverlight 的两种主要的序列化策略。 可以使用 DataContractSerializer(用来发出 XML)或 XMLSerializer 来得到 XML。 使用 Windows Communication Foundation (WCF) 的开发人员对 DataContractSerializer 非常熟悉,框架利用它来冻结、解冻通过 Web 服务发出的消息。 它要求使用 DataContract 和 DataMember 属性来标记数据。 这基本上是一个“可选”方法,因为您明确地标记了您想序列化的域。 XmlSerializer 使用 getter 和 setter 来序列化所有公共属性,您可以通过使用特定于 XML 的特殊属性来更改行为。 有关 DataContractSerializer 的更多信息,请参见 bit.ly/fUDPha。 有关 XmlSerializer 的更多信息,请参见 bit.ly/fCIa6q

JSON 是 JavaScript Object Notation 的缩写,它在网络上很普遍,因为这种格式很容易转换成 JavaScript 对象。 一些开发人员更喜欢这种方法,因为在序列化的对象中,它能提供比 XML 更精简的可读文本。 JSON 序列化是使用一种名为 DataContractJsonSerializer 的数据协定序列化程序的特殊版本来实现的。 它生成的输出在磁盘上占用的空间要比 XML 小得多。 有关 DataContractJsonSerializer 的更多信息,请参见 bit.ly/8cFyjV

二进制 Silverlight 和 Windows Phone 7 也可以使用二进制序列化。 Silverlight 未提供 BinaryFormatter(有助于自动将对象序列化为二进制的类),所以您必须亲自执行序列化。 要使用二进制序列化,您只需创建一个二进制编写器并写入流即可。 编写器可以处理许多基元,因此您可以在大部分时间写出您的类的属性,以序列化实例,然后创建实例并使用阅读器填充属性。 但对于拥有大量类的项目,这样做会非常麻烦。 这时就该用到 Sterling 了。

Sterling Sterling 专门用于减少在 Silverlight 中序列化和反序列化对象的麻烦。 起初,Sterling 是为浏览器中的 Silverlight 4 创建的,后来它演变成了一种支持 Windows Phone 7 但基本相同的代码库。 实际上,Sterling 使用二进制序列化,这使它在磁盘上占用的空间非常小。 Sterling 几乎可以序列化任何类并通过您提供的键来组织实例(实例的任何属性均可被指定为键)。 Sterling 还提供索引,在从磁盘加载整个实例之前, 可在内存中查询索引以节省时间。 Sterling 使您可以完全控制基础序列化流;您可以对二进制流进行加密、压缩甚至覆盖,以按照您需要的方式序列化类型。

Sterling 的优势是:序列化对象的过程简单、快捷,并且能够使用 LINQ to Objects 以闪电般的速度查询键和索引。 它可以很好地处理外键和外部关系。 进行序列化和反序列化时,这些优点只会使速度稍微变慢一些。

图 1 比较各种序列化策略及其占用的磁盘空间。

Size-on-Disk Comparison of Various Serialization Strategies

图 1 各种序列化策略占用磁盘空间的比较

为了生成这些数字,我们使用随机的姓名和地址,创建了一个包含 2,000 名联系人的集合。 每个联系人记录都包含全名、地址和唯一的 ID(请参见图 2)。

The Contact Class

图 2 Contact 类

然后使用不同的序列化程序(包括 Sterling)保存这些记录。 计算的空间是总磁盘空间,包括 Sterling 为了跟踪索引和键所需的额外文件。

对于 Sterling,从磁盘或对磁盘进行完全序列化会稍微慢一些,因为遍历对象图表及处理索引和键都需要占用资源。 图 3 中的图表比较了每个选项在保存和加载 2,000 名联系人时的速度。

Comparison of Speed

图 3 速度比较

最终的查询统计信息显示,在扫描姓名以字母“L”开头的联系人,然后加载这些完整的联系人信息的过程中,Sterling 在哪个环节最具优势。 在示例运行中,查询过程从 2,000 名联系人中检索出 65 名联系人。 Sterling 仅用了 110 毫秒就筛选并加载完了这些信息,而其他格式却用了 2 秒多。

食谱应用程序

为了在 Windows Phone 7 上演示 Sterling 的用途,我编写了一个小的食谱应用程序,可供您浏览、编辑并添加新食谱。 每个食谱都包括一个名称、一套做法说明、一个类别(如午餐或晚餐)和一套原料。 原料包含数量、分量和食物项目。 食物可随意添加。 图 4 展示了此应用程序的示例页面。

Edit Recipe Screen

图 4**“Edit Recipe”(编辑食谱)屏幕**

对于此应用程序,Sterling 具有三种不同的用途。 首先,Sterling 可帮助预加载此应用程序使用的参考数据,如类别、分量、最初的食物及示例食谱。 其次,当用户添加他们自定义的食谱和食物时,Sterling 可保留用户输入的数据。 最后,停用此应用程序后,Sterling 可通过序列化此应用程序的状态来帮助进行逻辑删除。 示例应用程序使用 MVVM 模式并演示了如何从 ViewModel 中进行逻辑删除。

当然,第一步是在项目中添加 Sterling。 您可以使用 NuGet(仅搜索 Sterling)或通过从 CodePlex (sterling.codeplex.com) 上下载二进制文件或完整的源代码来添加 Sterling。

设置数据库

下一步是设置数据库。 数据库配置定义了持久化哪些类型以及使用哪些键和索引。 Sterling 数据库是由 BaseDatabaseInstance 派生而来的一个类。 您必须提供两个重载:数据库的唯一名称(每个应用程序的名称都是唯一的,您可以托管多个数据库来分隔数据或管理不同的版本)和一个包含表定义的列表。 基类提供辅助方法来定义表、键和索引。

键和索引 此应用程序将对象 ID 用作键,并使用 FoodName 属性的一个索引,来定义一个 FoodModel 类型的表。 这创建了一个驻留在内存中的对象列表,您可以对其进行快速查询和筛选。

以下代码使用一个整数键和一个字符串索引来定义食物“表”:

CreateTableDefinition<FoodModel, int>(f => f.Id)
.WithIndex<FoodModel, string, int>(IDX_FOOD_NAME, f=>f.FoodName)

用于创建表定义的调用带有类类型和键类型,该调用接收了一个用于解析键的 lambda 表达式。 您可以对类使用任何唯一非空值作为您的键。 索引扩展方法需要表类型、索引类型和键类型。 在这里,常数定义索引名称,lambda 表达式提供索引将使用的值。

使用触发器的身份标识 Sterling 支持任何类型的键,因此,并没有内置能够自动生成新键的功能, 而是允许您指定通过使用触发器以何种方式生成键。 触发器被注册到 Sterling 数据库中,并在保存操作前后以及删除操作之前被调用。 如果在保存操作之前调用触发器,则您可以检查实例,若不存在键,还可以生成键。

在本文随附的可下载代码中的示例应用程序中,所有的实体都使用整数键。 因此,可以根据实例类型创建一个通用触发器来生成键。 首次对触发器进行实例化时,触发器会查询数据库,从中查找现有键并寻找值最高的键。 如果没有记录,它会将起始键设置为 1。 每次保存具有小于等于 0 的键的实例时,它就会分配下一个数字并递增键值。 图 5 显示了此基础触发器的代码。

图 5 用于自动生成键的基础触发器

public class IdentityTrigger<T> : BaseSterlingTrigger<T,int>  
  where T: class, IBaseModel, new()
{
  private static int _idx = 1;

  public IdentityTrigger(ISterlingDatabaseInstance database)
  {
    // If a record exists, set it to the highest value plus 1
    if (database.Query<T,int>().Any())
    {
      _idx = database.Query<T, int>().Max(key => key.Key) + 1;
    }
  }

  public override bool BeforeSave(T instance)
  {
    if (instance.Id < 1)
    {
      instance.Id = _idx++;
    }

    return true;
  }

  public override void AfterSave(T instance)
  {
    return;
  }

  public override bool BeforeDelete(int key)
  {
    return true;
  }
}

请注意,查询可使用标准 LINQ 表达式,如 Any 和 Max。 另外,开发人员负责保障触发器机制中线程的安全。

触发器的用法非常简单:您只需将触发器注册到数据库中并传递一个实例(这允许您传递任何必要的构造函数参数)。 您可以使用类似的调用来撤消注册触发器。

自定义序列化程序 Sterling 本来就支持各种类型。 当将类和结构一起使用时,这些类的公共属性和公共域就会迭代以序列化内容。 子类和结构也可以递归迭代。 Sterling 无法直接序列化某些基本 类型。 例如,System.Type 被定义为拥有许多种可能的派生类的抽象类。 Sterling 无法直接序列化或反序列化这种类型。 为了支持逻辑删除,需要创建一个特殊类来存储 ViewModel 属性并将 ViewModel 类型用作键。 为了处理这种类型,Sterling 会让您创建一个自定义序列化程序。

要创建自定义序列化程序,从 BaseSerializer 类中派生并处理重载。 对于自定义 TypeSerializer 类,任何从 System.Type 派生的类均受支持,序列化过程只写出程序集限定的类型名称。 反序列化对 Type 类使用静态的 GetType 方法,以便从程序集限定名称返回类型。 结果显示在图 6 中。 请注意,它明确支持派生自(或可分配给)System.Type 的任何类型。

图 6 TypeSerializer

public class TypeSerializer : BaseSerializer 
{
  /// <summary>
  ///     Return true if this serializer can handle the object, 
  ///     that is, if it can be cast to type
    /// </summary>
  /// <param name="targetType">The target</param>
  /// <returns>True if it can be serialized</returns>
  public override bool CanSerialize(Type targetType)
  {
    return typeof (Type).IsAssignableFrom(targetType);
  }

  /// <summary>
  ///     Serialize the object
  /// </summary>
  /// <param name="target">The target</param>
  /// <param name="writer">The writer</param>
  public override void Serialize(object target, 
    BinaryWriter writer)
  {
    var type = target as Type;
    if (type == null)
    {
      throw new SterlingSerializerException(
        this, target.GetType());
    }
    writer.Write(type.AssemblyQualifiedName);
  }

  /// <summary>
  ///     Deserialize the object
  /// </summary>
  /// <param name="type">The type of the object</param>
  /// <param name="reader">A reader to deserialize from</param>
  /// <returns>The deserialized object</returns>
  public override object Deserialize(
    Type type, BinaryReader reader)
  {
    return Type.GetType(reader.ReadString());
  }
}

在激活 Sterling 引擎之前,任何自定义序列化程序都将被注册到 Sterling 引擎中。

植入数据和保存数据

定义数据库后,通常会提供“种子数据”。在示例应用程序中,提供了一个包含类别、标准分量和食物的列表(还包括一个示例食谱),以帮助用户入门。 嵌入数据的方法有好几种,但最简单的方法是将数据作为资源添加到 XAP 文件中。 应用程序首次运行时,数据将被分析为资源流,然后存储到数据库中。

为了执行逻辑删除过程,当应用程序本身被激活时,Sterling 数据库引擎也被激活;当应用程序退出或被逻辑删除时,Sterling 数据库引擎停用。 这确保了数据库键和索引被刷新到磁盘,并且再次使用数据库时,数据库会处于稳定状态。 在 App.xaml.cs 文件中,可将这些事件与手机生命周期连接在一起。 要设置数据库,只需几行代码即可,如下所示:

_engine = new SterlingEngine();
_engine.SterlingDatabase.RegisterSerializer<TypeSerializer>();
_engine.Activate();
Database =  
  _engine.SterlingDatabase.RegisterDatabase<RecipeDatabase>();

前面的代码片段演示了创建引擎、注册自定义序列化程序、激活引擎并准备数据库以备使用等一系列步骤。 下面的代码说明了当应用程序退出或被逻辑删除时,如何关闭引擎和数据库:

Database.Flush();
_engine.Dispose();
Database = null;
_engine = null;

激活数据库后,就可以接收数据了。 打包数据的最常见方法是将数据作为资源添加到可读格式(如 XML、JSON 或 CSV)文件中。 对数据库进行查询可以确定数据是否已经存在,若不存在,则会加载数据。 在 Sterling 中保存数据非常简单:您只需传递要保存的实例,剩下的都可以交给 Sterling 去处理。 图 7 展示了用于检查类别的查询。 如果不存在类别,则会从嵌入的资源文件中读取数据并将这些数据植入到数据库中。 注意要先进行截断操作来清除表。

图 7 植入数据

if (database.Query<CategoryModel, int>().Any()) return;

// Get rid of old data
database.Truncate(typeof(MeasureModel));
database.Truncate(typeof(FoodModel));
                
var idx = 0;

foreach(var measure in ParseFromResource(FILE_MEASURES,
  line =>
  new MeasureModel
  { Id = ++idx, Abbreviation = line[0], FullMeasure = line[1]} ))
{
  database.Save(measure);
}

// Sample foods auto-generate the id
foreach (var food in
  ParseFromResource(FILE_FOOD, line 
    => new FoodModel { FoodName = line[0] })
    .Where(food => !string.IsNullOrEmpty(food.FoodName)))
{
  database.Save(food);
}

var idx1 = 0;

foreach (var category in ParseFromResource(FILE_CATEGORIES,
  line =>
  new CategoryModel { Id = ++idx1, CategoryName = line[0] }))
{
  database.Save(category);
}

主 ViewModel:类别和食谱

当分析并加载了被植入数据的数据库后,应用程序的其余部分可以利用现有的数据为用户提供列表和提示,还可以保存用户输入的任何信息。 下面的示例说明了如何将植入的数据提供给应用程序。 下面的代码片段将所有类别加载到主 ViewModel 的一个可观测集合:

Categories = new ObservableCollection<CategoryModel>();
foreach(var category in App.Database.Query<CategoryModel,int>())
{
  Categories.Add(category.LazyValue.Value);
}

现在,Categories 集合可直接绑定到 Pivot 控件。 Pivot 控件的一个常见用途是针对大型数据集提供筛选视图。 类别指示餐类(是早餐、午餐还是其他类别),当选择某一类别后,Pivot 显示其相关的食谱。 根据当前选择的类别进行筛选的查询会显示每个类别的食谱。

下面的 XAML 片段说明了控件如何直接绑定到集合和所选的类别:

<controls:Pivot        
  x:Name="pivotMain"
  Title="Sterling Recipes"
  ItemsSource="{Binding Categories}"
  SelectedItem="{Binding CurrentCategory,Mode=TwoWay}">

编辑原料:外键

当然,食谱的键就是它的原料。 此应用程序包含一个关于食物项目和分量的“主列表”。 这样,每个食谱都会有一个包括数量、分量类型和食物项目的“原料”列表。 单击原料按钮(请参见上面的图 4),用户会看到一个原料列表,用户可以在该列表中增添、删除或编辑现有原料。

此功能可以实现是因为 Sterling 具有一个重要特征,那就是支持与外键起相似作用的导航属性。 图 8 展示了此应用程序中使用的模型层次结构。

Recipe Class Hierarchy

图 8 食谱类层次结构

食谱包含可以循环引用父级食谱的原料列表。 原料还包含一个包括单位和分量的“数量”模型。

当您保存食谱时,Sterling 会自动将每一种原料识别为单独表定义中的单独条目。 Sterling 不是用食谱对象序列化原料,而是先序列化索引键,然后再保存原料。 它还会通过食谱识别循环引用并在对象图表上终止循环。 当包含带有原料的食谱时,允许直接对原料进行查询,然后查询可加载相应的食谱。

当您修改或添加原料时,保存操作也会自动保存相关表。 加载时,始终从磁盘提取外表,以确保外表始终与最新版本同步。

食物搜索:通过索引查询

Sterling 使用驻留在内存中的键和索引来帮助查询和筛选。 食物搜索为筛选和查询提供了很好的例子。 文本框与 ViewModel 进行了绑定,用户输入文本时,文本框将更新键入的文本。 用户一键入内容就会看到结果(那些包含他们在名称中键入的文本的食物)。 这便于用户缩小搜索范围,以及选择现有食物或输入新食物。 您会在图 9 中看到食物搜索页面,其中的所选项目含有字母“pe”。

Food Search for Items that Include the Letters “pe”

图 9 针对含有字母“pe”的项目的食物搜索

每当用户键入时,都会以搜索文本更新 ViewModel 的属性 setter。 此 setter 接着会引发食物列表中发生属性更改的事件。 食物列表对食物数据库执行新的查询,并使用 LINQ to Objects 返回结果。 图 10 显示了此查询,它使用索引来筛选数据并将数据排序。 为了访问此查询,将使用类类型、索引类型和键类型来调用数据库,然后向数据库传递索引名称。 请注意,将根据从索引返回的键和索引值创建一个新的食物模型。

图 10 食物查询

public IEnumerable<FoodModel> Food
{
  get
  {
    if (string.IsNullOrEmpty(_foodText))
    {
      return Enumerable.Empty<FoodModel>();
    }
    var foodTextLower = _foodText.ToLower();
    return from f in App.Database.Query<FoodModel, 
      string, int>(RecipeDatabase.IDX_FOOD_NAME)
      where f.Index.ToLower().Contains(foodTextLower)
      orderby f.Index
      select new FoodModel { Id = f.Key, FoodName = f.Index };
  }
}

实际逻辑删除

为了让逻辑删除能够起作用,当用户返回应用程序时,应用程序的当前状态必须是已保存并已恢复。 有时,此要求可能会比较复杂,因为当应用程序从逻辑删除状态返回后,用户可能还会在应用程序中往回浏览。 因此,所有页面必须保留其各自的值,这样才能继续获得无缝体验。

MVVM 模式摆脱了以视图为中心的 XAML 和代码隐藏进行逻辑删除,因为视图的状态是通过数据绑定的方式与 ViewModel 同步的。 因此,每个 ViewModel 都负责保存及恢复各自的状态。 绑定会相应地更新视图。 为了便于进行逻辑删除,创建了一个叫作 TombstoneModel 的类。

它是基于一个类型(该类型将成为要保存的 ViewModel 的接口)来进行调整的,包含一个含有键和对象的词典。 这提供了根据需要存储类型或类以保留 ViewModel 状态的最高灵活性。

Sterling 支持此灵活性,因为无论将属性定义为通用对象、接口还是抽象基类,序列化都会将对象作为已实现类型写出。 这在内置序列化程序中是不可能实现的。 为了提供通用接口,轻量级 MVVM 框架提供了一个 ITombstoneFriendly 接口。 此接口定义了激活和停用方法;当导航到绑定视图或从绑定视图导航时,将会调用这些方法(例如,逻辑删除会触发“从”绑定视图导航)。

然后,逻辑删除就变得很简单,仅需要创建模型、设置类型,然后设置必须持久化的值而已。 从逻辑删除返回事件涉及在 ViewModel 上加载模型、读取值并恢复状态。 图 11 在文本编辑器 ViewModel 中说明了这些步骤,该编辑器必须保留已传递的标题和用户已输入的文本。

图 11 逻辑删除

/// <summary>
///     Tombstone
/// </summary>
public void Deactivate()
{
  var tombstone = new TombstoneModel 
    {SyncType = typeof (ITextEditorViewModel)};
  tombstone.State.Add(ExtractPropertyName(()=>Title), Title);
  tombstone.State.Add(ExtractPropertyName(() =>Text), Text);
  App.Database.Save(tombstone);
}

/// <summary>
///     Returned from tombstone
/// </summary>
public void Activate()
{
  var tombstone = App.Database.Load<TombstoneModel>
    (typeof(ITextEditorViewModel));
  if (tombstone == null) return;
  Title = tombstone.TryGet(ExtractPropertyName(() => 
    Title), string.Empty);
  Text = tombstone.TryGet(ExtractPropertyName(() => 
    Text), string.Empty);
}

如果通过正常方式(而不是逻辑删除)关闭视图,记录很容易被删除。 关闭应用程序后,表将被截断以移除所有记录,因为再次运行应用程序时,不会保留其状态。

轻便且灵活

如您所见,Sterling 为开发人员解决了复杂的序列化策略的负担。 持久化实体只需定义一个类类型和一个返回唯一键的 lambda 表达式即可。 Sterling 既轻便(撰写本文时,不足 100KB 的 DLL)又灵活(带有用于触发器、加密、压缩和自定义序列化的挂钩),可满足大部分与本地(嵌入式数据库)高速缓存和逻辑删除有关的需求。 食谱应用程序演示了 Sterling 如何与 Windows Phone 7 集成,轻松有效地满足这些需求。

Jeremy Likness是亚特兰大 Wintellect LLC 的资深顾问兼项目经理。他是 Microsoft Silverlight MVP 和认证的 MCTS Silverlight 开发人员。Likness 经常在会议或用户组中提出关于面向企业的 Silverlight 应用程序话题。他还会定期更新他的 Silverlight 博客,博客地址为:csharperimage.jeremylikness.com

衷心感谢以下技术专家对本文的审阅:Rob Cameron、John GarlandJeff Prosise