Hands-On Lab
在Windows Phone Mango中使用本地数据库
Lab version: 1.0.0
Last updated: 2/29/2012
内容
Windows® Phone Mango提供了一种新的方式来存储和管理您的应用程序的结构化数据- Microsoft SQL Server Compact edition 数据库。本地数据库作为一个文件存储在应用程序的独立存储空间。 本次实验使用Tidy 应用程序来演示在 Windows® Phone应用程序中使用本地数据库。应用程序管理与项目相关的任务。每个任务支持附件以及预定义的数据集,包括优先级和截止时间。所有这些信息都使用本地数据库与几个主要的表:
Projects
Tasks
Attachments
Locations
下图显示在应用程序所支持的实体中包含的数据和实体之间的关系。
Figure 1
Database entities and theirassociations
Window Phone Mango 应用程序 使用LINQ to SQL 访问本地数据库。 LINQ to SQL提供了面向对象的方法,用于处理存储在数据库中的数据。System.Data.Linq.DataContext 类连接应用程序的对象模型与数据库中的数据。
目标
本次动手实验,包括下列的任务:
利用本地数据库开发 Windows Phone Mango应用程序。
了解 Tidy 应用程序实施本地数据库。
前提条件
您在开始本次动手实验前,请先确认达到下列前提条件:
Microsoft Visual Studio 2010 或者Microsoft Visual C# Express 2010, 和 Windows® Phone 7 Codenamed “Mango” DeveloperTools,下载地址: http://go.microsoft.com/?linkid=9772716
如何创建 Windows® Phone7应用程序
熟悉基本的SQL术语
架构
Tidy 应用程序使用 MVVM 模式。 MVVM决定了应用程序分成三个不同的部分:
View –用户接口,并通过用户操作ViewModel
ViewModel – 连接用户的操作和应用程序的数据
Model – 管理应用程序的数据
Tidy应用程序以下列方式划分:
DataContextBase类(继承自System.Data.Linq.DataContext )实现了 Model。允许执行本地应用程序对象上的操作,以影响应用程序的数据库中的数据。
ViewModelItemsBase和 ViewModelBase类(以及这两个类的子类)实现了ViewModel。这些类隐藏了用户界面,提供简单的访问模型的内容,以及通过简化,如确定新的任务,更新项目等的各种应用程序的网页,形成应用程序的用户界面,实现视图的数据操纵操作模式本身。
LINQ to SQL
LINQ to SQL是.Net框架的ORM(对象关系映射)平台的数据库。当应用程序执行LINQ语句在运行时,它转换为Transact- SQL对数据库执行操作;一旦数据库返回查询结果,LINQ to SQL将数据转换为应用程序对象。
了解更多有关LINQ to SQL的知识请访问http://msdn.microsoft.com/en-us/library/bb425822.aspx
NOTE:如果你熟悉在桌面上使用LINQ to SQL,请注意在Windows Phone中使用LINQ to SQL的一些限制,不支持“原始”的Transact – SQL、DML(数据建模语言),DDL(数据定义语言),不提供直接访问ADO.NET对象。
实验提纲
这个动手实验将包括一个完整的实验,包括下列的任务:
创建数据库
部署一个现有的数据库
获取数据
插入数据
更新数据
删除数据
预计完成时间
完成该实验预计花费15到30分钟的时间。
本次实验将重点介绍ViewModel. 实验提供了 “guided tour”的相关代码,将让您获得更深入的了解如何在您未来编写的应用程序中使用本地数据库.
任务 1 – 定义一个数据上下文
System.Data.Linq.DataContext类连接应用程序的对象模型与数据库中的数据。 应用程序对象映射到数据库的对象需要你实现从System.Data.Linq.DataContext 类派生的一个类。 同时你还要将属性添加到对象模型中. 数据上下文负责将对象映射到相应的数据库实体。
推荐的方法是在WindowsPhone优先生成数据库代码。您手动生成类,并提供相应的属性,然后在运行时,数据上下文创建在你的对象模型中定义的关系数据库。
它也有可能采用一个现有的数据库,并使用SqlMetaltool自动生成一个匹配的数据上下文. 重要的是要注意SqlMetal是不完全兼容的WindowsPhone,它生成的代码将无法编译。
Tidy 应用程序的数据上下文由SqlMetal生成。生成了对象模型类的部分方法和事件,当应用程序数据改变时,将执行具体的代码。
打开本实验安装文件夹下的 Source\End 文件. 导航到Todo.Business 项目, 展开 Models 文件并打开 DataContextBase.cs文件查看 DataContextBase 类(应用程序数据上下文).
在DataContextBase 类中 每个Table<T> 类型对应数据库中的一张表。 其中T是表中所代表的实体类型。
注意DataContextBase中的每一个 Table<T> 属性 , T具有 Table 的属性。此属性定义包含T类型的实体数据表。
请注意在对象模型中的所有的类都实现了IPropertyChangedNotifier 接口, 它们是如何转换成 INotifyPropertyChanged的。这种转换允许你绑定用户界面对象中的数据部分。 实体类也实现了 INotifyPropertyChanging 接口, 此接口允许 SQL CE 进行缓存优化和管理数据, ITodoTable 公开ID 属性。
这里是一部分代码用于定义数据上下文.该代码演示了如何定义Task表的DataContextBase 类以及如何在类中定义其属性与数据库列之间的联系,以及 Attachment类所代表的不同实体的联系:
C#
public partial class DataContextBase : System.Data.Linq.DataContext
{
private static System.Data.Linq.Mapping.MappingSource mappingSource =
new AttributeMappingSource();
publicDataContextBase(string connection) :
base(connection,mappingSource)
{
OnCreated();
}
publicDataContextBase(string connection,
System.Data.Linq.Mapping.MappingSource mappingSource) :
base(connection,mappingSource)
{
OnCreated();
}
publicSystem.Data.Linq.Table<Task> Items
{
get
{
returnthis.GetTable<Task>();
}
}
// The rest of the class was omitted
}
[Table( Name="Tasks")]
public partial class Task : INotifyPropertyChanging,INotifyPropertyChanged, ITodoTable
{
[Column(Storage= "_EstimateTime", DbType = "DateTime")]
publicSystem.Nullable<System.DateTime> EstimateTime
{
get
{
returnthis._EstimateTime;
}
set
{
if((this._EstimateTime != value))
{
this.OnEstimateTimeChanging(value);
this.SendPropertyChanging();
this._EstimateTime= value;
this.SendPropertyChanged("EstimateTime");
this.OnEstimateTimeChanged();
}
}
[Association(Name= "Attachment_Items", Storage = "_Attachment",
ThisKey = "ItemID",OtherKey = "ItemID", DeleteRule = "NO ACTION")]
public EntitySet<Attachment>Attachment
{
get
{
returnthis._Attachment;
}
set
{
this._Attachment.Assign(value);
}
}
// The rest of the class was omitted
}
我们曾提到上述代码中的属性,这些属性允许你定义表,列,键和索引。
LINQ to SQL的一些属性:
虽然我们在此应用程序中使用SqlMetal 定义数据上下文,但我们还是使用Task类来演示看如何创建一个完整的实体。
首先通过定义类和声明Table 属性:
C#
[Table]
public partial class Task
{
}
现在定义类的属性:
C#
[Table]
public partial class Task
{
publicTaskStatus Status { get;set; }
publicPriorityValue Priority { get; set; }
publicSystem.Guid Id { get;set; }
publicSystem.Guid ProjectID { get; set; }
publicSystem.Guid LocationID { get; set; }
publicbool Completed { get;set; }
publicstring Title { get;set; }
publicSystem.Nullable<System.DateTime> DueDate { get;set; }
publicEntitySet<Attachment>Attachment {get; set; }
publicProject Projects { get;set; }
publicLocation Location { get;set; }
}
添加Column属性定义的列将映射到不同的属性。注意有一些没有这个属性,我们将在下一步进行处理:
C#
public partial class Task
{
[Column(Storage = "_Status", DbType="Int")]
publicTaskStatus Status { get;set; }
[Column(Storage= "_Priority", DbType = "Int")]
publicPriorityValue Priority { get; set; }
[Column(Storage= "_ItemID", DbType = "UniqueIdentifier NOT NULL",
IsPrimaryKey = true)]
publicSystem.Guid Id { get;set; }
[Column(Storage= "_ProjectID", DbType = "UniqueIdentifier NOT NULL")]
publicSystem.Guid ProjectID { get; set; }
[Column(Storage= "_LocationID", DbType = "UniqueIdentifier")]
publicSystem.Guid LocationID { get; set; }
[Column(Storage= "_Completed", DbType = "Bit NOT NULL")]
publicbool Completed { get;set; }
[Column(Storage= "_Title", DbType = "NVarChar(100) NOT NULL",
CanBeNull = false)]
publicstring Title { get;set; }
[Column(Storage= "_DueDate", DbType = "DateTime")]
publicSystem.Nullable<System.DateTime> DueDate { get;set; }
publicEntitySet<Attachment>Attachment {get; set; }
publicProject Projects { get;set; }
publicLocation Location { get;set; }
}
其余的属性加入 Association . 这些属性表示不同的实体的引用:
C#
public partial class Task
{
[Column( Storage = "_Status",DbType="Int")]
publicTaskStatus Status { get;set; }
[Column(Storage= "_Priority", DbType = "Int")]
publicPriorityValue Priority { get; set; }
[Column(Storage= "_ItemID", DbType = "UniqueIdentifier NOT NULL",
IsPrimaryKey = true)]
publicSystem.Guid Id { get;set; }
[Column(Storage= "_ProjectID", DbType = "UniqueIdentifier NOT NULL")]
publicSystem.Guid ProjectID { get; set; }
[Column(Storage= "_LocationID", DbType = "UniqueIdentifier")]
publicSystem.Guid LocationID { get; set; }
[Column(Storage= "_Completed", DbType = "Bit NOT NULL")]
publicbool Completed { get;set; }
[Column(Storage= "_Title", DbType = "NVarChar(100) NOT NULL",
CanBeNull = false)]
publicstring Title { get;set; }
[Column(Storage= "_DueDate", DbType = "DateTime")]
publicSystem.Nullable<System.DateTime> DueDate { get;set; }
[Association(Name= "Attachment_Items", Storage = "_Attachment",
ThisKey = "Id",OtherKey = "ItemID", DeleteRule = "NO ACTION")]
publicEntitySet<Attachment>Attachment {get; set; }
[Association(Name= "Project_Item", Storage = "_Projects",
ThisKey = "ProjectID",OtherKey = "Id", IsForeignKey = true)]
publicProject Projects { get;set; }
[Association(Name= "Items_Location", Storage = "_Location",
ThisKey = "LocationID",OtherKey = "Id", IsForeignKey=true)]
publicLocation Location { get;set; }
}
我们刚才定义的关联任务的附件设置,该项目的任务属于任务的相关位置。工作实体现在正确映射到数据库中。在实际中代码的执行情况是不同的,因为我们没有包括SqlMetal自动引入的各种挂钩,也没有实现前面提到的IPropertyNotifier接口。
任务2 – 创建数据库
您刚才看到了如何定义对象模型和把它映射到一个数据库时所需要的属性。这一步将讨论如何创建基于该架构的数据库。
创建一个数据上下文的实例。观察位于App.xaml.cs的App类的构造函数中的TODO代码:
C#
TodoDC = new DataContextBase("DataSource='" + DatabaseFilename + "'"+
(DatabasePassword.Length== 0 ? "" : ";Password='" + DatabasePassword + "'"));
这将创建一个由应用程序使用的 DataContextBase的新实例。数据库的备份文件的位置由常量DatabaseFilename 设置,其内容为:“isostore:ToDo.sdf”.
DataHelper 类(位于 Todo.Business 项目Data 文件夹下), 使用DataContextBase 实例创建实际的数据库。 DataHelper 类提供的方法用来检查是否已经在应用程序的独立存储中创建并初始化应用程序的数据库。数据库一旦建立使用DataHelper类初始化数据库数据,并定义可用的附件类型。
打开 DataHelper.cs文件,查看DataHelper类的CreateDatabase 方法:
C#
private static voidCreateDatabase(DataContextBase todo)
{
try
{
// Generate the database (with structure) from the code-
// based data context
todo.CreateDatabase();
// Populate the database with system data
GenerateSystemData(todo);
// Create an initial task
Task items = new Task();
items.ItemID = Guid.NewGuid();
items.Title = "Welcome to the \"Todo\"!";
items.Completed = true;
todo.Items.InsertOnSubmit(items);
todo.SubmitChanges();
}
catch(Exception ex)
{
MessageBox.Show("Errorwhile creating the DB: " + ex.Message);
System.Diagnostics.Debug.WriteLine("Errorwhile creating the DB: " +
ex.Message);
}
}
在上述方法中,只有一行代码用于创建 数据库-DataContextBase的CreateDatabase 方法的调用, 它继承自 System.Data.Linq.DataContext。该方法的其余部分填充新的数据库初始数据.这部分代码只在应用程序第一次运行时运行。 一旦已经创建了一个数据库,应用程序的后续操作只需打开现有的数据库,在DataHelper类的 InitializeDatabase方法中可以看到:
C#
public static voidInitializeDatabase(DataContextBase todo)
{
using (IsolatedStorageFile iso =
IsolatedStorageFile.GetUserStoreForApplication())
{
if(iso.FileExists("ToDo.sdf"))
return;
CreateDatabase(todo);
}
}
任务 3 – 部署一个现有的数据库
有一个从头开始创建一个新的数据库的替代,就是部署一个预定义的数据库。 初始数据库嵌入到应用程序的xap文件中的过程与任何其他资源(如图像或声音文件)一样。应用程序加载数据库文件,并将其写入独立存储。 一旦独立存储存在数据库,则由应用程序做好使用准备。
这种方法通常适用于数据库需要包含大量的参考数据。一个很好的例子就是一个数据库中包含大量的数据(字典)。Tidy应用程序没有什么太大的初始数据,所以不使用这种方法。
部署一个现成的数据库有三个简单的步骤:
初始数据库文件资源访问的资源流。
创建一个独立的存储文件作为应用程序的数据库文件.
初始数据库文件的内容写入独立存储文件
下面的代码演示了上面的步骤:
C#
private void CreateDatabaseFromResourceFile(string inFilename, stringoutFilename)
{
// Step 1
Streamstr = Application.GetResourceStream(new Uri(inFilename, UriKind.Relative)).Stream;
// Step 2
IsolatedStorageFileStreamoutFile = iso.CreateFile(outFilename);
// Step 3
outFile.Write(ReadToEnd(str), 0, (int)str.Length);
str.Close();
outFile.Close();
}
“ReadToEnd”只是简单地返回一个字节数组部署的数据库文件流的全部内容。
任务 4 – 获取数据
现在,我们已经看到了如何初始化应用程序的数据库,现在是时候来看看我们如何访问内部的数据库中包含的数据。 我们将展示 TaskViewModel 类,它位于ViewModels文件夹下的Todo项目中。TaskViewModel 是访问应用程序的模型,直接使用的数据上下文。
Note:虽然在本实验中,我们使用 TaskViewModel 作为例子,但任何从 ViewModelItemsBase 继承的类都可以作为示例。
观察TaskViewModel 类的LoadData 方法
C#
public override void LoadData()
{
PropertyChanged+= new PropertyChangedEventHandler(TasksViewModel_PropertyChanged);
var tasks = from t in todoDC.Items select t;
foreach (var task in tasks)
{
Items.Add(task);
task.PropertyChanged += task_PropertyChanged;
}
//Only register this now, so that we do not fire updates for each and
//every item loaded
Items.CollectionChanged+= Items_CollectionChanged;
IsDataLoaded= true;
NotifyCollectionChanges();
}
ViewModel关联需要更新的用户界面,并没有直接使用数据库中的数据.
上面深背景色的代码用于查询所有的task。它使用“todoDC”(其中包含相同的实例,我们在任务2的第1步已经展示了如何创建它)中的数据上下文实例。
上面的代码在集合中添加所有task,并保持最新的接口寄存器。
观察 TaskViewModel 类的 NotifyCollectionChanges 方法:
C#
private void NotifyCollectionChanges()
{
NotifyPropertyChanged("Today");
NotifyPropertyChanged("Urgent");
NotifyPropertyChanged("Completed");
NotifyPropertyChanged("ByDate");
}
上面的方法为几个TaskViewMode的属性,作为一个在数据库中过滤收集的task并发送更改通知。
观察TaskViewModel 类的 Today 属性。此属性包含今天到期的所有task和更新task的截止日期时更新(见task_PropertyChangedhandler)或集合本身的变化(见Items_CollectionChangedhandler):
C#
public IOrderedEnumerable<Task>Today
{
get
{
return from task in App.TasksViewModel.Items
where task.DueDate.HasValue && task.DueDate.Value.Date == DateTime.Now.Date
orderby task.Priority
select task;
}
}
任务5 – 插入数据
插入到数据库中的新的数据是我们在前面的实验中已经添加的。我们将回过头查看早先的代码,以及研究TaskViewModel中有关的部分。
观察 DataHelper的 CreateDatabase 方法的以下部分:
C#
...
// Create an initial task
Task items = new Task();
items.ItemID = Guid.NewGuid();
items.Title = "Welcome to the\"Todo\" application!";
items.Completed = true;
todo.Items.InsertOnSubmit(items);
todo.SubmitChanges();
.
将数据插入数据库是一个两步过程。首先创建一个实体对象,调用InsertOnSubmit方法将对象添加到数据上下文中,然后调用SubmitChanges方法来保持数据作为数据库中的行的数据上下文。你必须调用SubmitChanges 方法,以确定他们对数据库进行更改。
打开EditTaskView.xaml.cs这是EditTaskView类的代码隐藏文件。导航到OnNavigatedTo方法,你应该能看到下面的一段代码:
C#
...
if(NavigationContext.QueryString.ContainsKey(UIConstants.ProjectIdQueryParam))
{
// We navigatedto the page to create a new task for the specified project
PageTitle.Text = ApplicationStrings.TaskEditNewTaskTitle;
task = newTask();
.
当用户选择保存新的task,TaskViewModel的 Insert方法将task保存到数据库中:
C#
public void Insert(Taskitem)
{
Items.Add(item);
todoDC.Items.InsertOnSubmit(item);
todoDC.SubmitChanges();
}
任务 6 – 更新数据
更新数据在数据库中已经存在,甚至比插入新的数据简单。第一步,将获得一个现有实体的引用。
导航到EditTaskView类,查看GetTaskFromQueryString方法:
C#
private void GetTaskFromQueryString()
{
GuidtaskGuid = NavigationContext.GetGuidParam(
UIConstants.TaskIdQueryParam);
task = App.TasksViewModel.Items.FirstOrDefault(t =>t.ItemID == taskGuid);
}
根据ID在TaskViewModel 中查询到要更新的对象。
一旦查询到要更新的对象,下一步就是要该变它的某个属性。在这个应用程序中没有相对应的代码,因为数据的更新通过数据绑定实现。下面的代码演示了除了数据绑定,如何更改对象的属性:
C#
task.Title = "NewTitle";
下面的代码显示了TaskViewModel的Update方法中调用SubmitChanges更新本地数据库中的数据。在SubmitChanges方法调用之前,数据是不会更新至数据库的:
C#
public void Update(Taskitem)
{
todoDC.SubmitChanges();
}
任务 7 –删除数据
删除数据与插入或更新数据相似,通过查看 TaskViewModel 类来解释:
导航到 TaskViewModel 类的 Delete 方法:
C#
public void Delete(GuiditemID)
{
App.AttachmentViewModel.DeleteByTaskID(itemID);
var res = from i in Items
where i.ItemID == itemID
select i;
Taskitm = res.FirstOrDefault();
if(null != itm)
{
Items.Remove(itm);
todoDC.Items.DeleteOnSubmit(itm);
todoDC.SubmitChanges();
}
}
删除数据库中的数据也包括三个步骤。首先,查询的数据库中删除的对象。然后,取决于您是否要删除一个或多个对象,调用DeleteOnSubmit方法,分别放在挂起的删除状态的那些对象。最后,调用SubmitChanges方法,以将所做的更改保存到本地数据库
该实验室通过必要的步骤,设置在一个WindowsPhone应用程序中使用本地数据库。在初始设置,主要包括数据上下文的情况下,使用LINQ to SQL的定义,使你几乎无视存在的数据库。使用这种新的功能,可以很容易的创建处理大量数据的应用程序。