本文章是由機器翻譯。

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 比較各種序列化策略及其佔用的磁碟空間。

圖 1各種序列化策略佔用磁碟空間的比較

為了生成這些數位,我們使用隨機的姓名和位址,創建了一個包含 2,000 名連絡人的集合。每個連絡人記錄都包含全名、位址和唯一的 ID(請參見圖 2)。

圖 2 Contact 類

然後使用不同的序列化程式(包括 Sterling)保存這些記錄。計算的空間是總磁碟空間,包括 Sterling 為了跟蹤索引和鍵所需的額外檔。

對於 Sterling,從磁片或對磁片進行完全序列化會稍微慢一些,因為遍歷物件圖表及處理索引和鍵都需要佔用資源。圖 3中的圖表比較了每個選項在保存和載入 2,000 名連絡人時的速度。

圖 3速度比較

最終的查詢統計資訊顯示,在掃描姓名以字母“L”開頭的連絡人,然後載入這些完整的連絡人資訊的過程中,Sterling 在哪個環節最具優勢。在示例運行中,查詢過程從 2,000 名連絡人中檢索出 65 名連絡人。Sterling 僅用了 110 毫秒就篩選並載入完了這些資訊,而其他格式卻用了 2 秒多。

食譜應用程式

為了在 Windows Phone 7 上演示 Sterling 的用途,我編寫了一個小的食譜應用程式,可供您流覽、編輯並添加新食譜。每個食譜都包括一個名稱、一套做法說明、一個類別(如午餐或晚餐)和一套原料。原料包含數量、分量和食物專案。食物可隨意添加。圖 4展示了此應用程式的示例頁面。

圖 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);
}

The Main 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展示了此應用程式中使用的模型層次結構。

圖 8食譜類層次結構

食譜包含可以迴圈引用父級食譜的原料清單。原料還包含一個包括單位和分量的“數量”模型。

當您保存食譜時,Sterling 會自動將每一種原料識別為單獨表定義中的單獨條目。Sterling 不是用食譜物件序列化原料,而是先序列化索引鍵,然後再保存原料。它還會通過食譜識別迴圈引用並在物件圖表上終止迴圈。當包含帶有原料的食譜時,允許直接對原料進行查詢,然後查詢可載入相應的食譜。

當您修改或添加原料時,保存操作也會自動保存相關表。載入時,始終從磁片提取外表,以確保外表始終與最新版本同步。

Food Search:通過索引查詢

Sterling 使用駐留在記憶體中的鍵和索引來説明查詢和篩選。食物搜索為篩選和查詢提供了很好的例子。文字方塊與 ViewModel 進行了綁定,使用者輸入文本時,文字方塊將更新鍵入的文本。使用者一鍵入內容就會看到結果(那些包含他們在名稱中鍵入的文本的食物)。這便於使用者縮小搜索範圍,以及選擇現有食物或輸入新食物。您會在圖 9中看到食物搜尋網頁面,其中的所選項目含有字母“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