與眾不同 windows phone (7) - Local Database(本地數據庫)
作者:webabcd
介紹
與眾不同 windows phone 7.5 (sdk 7.1) 之本地數據庫
- 概述
- 演示如何使用“本地數據庫”
示例
1、概述
Summary.xaml
<phone:PhoneApplicationPage x:Class="Demo.LocalDatabase.Summary" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}" SupportedOrientations="Portrait" Orientation="Portrait" mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480" shell:SystemTray.IsVisible="True"> <Grid x:Name="LayoutRoot" Background="Transparent"> <TextBlock TextWrapping="Wrap"> <Run>本地數據庫概述</Run> <LineBreak /> <LineBreak /> <Run>1、App 創建數據庫時,其文件會被保存到獨立存儲;程序包內數據庫只能被讀取,如果需要更新它,則必須把其復制到獨立存儲后再操作</Run> <LineBreak /> <Run>2、數據庫結構發生改變時優先使用 DatabaseSchemaUpdater 來更新數據庫結構;數據遷移是下策</Run> <LineBreak /> <Run>3、只讀場景下建議將 DataContext 的 ObjectTrackingEnabled 設置為 false(因為只讀時不需要對象跟蹤),從而關閉對象跟蹤以減小內存使用量</Run> <LineBreak /> <Run>4、在多線程操作本地數據庫的場景下,建議使用互斥鎖,即 System.Threading.Mutex</Run> </TextBlock> </Grid> </phone:PhoneApplicationPage>
2、使用“本地數據庫”的 Demo
Model層 - ModelBase.cs
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.ComponentModel; using System.Data.Linq.Mapping; using System.Data.Linq; namespace Demo.LocalDatabase.Model { public class ModelBase : INotifyPropertyChanged, INotifyPropertyChanging { // 實現 INotifyPropertyChanged 是為了屬性變更后的通知 public event PropertyChangedEventHandler PropertyChanged; protected void NotifyPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } // 實現 INotifyPropertyChanging 是為了最大限度地減少內存使用量(NotifyPropertyChanging 的用法:在屬性賦值之前調用,具體可見 Category 或 Product) /* * 為什么會減少內存使用量呢? * 因為 LINQ to SQL 更改跟蹤是通過維護每個對象的兩個副本進行工作的,第一個副本保存原始數據,第二個副本有程序更改,這樣提交更新時 LINQ to SQL 就知道哪些數據被更改了,從而只提交這些被更改的數據 * INotifyPropertyChanging 接口允許應用程序在將修改后的數據提交到數據庫前通知 DataContext,DataContext 可以將該通知用作創建副本的觸發器,這樣就不用保留第二個副本了,從而減少內存使用 */ public event PropertyChangingEventHandler PropertyChanging; protected void NotifyPropertyChanging(string propertyName) { if (PropertyChanging != null) { PropertyChanging(this, new PropertyChangingEventArgs(propertyName)); } } } }
Model層 - Category.cs
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Data.Linq.Mapping; using System.Data.Linq; namespace Demo.LocalDatabase.Model { /* * Table - 將類標記為數據庫中的一個表 * Index - 把類中的指定字段標記為索引字段 */ [Table] public class Category : ModelBase { // 版本列,可以顯著改進表的更新性能 [Column(IsVersion = true)] private Binary _version; private int _categoryId; /* * Column - 將屬性標記為數據表中的一個字段 * IsPrimaryKey - 是否是主鍵 * IsDbGenerated - 數據是否由數據庫自動生成,如自增列 * DbType = "INT NOT NULL Identity" - int類型,不能為null,標識列 * CanBeNull - 是否可以為 null * AutoSync = AutoSync.OnInsert - 標記此值的作用是:當數據添加完成后,此屬性的值會自動同步為數據庫自增后的值 */ [Column(DbType = "INT NOT NULL IDENTITY", IsDbGenerated = true, IsPrimaryKey = true)] public int CategoryId { get { return _categoryId; } set { NotifyPropertyChanging("CategoryId"); _categoryId = value; NotifyPropertyChanged("CategoryId"); } } private string _name; [Column] public string Name { get { return _name; } set { NotifyPropertyChanging("Name"); _name = value; NotifyPropertyChanged("Name"); } } private EntitySet<Product> _products; /* * Association - 用於標記表之間的關聯關系 * Storage - 指定用於保存關聯數據的私有字段。本例中類型為 EntitySet<T> 的私有字段 _products 用於保存關聯數據,本例中所謂的關聯數據就是 Category 下的 Products * ThisKey - 關聯數據在本表中所對應的 key 字段 * OtherKey - 關聯數據在他表中所對應的 key 字段 * IsForeignKey - 是否是外鍵 */ [Association(Storage = "_products", ThisKey = "CategoryId", OtherKey = "_categoryId")] public EntitySet<Product> Products { get { return this._products; } set { /* * Assign() - 將一個 EntitySet<T> 賦值給另一個 EntitySet<T> */ // 將 value 賦值給 _products this._products.Assign(value); } } // 指定 _products 做添加和刪除操作時的關聯操作 public Category() { _products = new EntitySet<Product> ( new Action<Product>(this.attach), new Action<Product>(this.detach) ); } // _products 添加 Product 時的關聯操作 private void attach(Product product) { NotifyPropertyChanging("Product"); product.Category = this; } // _products 刪除 Product 時的關聯操作 private void detach(Product product) { NotifyPropertyChanging("Product"); product.Category = null; } } }
Model層 - Product.cs
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Data.Linq.Mapping; using System.Data.Linq; using Microsoft.Phone.Data.Linq.Mapping; namespace Demo.LocalDatabase.Model { /* * Table - 將類標記為數據庫中的一個表 * Index - 把類中的指定字段標記為索引字段 */ [Table] [Index(Columns = "Name ASC, ProductId DESC", Name="MyIndex")] public class Product : ModelBase { // 版本列,可以顯著改進表的更新性能 [Column(IsVersion = true)] private Binary _version; private int _productId; /* * Column - 將屬性標記為數據表中的一個字段 * IsPrimaryKey - 是否是主鍵 * IsDbGenerated - 數據是否由數據庫自動生成,如自增列 * DbType = "INT NOT NULL Identity" - int類型,不能為null,標識列 * CanBeNull - 是否可以為 null * AutoSync = AutoSync.OnInsert - 標記此值的作用是:當數據添加完成后,此屬性的值會自動同步為數據庫自增后的值 */ [Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)] public int ProductId { get { return _productId; } set { if (_productId != value) { NotifyPropertyChanging("ProductId"); _productId = value; NotifyPropertyChanged("ProductId"); } } } private string _name; [Column] public string Name { get { return _name; } set { if (_name != value) { NotifyPropertyChanging("Name"); _name = value; NotifyPropertyChanged("Name"); } } } private double _price; [Column] public double Price { get { return _price; } set { if (_price != value) { NotifyPropertyChanging("Price"); _price = value; NotifyPropertyChanged("Price"); } } } [Column] internal int _categoryId; private EntityRef<Category> _category; /* * Association - 用於標記表之間的關聯關系 * Storage - 指定用於保存關聯數據的私有字段。本例中類型為 EntityRef<T> 的私有字段 _category 用於保存關聯數據,本例中所謂的關聯數據就是 Product 所屬的 Category * ThisKey - 關聯數據在本表中所對應的 key 字段 * OtherKey - 關聯數據在他表中所對應的 key 字段 * IsForeignKey - 是否是外鍵 */ [Association(Storage = "_category", ThisKey = "_categoryId", OtherKey = "CategoryId", IsForeignKey = true)] public Category Category { get { return _category.Entity; } set { NotifyPropertyChanging("Category"); // 更新 Storage 以及 ThisKey _category.Entity = value; if (value != null) _categoryId = value.CategoryId; NotifyPropertyChanging("Category"); } } } }
Model層 - MyContext.cs
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Data.Linq; namespace Demo.LocalDatabase.Model { public class MyContext : DataContext { public MyContext(string connectionString) : base(connectionString) { } public Table<Product> Products; public Table<Category> Categories; } }
ViewModel層 - MyViewModel.cs
/* * 連接字符串設置 * 1、data source - 本地數據庫文件地址 * 示例:data source=isostore:/database.sdf;appdata:/ 代表程序包內,isostore:/ 代表獨立存儲,默認為獨立存儲 * 2、pwd - 密碼 * 3、max buffer size - 最大內存使用量(保存到磁盤之前會使用內存),默認值為 384,最大值為 5120,單位為 KB * 4、max database size - 數據庫文件的最大大小,默認值為 32,最大值為 512,單位為 MB * 5、file mode - 操作數據庫文件時的模式 * Read Write - 可讀寫,默認值 * Read Only - 只讀 * Exclusive - 不允許其他進程打開或修改數據庫 * Shared Read - 數據庫打開后,允許其他進程讀取,但不允許其他進程修改 * 6、Culture Identifier - 區域代碼,如中國大陸地區是 zh-CN(創建數據庫時此屬性才有用) * 7、Case Sensitive - 排序時是否區分大小寫,默認值為 false(創建數據庫時此屬性才有用) */ using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.ComponentModel; using Demo.LocalDatabase.Model; using Microsoft.Phone.Data.Linq; using System.Collections.ObjectModel; using System.Linq; using System.Data.Linq; namespace Demo.LocalDatabase.ViewModel { public class MyViewModel : INotifyPropertyChanged { private MyContext _context; public MyViewModel() { DataLoadOptions dlo = new DataLoadOptions(); dlo.LoadWith<Category>(p => p.Products); dlo.AssociateWith<Category>(p => p.Products.OrderByDescending(x => x.Price)); _context = new MyContext("Data Source=isostore:/database.sdf"); _context.LoadOptions = dlo; Init(); Categories = new ObservableCollection<Category>(_context.Categories.ToList()); } public void AddProduct(Product product) { var category = Categories.Single(p => p.CategoryId == product.Category.CategoryId); category.Products.Add(product); _context.Products.InsertOnSubmit(product); _context.SubmitChanges(); } public void DeleteProduct(Product product) { var category = Categories.Single(p => p.CategoryId == product.Category.CategoryId); category.Products.Remove(product); _context.Products.DeleteOnSubmit(product); _context.SubmitChanges(); } public void Init() { if (!_context.DatabaseExists()) { _context.CreateDatabase(); DatabaseSchemaUpdater dbUpdater = _context.CreateDatabaseSchemaUpdater(); dbUpdater.DatabaseSchemaVersion = 0; dbUpdater.Execute(); Category c1 = new Category { Name = "肉" }; c1.Products.Add(new Product { Name = "牛肉", Price = 35.5 }); c1.Products.Add(new Product { Name = "羊肉", Price = 38 }); Category c2 = new Category { Name = "水果" }; c2.Products.Add(new Product { Name = "蘋果", Price = 2.5 }); c2.Products.Add(new Product { Name = "香蕉", Price = 3.2 }); Category c3 = new Category { Name = "蔬菜" }; c3.Products.Add(new Product { Name = "菠菜", Price = 2.2 }); c3.Products.Add(new Product { Name = "白菜", Price = 1.3 }); _context.Categories.InsertOnSubmit(c1); _context.Categories.InsertOnSubmit(c2); _context.Categories.InsertOnSubmit(c3); _context.SubmitChanges(); } else { /* * DatabaseSchemaUpdater - 數據庫結構更新器 * DatabaseSchemaVersion - 數據庫結構的版本 * Execute() - 更新數據庫結構和版本號 * * 數據庫結構有改變時,用以下方法更新不同版本的數據庫結構 * AddColumn<T>(string columnPropertyName) - 為表 T 添加列 columnPropertyName * AddIndex<T>(string indexName) - 為表 T 添加索引 indexName * AddTable<T>() - 為數據庫添加表 T * AddAssociation<T>(string associationPropertyName) - 為表 T 添加數據庫關聯 associationPropertyName */ DatabaseSchemaUpdater dbUpdater = _context.CreateDatabaseSchemaUpdater(); if (dbUpdater.DatabaseSchemaVersion == 10) { dbUpdater.AddColumn<Product>("UpdateTime"); dbUpdater.DatabaseSchemaVersion = 11; dbUpdater.Execute(); } } } private ObservableCollection<Category> _categories; public ObservableCollection<Category> Categories { get { return _categories; } set { _categories = value; NotifyPropertyChanged("Categories"); } } public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } }
View層 - App.xaml
<Application x:Class="Demo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:localDatabaseViewModel="clr-namespace:Demo.LocalDatabase.ViewModel"> <Application.Resources> <localDatabaseViewModel:MyViewModel x:Key="LocalDatabaseViewModel" /> </Application.Resources> </Application>
View層 - NewProduct.xaml
<phone:PhoneApplicationPage x:Class="Demo.LocalDatabase.NewProduct" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}" SupportedOrientations="Portrait" Orientation="Portrait" mc:Ignorable="d" d:DesignHeight="696" d:DesignWidth="480" shell:SystemTray.IsVisible="True" xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit" DataContext="{Binding Source={StaticResource LocalDatabaseViewModel}}"> <Grid x:Name="LayoutRoot" Background="Transparent"> <StackPanel Orientation="Vertical"> <TextBlock Text="Product Name"/> <TextBox x:Name="txtProductName"/> <TextBlock Text="Product Price"/> <TextBox x:Name="txtProductPrice"/> <TextBlock Text="Product Category"/> <toolkit:ListPicker x:Name="listPickerCategory" ItemsSource="{Binding Categories}" DisplayMemberPath="Name" /> </StackPanel> </Grid> <phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar Mode="Default" IsVisible="True"> <shell:ApplicationBarIconButton x:Name="btnConfirm" IconUri="/ApplicationBarDemo/Assets/appbar.check.rest.png" Text="確認" Click="btnConfirm_Click" /> <shell:ApplicationBarIconButton x:Name="btnCancel" IconUri="/ApplicationBarDemo/Assets/appbar.cancel.rest.png" Text="取消" Click="btnCancel_Click" /> </shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar> </phone:PhoneApplicationPage>
View層 - NewProduct.xaml.cs
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using Microsoft.Phone.Controls; using Demo.LocalDatabase.ViewModel; using Demo.LocalDatabase.Model; namespace Demo.LocalDatabase { public partial class NewProduct : PhoneApplicationPage { public NewProduct() { InitializeComponent(); } private void btnConfirm_Click(object sender, EventArgs e) { double price = 0; if (!double.TryParse(txtProductPrice.Text, out price)) { MessageBox.Show("價格必須是 double 型"); return; } var vm = Application.Current.Resources["LocalDatabaseViewModel"] as MyViewModel; Product product = new Product() { Name = txtProductName.Text, Price = double.Parse(txtProductPrice.Text), Category = (Category)listPickerCategory.SelectedItem }; vm.AddProduct(product); NavigationService.Navigate(new Uri("/LocalDatabase/Demo.xaml", UriKind.Relative)); } private void btnCancel_Click(object sender, EventArgs e) { } } }
View層 - Demo.xaml
<phone:PhoneApplicationPage x:Class="Demo.LocalDatabase.Demo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}" SupportedOrientations="Portrait" Orientation="Portrait" mc:Ignorable="d" d:DesignHeight="696" d:DesignWidth="480" shell:SystemTray.IsVisible="True" DataContext="{Binding Source={StaticResource LocalDatabaseViewModel}}" xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"> <phone:PhoneApplicationPage.Resources> <DataTemplate x:Key="item"> <ListBox ItemsSource="{Binding Products}"> <ListBox.ItemTemplate> <DataTemplate> <Grid Width="460" HorizontalAlignment="Center"> <Grid.ColumnDefinitions> <ColumnDefinition Width="5*" /> <ColumnDefinition Width="5*" /> </Grid.ColumnDefinitions> <StackPanel Orientation="Horizontal" VerticalAlignment="Center"> <TextBlock Text="{Binding Name}" /> <TextBlock Text="{Binding Price}" Margin="10 0 0 0" /> </StackPanel> <StackPanel Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Right"> <Button x:Name="btnDelete" Content="刪除" Click="btnDelete_Click" /> </StackPanel> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </DataTemplate> <DataTemplate x:Key="header"> <TextBlock Text="{Binding Name}" /> </DataTemplate> </phone:PhoneApplicationPage.Resources> <Grid x:Name="LayoutRoot" Background="Transparent"> <controls:Pivot x:Name="pivot" Title="產品列表" ItemTemplate="{StaticResource item}" HeaderTemplate="{StaticResource header}" ItemsSource="{Binding Categories}"> </controls:Pivot> </Grid> <phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar Mode="Default" IsVisible="True"> <shell:ApplicationBarIconButton x:Name="btnAddProduct" IconUri="/ApplicationBarDemo/Assets/appbar.add.rest.png" Text="添加產品" Click="btnAddProduct_Click" /> </shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar> </phone:PhoneApplicationPage>
View層 - Demo.xaml.cs
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using Microsoft.Phone.Controls; using Demo.LocalDatabase.ViewModel; using Demo.LocalDatabase.Model; namespace Demo.LocalDatabase { public partial class Demo : PhoneApplicationPage { public Demo() { InitializeComponent(); } private void btnAddProduct_Click(object sender, EventArgs e) { NavigationService.Navigate(new Uri("/LocalDatabase/NewProduct.xaml", UriKind.Relative)); } private void btnDelete_Click(object sender, RoutedEventArgs e) { var vm = Application.Current.Resources["LocalDatabaseViewModel"] as MyViewModel; vm.DeleteProduct((sender as Button).DataContext as Product); } } }
OK
[源碼下載]