.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號:N120412302
出刊日期: 2012/4/25
Windows Phone OS 7.1版中,可以將資料儲存在本機資料庫(Local Database)。在Windows Phone應用程式中,要以物件的方式來操作資料庫中的資料。若Windows Phone應用程式可以使用LINQ to SQL技術搭配資料庫來存放資料,參考圖1所示。透過LINQ to SQL,你可以定義資料庫結構資訊、查詢資料庫資料,或將應用程式中的資料儲存到存在於Isolated Storage的資料庫。本文將介紹如何設計一個資料存取程式。

圖 1:Windows Phone資料存取應用程式。
本機資料庫(Local Database)執行在Windows Phone應用程式的程序(Process)中,不是以背景服務的方式執行。因為本機資料庫存在於Isolated Storage,因此只有對應的Windows Phone應用程式才能夠存取本機資料庫,其它的應用程式不能夠存取之。
本機資料庫(Local Database)只支援LINQ to SQL,不支援T-SQL。System.Data.Linq,DataContext是LINQ to SQL的核心物件,代表資料庫,它包含Table物件,對應到資料庫中的資料表。每一個Table物件都是由許多Entity物件組成,每一個Entity物件代表一筆資料。
我們先從建立Windows Phone應用程式專案開始,從Visual Studio 2010工具選單「File」-「New」-「Project」,選取Visual C#語言,點選「Silverlight for Windows Phone」-「Windows Phone Application」,使用預設的命名。接著會出現「New Windows Phone Application」對話方塊,「Target Windows Phone OS Version」項目則選取「Windows Phone OS 7.1」,然後按下「OK」按鈕,就會自動建立專案。
在專案之中新增一個類別以進行資料存取。請參考圖2所示,選取Visual Studio 2010選單「Project」-「Add Class」,便會跳出「Add New Item」對話盒,在對話盒中設定類別的檔案名稱為「DataClass」,按「Add」按鈕,就可以將類別檔案加入專案之中。

圖 2:新增一個類別檔案。
若要使用LINQ,您需要在專案之中,加入「System.Data.linq.dll」組件的參考。請參考圖3所示,在「Solution Explorer」視窗,點選你的專案 -「Reference」項目,按滑鼠右鍵,從突顯式選單中選取「Add Reference」,開啟「Add Reference」對話方塊,從「.NET」頁,選取「System.Data.linq.dll」組件,然後按下「OK」按鈕。

圖 3:加入「System.Data.linq.dll」組件的參考。
設計Entity資料類別
要建立本機資料庫之前,你需要先定義DataContext與Entity類別。這些類別定義了資料庫結構與LINQ to SQL物件模型之間的對應。
在設計Entity資料類別之前,需先引用相關的命名空間。開啟DataClass.cs程式設計畫面,在檔案最上方using區塊中加入以下程式碼,匯入以下命名空間:
using System.Linq;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Collections.ObjectModel;
using System.ComponentModel;
接下來,我們要為想存取的資料設計一個Entity類別,然後再利用LINQ自動在資料庫中建立對應的資料表(Table)。例如,未來資料庫想要儲存員工的清單,你可以在DataClass.cs檔案中,定義一個Employee類別如下:
[Table]
public class Employee : INotifyPropertyChanged , INotifyPropertyChanging
{
public Employee ( int id,string name)
{
ID = id;
Name = name;
}
public Employee ( )
{
}
private int _id;
[Column(IsPrimaryKey = true )]
public int ID
{
get
{
return _id;
}
set
{
if ( PropertyChanging != null )
{
PropertyChanging(this ,
new PropertyChangingEventArgs("ID"));
}
_id = value;
if ( PropertyChanged != null )
{
PropertyChanged(this ,
new PropertyChangedEventArgs("ID"));
}
}
}
private string _name;
[Column()]
public string Name
{
get
{
return _name;
}
set
{
if ( PropertyChanging != null )
{
PropertyChanging(this ,
new PropertyChangingEventArgs("Name"));
}
_name = value;
if ( PropertyChanged != null )
{
PropertyChanged(this ,
new PropertyChangedEventArgs("Name"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangingEventHandler PropertyChanging;
}
我們來分解這個類別中的程式碼,Employee類別上方套用了Table Attribute,表示對應到資料庫中的資料表。Table Attribute是定義在System.Data.Linq.Mapping命名空間之下:
[Table]
public class Employee : INotifyPropertyChanged , INotifyPropertyChanging
{
…
}
Employee類別也實作了INotifyPropertyChanged 與INotifyPropertyChanging兩個介面,這兩個介面分別定義了PropertyChanged與PropertyChanging事件程式碼,以通知LINQ,Employee物件中的資料是否被異動:
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangingEventHandler PropertyChanging;
Employee類別之中,包含了兩個屬性,分別是ID與Name屬性,這兩個屬性的上方套用了Column Attribute,代表對應到資料表中的欄位。其中ID屬性的Column Attribute之IsPrimaryKey屬性設定為true,表示未來ID屬性的值是不能夠重複的,同樣的ID屬性也會對應到資料庫中資料表的主鍵欄位:
private int _id;
[Column(IsPrimaryKey = true )]
public int ID
{
get
{
return _id;
}
set
{
if ( PropertyChanging != null )
{
PropertyChanging(this ,
new PropertyChangingEventArgs("ID"));
}
_id = value;
if ( PropertyChanged != null )
{
PropertyChanged(this ,
new PropertyChangedEventArgs("ID"));
}
}
}
private string _name;
[Column()]
public string Name
{
get
{
return _name;
}
set
{
if ( PropertyChanging != null )
{
PropertyChanging(this ,
new PropertyChangingEventArgs("Name"));
}
_name = value;
if ( PropertyChanged != null )
{
PropertyChanged(this ,
new PropertyChangedEventArgs("Name"));
}
}
}
這兩個屬性get存取子單純地將私有變數_id與_name的值回傳。而set存取子較為複雜,先判斷是否有物件附加到PropertyChanging與PropertyChanged事件處理常式(即某個物件註冊了PropertyChanging與PropertyChanged事件的意思),若有(不為null),則分別在屬性資料變動之前與之後,觸發PropertyChanging與PropertyChanged事件。觸發事件時,傳入this代表目前的Employee物件實體,與PropertyChangingEventArgs物件當參數,PropertyChangingEventArgs物件的建構函式中傳入變動的屬性名稱。
設計Data Context類別
Data Context類別的主要功能是用來管理資料庫連線(Database Connection),以及所有Employee物件。我們在專案之中DataClass.cs檔案內加入一個DB類別,此類別繼承自System.Data.Linq 命名空間下的DataContext類別:
public class DB : DataContext
{
public string Name { get; set; }
public Table<Employee> EmployeeTable;
public DB ( string cn )
: base(cn)
{
}
public static void CreateDB ( )
{
DB db = new DB("Data Source=isostore:/mydb.sdf");
if ( db.DatabaseExists() )
{
db.DeleteDatabase();
}
db.CreateDatabase();
db.EmployeeTable.InsertOnSubmit ( new Employee(1,"Mary"));
db.EmployeeTable.InsertOnSubmit(new Employee(2 , "Candy"));
db.EmployeeTable.InsertOnSubmit(new Employee(3 , "Lilly"));
db.SubmitChanges();
}
}
DB類別的建構函式中叫用了DataContext類別的建構函式,並傳入連接字串,以便連接、存取本機資料庫。類別中的CreateDB靜態(static)方法則用來建立本機資料庫,以及新增一些測試用的員工資料,方法中首先建立DataContext物件,傳入連接字串:
DB db = new DB("Data Source=isostore:/mydb.sdf");
連接字串中Data Source設定為「isostore」代表使用的是Isolated Storage來存放本機資料庫DataClass.cs檔案。本機資料庫的檔案名稱將會是mydb.sdf檔案。
Windows Phone上的LINQ to SQL並不直接支援T-SQL語法的執行,無法直接執行DDL或DML語法,需要使用ADO.NET物件來處理。範例中利用DataContext類別的DatabaseExists方法判斷資料庫是否已存在,若已存在則叫用DeleteDatabase方法將之刪除,若不存在則叫用DataContext類別CreateDatabase方法來建立本機資料庫:
if ( db.DatabaseExists() )
{
db.DeleteDatabase();
}
db.CreateDatabase();
接下來,我們叫用InsertOnSubmit方法,新增三個員工物件:
db.EmployeeTable.InsertOnSubmit ( new Employee(1,"Mary"));
db.EmployeeTable.InsertOnSubmit(new Employee(2 , "Candy"));
db.EmployeeTable.InsertOnSubmit(new Employee(3 , "Lilly"));
最後透過DataContext類別的SubmitChanges方法,將三筆員工資料寫入資料庫中的資料表:
db.SubmitChanges();
設計使用者介面
開啟MainPage.xaml設計畫面,從Toolbox拖曳一個ListBox控制項到設計畫面,以便顯示員工的清單,請參考圖4所示:

圖 4:設計使用者介面。
請參考圖5所示,選取設計畫面上的PhoneApplicationPage (或將游標停留在XAML編輯畫面PhoneApplicationPage項目上),點選「Properties」視窗「Events」按鈕切換到事件,然後雙擊Loaded事件,產生Loaded事件處理常式。

圖 5:產生Loaded事件處理常式。
在PhoneApplicationPage 的Loaded事件處理常式加入以下程式碼:
private void PhoneApplicationPage_Loaded ( object sender , RoutedEventArgs e )
{
DB.CreateDB();
DB db = new DB("Data Source=isostore:/mydb.sdf");
var empList = from Employee emp in db.EmployeeTable
select emp;
listBox1.ItemsSource = empList;
}
首先叫用DB類別的CreateDB方法建立資料庫與測試資料。當本機資料庫建立完成之後,就可以利用LINQ to SQL來存取資料庫資料。接著建立DB物件,連結到資料庫,透過LINQ查詢,將資料庫中所有的Employee資料回傳最後將回傳的資料顯示在ListBox控制項中。
應用程式測試
我們先來看看設計到目前這個階段個成果,在Visual Studio 2010開發工具上,按F5或選取「Debug」-「Start Debugging」選項執行程式,此時會啟動Windows Phone Emulator,並看到畫面如下圖,ListBox顯示的是Employee型別的清單資料,我們稍後再來解決這個問題。

圖 6:應用程式測試。
檢視Isolated Storage中的本機資料庫檔案
設計Windows Phone程式時,你可以將本機資料庫檔案儲存在Isolated Storage之中,顧名思義,只有你的這個程式可以存取到它的內容,你可以使用Windows SDK 附的Isolated Storage Explorer 工具程式,來檢視Isolated Storage 中所包含的檔案。Isolated Storage Explorer 工具程式,是一個沒有UI介面的工具,當你安裝Windows Phone SDK之後,它會出現在以下路徑:
C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.1\Tools\IsolatedStorageExplorerTool
使用此工具程式時,要設定幾個參數,常用的參數表列如下:
- ts (Take snapshot) :將特定應用程式的Isolated Storage之所有內容,複製到本機目錄。
- rs(Restore snapshot) :將本機某目錄中所有的內容複製到模擬器。
- dir: 列出Isolated Storage中的目錄和檔案。
- xd:代表模擬器。
- de:代表實體機器。
- 應用程式的GUID。可以找尋應用程式專案,bin\debug目錄下WMAppManifest.xml檔案,的ProductID。
例如本範例的ProductID參考如圖7:

圖 7:WMAppManifest.xml檔案。
利用以下指令執行Isolated Storage Explorer 工具程式:
ISETool.exe dir xd 7150fad1-3dbe-4ce7-9f7e-ba3dd6254e8d
將會列出目前Isolated Storage中包含一個mydb.sdf檔,請參考圖8所示:

圖 8:使用Isolated Storage Explorer 工具程式。
使用Expression Blend設計資料顯示樣版
前文提及第一次執行應用程式進行測試時,畫面中ListBox控制項顯示的不是員工的資訊,而是Employee型別,若要修正這個問題,你可以為ListBox設計資料顯示樣版(Data Template)。要設計資料顯示樣版,透過Microsoft Expression Blend工具會比使用Visual Studio 2010來的方便。底下我們將說明如何使用Expression Blend,為ListBox設計資料顯示樣版。
在「Solution Explorer」視窗中,點選MainPage.xaml,按滑鼠右鍵,從突顯式選單中,選取「Open in Expression Blend」選項,就會開啟Expression Blend設計工具,請參考圖9所示。

圖 9:開啟Expression Blend設計工具編輯MainPage.xaml檔。
選取Expression Blend Artboard上的ListBox,按滑鼠右鍵,從突顯式選單中,選取「Edit Additional Templates」-「Edit Generated Items (ItemTemplate)」-「Create Empty」選項,請參考圖10所示:

圖 10:建立空白資料樣版。
Expression Blend會自動將資料樣版設定成資源,請參考圖11所示,在「Create DataTemplate Resource」對話盒中,按下「OK」按鈕,便會進入到資料樣版設計畫面。

圖 11:建立資料樣版資源。
在資料樣版設計畫面中,選取左方工具箱上的StackPanel,然後雙擊之,將它加入資料樣版,請參考圖12所示。

圖 12:設計資料樣版,加入StackPanel。
選取左方工具箱上的TextBlock,然後雙擊之,將它加入資料樣版內StackPanel之中。按相同步驟,再加入一個TextBlock,目前的設計畫面看起來如下:

圖 13::設計資料樣版,加入TextBlock。
樣版設計畫面上的TextBlock將利用資料繫結技術來顯示ID與Name的資料。選取樣版設計畫面上的第一個TextBlock,在「Properties」頁-「Common Properties」區塊中找到Text屬性,點選屬性後方「Advanced Options」按鈕,從突顯式選單中,選取「Data Binding」設定資料繫結,請參考圖14所示。

圖 14:設定資料繫結。
請參考圖15所示,在「Create Data Binding」對話方塊中,選取「Data Context」頁,勾選下方的「Use a custom path expression」項目,在後方的文字方塊中輸入「ID」,然後按下「OK」按鈕。

圖 15:繫結到Employee類 ID屬性。
請參考圖16所示,重複上面的步驟,選取樣版設計畫面上的第二個TextBlock,在「Properties」頁-「Common Properties」區塊中找到Text屬性,點選屬性後方「Advanced Options」按鈕,從突顯式選單中,選取「Data Binding」。接著在「Create Data Binding」對話方塊中,選取「Data Context」頁,勾選下方的「Use a custom path expression」項目,在後方的文字方塊中輸入「Name」,然後按下「OK」按鈕:

圖 16:繫結到Employee類別 Name屬性。
設定完成之後,選取Expression Blend選單「File」-「Save All」,將所有檔案存檔。再回到Visual Studio,在Expression Blend工具中所進行的變動,會即時更新到Visual Studio之中,產生以下DataTemplate 的XAML標籤:
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="DataTemplate1">
<Grid>
<StackPanel>
<TextBlock TextWrapping="Wrap" Text="{Binding ID}"/>
<TextBlock TextWrapping="Wrap" Text="{Binding Name}"/>
</StackPanel>
</Grid>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
ListBox的XAML標籤如下,其中ItemTemplate屬性關聯到DataTemplate1:
<ListBox Height="539" HorizontalAlignment="Left" Margin="37,32,0,0" Name="listBox1" VerticalAlignment="Top" Width="383" ItemTemplate="{StaticResource DataTemplate1}" />
好了,現在再度測試應用程式測試,在Visual Studio 2010工具中,按F5或選取「Debug」-「Start Debugging」執行程式,此時會啟動Windows Phone Emulator,並看到畫面如下,ListBox會透過資料樣版顯示資料,將Employee的ID與Name屬性呈現在畫面上。

圖 17:透過資料樣版顯示資料。
使用Expression Blend設定Style
預設Windows Phone有許多內建了Style可以套用到資料樣版中的項目,以便你修改文字顯示使用的字型、大小、顏色等等外觀。舉例來說,請參考圖18所示,在資料樣版設計畫面中,從「Objects and Timeline」區塊中選取要套用Style的TextBlock,然後從Expression Blend的「Object」-「Edit Style」-「Apply Resource」,選取一個喜好的樣式:

圖 18:設定Style。
Expression Blend會產生以下的Style定義:
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="DataTemplate1">
<Grid>
<StackPanel>
<TextBlock TextWrapping="Wrap" Text="{Binding ID}" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock TextWrapping="Wrap" Text="{Binding Name}" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</Grid>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
按F5或選取「Debug」-「Start Debugging」執行程式,此時會啟動Windows Phone Emulator,並看到畫面如下,ID的字型看起來比較大一些。

圖 19:套用Style展示資料。
資料篩選
使用LINQ的好處是透過簡單類似SQL的語法,就可以用來查詢物件模型中的資料。讓我們回到Visual Studio 2010進行畫面設計,在ListBox上方加入一個TextBox與一個TextBlock:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox Height="453" HorizontalAlignment="Left" Margin="37,118,0,0" Name="listBox1" VerticalAlignment="Top" Width="383" ItemTemplate="{StaticResource DataTemplate1}" />
<TextBox Height="66" HorizontalAlignment="Left" Margin="179,28,0,0" Name="textBox1" Text="" VerticalAlignment="Top" Width="214" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="56,46,0,0" Name="textBlock1" Text="員工編號:" VerticalAlignment="Top" />
</Grid>
TextBlock 顯示提示字串「員工編號:」;TextBox則用來輸入篩選條件,我們要將員工ID與 TextBox Text屬性相符的Employee資料取出。
下一步是修改程式碼,將建立DB物件的程式碼移到類別階層:
DB db = new DB("Data Source=isostore:/mydb.sdf");
private void PhoneApplicationPage_Loaded ( object sender , RoutedEventArgs e )
{
DB.CreateDB();
var empList = from Employee emp in db.EmployeeTable
select emp;
listBox1.ItemsSource = empList;
}
Windows Phone應用程式也支援事件處理的機制,您可以攔截事件,然後撰寫對應的事件處理常式。例如,當使用者在用來輸入篩選條件文字方塊(TextBox)輸入資料之後,您馬上要自動取出員工資料,你可以攔截TextChanged事件,這個事件會在每回文字方塊的內容變動時,自動地觸發。使用Visual Studio 2010工具程式,選取畫面上的TextBox,在「Properties」視窗,選取「Events」來設定事件 ,雙擊TextChanged事件,就會自動產生事件處理常式。Visual Studio 2010會自動在TextBox的XAML標籤中新增事件註冊語法:
<TextBox Height="66" HorizontalAlignment="Left" Margin="179,6,0,0" Name="textBox1" Text="" VerticalAlignment="Top" Width="214" TextChanged="textBox1_TextChanged" />
在textBox1的TextChanged事件處理常式加入以下程式:
private void textBox1_TextChanged ( object sender , TextChangedEventArgs e )
{
if (string.IsNullOrEmpty(textBox1.Text))
return;
int i = 0;
int.TryParse(textBox1.Text , out i);
var empList = from Employee emp in db.EmployeeTable
where emp.ID== i
select emp;
listBox1.ItemsSource = empList;
}
先利用String類別的IsNullOrEmpty方法判斷文字方塊的內容是否有輸入,接著使用int類別的TryParse方法,將文字方塊的內容轉型成int型別。接著透過LINQ查詢where運算子,設定篩選條件,再將符合條件的查詢結果呈現在ListBox之中。
按F5或選取「Debug」-「Start Debugging」執行程式,此時會啟動Windows Phone Emulator,並看到畫面如下,只要在文字方塊中輸入員工編號,符合查詢條件的員工資料馬上會出現在清單方塊之中。

圖 20:篩選資料。
修改資料
最後,我們來談談如何修改資料。我們先在設計畫面中,加入TextBlock、TextBox與Button,畫面看起來如圖21:

圖 21:設計編輯畫面。
參考的XAML標籤如下所示:
<TextBlock Height="30" HorizontalAlignment="Left" Margin="56,385,0,0" Name="textBlock2" Text="員工編號:" VerticalAlignment="Top" />
<TextBox Height="71" HorizontalAlignment="Left" Margin="170,437,0,0" Name="textBox2" Text="" VerticalAlignment="Top" Width="250" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="56,454,0,0" Name="textBlock3" Text="姓名:" VerticalAlignment="Top" />
<TextBlock Height="46" HorizontalAlignment="Left" Margin="179,385,0,0" Name="textBlock4" Text="" VerticalAlignment="Top" Width="227" />
<Button Content="儲存" Height="72" HorizontalAlignment="Left" Margin="123,514,0,0" Name="button1" VerticalAlignment="Top" Width="160" />
直接修改XAML檔案,設定右上方TextBlock的Text屬性,它的值應該來自於ListBox選到的那一個Employee物件的ID屬性:
<TextBlock Height="46" HorizontalAlignment="Left" Margin="179,385,0,0" Name="textBlock4" Text="{Binding ElementName=listBox1, Path=SelectedItem.ID}" VerticalAlignment="Top" Width="227" />
修改TextBox的Text屬性,設定它繫結到ListBox選到的那一個Employee物件的Name屬性,且Mode為TwoWay,代表支援雙向繫結:
<TextBox Height="71" HorizontalAlignment="Left" Margin="170,437,0,0" Name="textBox2" Text="{Binding ElementName=listBox1, Path=SelectedItem.Name, Mode=TwoWay}" VerticalAlignment="Top" Width="250" />
使用Visual Studio 2010工具程式,選取畫面上用來存檔的Button,在「Properties」視窗,選取「Events」來設定事件 ,雙擊Click事件產生事件處理常式。在其中,叫用DataContext類別的SubmitChanges方法,將異動的資料儲存到本機資料庫。
private void button1_Click ( object sender , RoutedEventArgs e )
{
db.SubmitChanges();
}
總結
在Windows Phone應用程式之中,可以在Isolated Storage建立本機資料庫以存放關聯式資料庫的資料。然後使用LINQ to SQL來存取本機資料庫。不過在使用上有一些限制需要注意,在Windows Phone應用程式之中,不可以直接執行T-SQL,或資料定義語法(Data Definition Language (DDL);也不可以執行資料模型語法(Data Modeling Language,DML)。大部分的ADO.NET物件都不支援,如DataReader。
範例下載: 2_PhoneApp2.rar (76.71 kb)