做個windows 8開發的或者正要做windows 8開發的需要知道一點是,在win8目前的SDK中是不支持本地數據庫的。據說現在有win8版的sqllite數據庫了,我沒用過,不過就算真的有也沒關系,我這篇博客不是講如果用使用數據庫的,而是講如果利用現有的API和資源做一個自定義的數據庫。如果你覺得使用SQllite夠用的話那你可以跳過這篇文章。
首先win8是沒有本地數據庫的(當前win8SDK版本),其次我們的APP可能就是需要數據庫來存放一些客戶端的東西,那么目前的唯一的方法就是自己做數據庫,其實這里說自己做數據庫說的有點大,因為我這里給大家介紹的不是說自己開發一個類似sqllite那種真正的數據庫(我沒這本事),而是給大家介紹一種方法可以達到類似WP7 中SQL CE那樣使用習慣和使用方法的偽數據庫(也可以稱為山寨版數據庫),如果你用過WP7的SQL CE的數據庫,你會對我介紹的方法很熟悉,用起來會很順手。
廢話不多說,進入正題。
首先數據庫的存儲方式肯定不是存儲在內存里的,而是以文件的形式存儲在“硬盤”上的。另外,既然是數據庫,那么數據在內存中的狀態應該是以集合形式存儲的。這樣我們的問題就是,把集合以文件的形式存儲,如何達到這個目的,很顯然就是利用序列化和反序列化。目前數據的序列化有兩種方式,一個是XML,一個JSON,我個人推薦Json,速度快,序列化后的體積也小。
下面正是進入編碼階段,新建一個項目叫做DatabaseTest,然后新建一個類庫項目Database,記得引用下。先對DataBase進行修改,既然我們的數據庫不是真數據庫,那么我就取名叫FakeDatabase,這是一個基類,所有以后用到數據庫的地方必須繼承自這個類。
先不管FakeDatabase,繼續新建一個類叫Table,繼承自ObservableCollection,這個類是一個密封類,看名字就知道數數據庫中的表,代碼如下:
public sealed class Table<T> : ObservableCollection<T> { internal Table() { } protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { base.OnCollectionChanged(e); IsChanged = true; if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace) { foreach (INotifyPropertyChanged item in e.NewItems) { item.PropertyChanged += item_PropertyChanged; } } } //指示表中的數據是否發生更改 internal bool IsChanged; void item_PropertyChanged(object sender, PropertyChangedEventArgs e) { IsChanged = true; } }
然后回到FakeDataBase。首先我們的目標是做一個在使用方法和使用習慣上類似WP7 SQLCe的數據庫,那么就是說在新建數據庫的時候,只要在數據庫類中添加相應的Table就行了。我不說具體實現過程了,直接貼代碼好了:
/// <summary> /// 偽數據庫基類 /// </summary> public abstract class FakeDataBase { private string DBAddress; public FakeDataBase(string dbAddress) { this.DBAddress = dbAddress; } private bool isInitialed; /// <summary> /// 初始化數據庫中的數據 /// </summary> /// <returns></returns> public virtual async Task InitialBatabaseAsync() { if (!isInitialed) { Type t = this.GetType(); foreach (var item in t.GetTypeInfo().DeclaredFields) { if (item.FieldType.Name == "Table`1") { string address = DBAddress + "_" + item.Name; var value = await DeserializeObjectFromFile(address, item.FieldType); if (value == null) { item.SetValue(this, CreateInstance(item.FieldType, null)); } else item.SetValue(this, value); } } isInitialed = true; } } /// <summary> /// 提交更改 /// </summary> /// <returns></returns> public virtual async Task SubmitChanges() { Type t = this.GetType(); foreach (var item in t.GetTypeInfo().DeclaredFields) { var itemType = item.FieldType; if (itemType.Name == "Table`1") { var info = itemType.GetTypeInfo().DeclaredFields.FirstOrDefault(c => c.Name == "IsChanged"); var table = item.GetValue(this); var value = (bool)info.GetValue(table); if (value) { string address = DBAddress + "_" + item.Name; await SerializeObjectToFile(address, item.GetValue(this)); info.SetValue(table, false); } } } } /// <summary> /// 根據文件名,將文件內容反序列化成某個對象 /// </summary> /// <param name="fileName"></param> /// <param name="type"></param> /// <returns></returns> private async Task<object> DeserializeObjectFromFile(string fileName, Type type) { StorageFolder storageFolder = ApplicationData.Current.LocalFolder; StorageFile file = null; object objTarget; try { file = await storageFolder.GetFileAsync(fileName); } catch (Exception) { } if (file == null) { objTarget = null; } else { string str = await FileIO.ReadTextAsync(file); objTarget = JsonDeserialize(str, type); } return objTarget; } /// <summary> /// 將Json字符串反序列化成對象 /// </summary> /// <param name="str"></param> /// <param name="type"></param> /// <returns></returns> private object JsonDeserialize(string str, Type type) { if (string.IsNullOrEmpty(str)) return null; DataContractJsonSerializer serializer = new DataContractJsonSerializer(type); using (Stream stream = new MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(str))) { return serializer.ReadObject(stream); } } /// <summary> /// 將某個對象序列化成Json字符串 /// </summary> /// <param name="target"></param> /// <returns></returns> private static string JsonSerializer(object target) { DataContractJsonSerializer serializer = new DataContractJsonSerializer(target.GetType()); using (Stream stream = new MemoryStream()) { serializer.WriteObject(stream, target); stream.Seek(0, SeekOrigin.Begin); using (StreamReader reader = new StreamReader(stream)) { return reader.ReadToEnd(); } } } /// <summary> /// 將某個對象序列化成文件,如果傳遞的對象為null,那么刪除原來的文件 /// </summary> /// <param name="fileName"></param> /// <param name="target"></param> /// <returns></returns> private async Task SerializeObjectToFile(string fileName, object target) { StorageFolder storageFolder = ApplicationData.Current.LocalFolder; StorageFile storageFile = null; //如果target為null,那么刪除文件 if (target == null) { storageFile = await storageFolder.GetFileAsync(fileName); if (storageFile != null) { await storageFile.DeleteAsync(); } return; } string str = JsonSerializer(target); storageFile = await storageFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting); await FileIO.WriteTextAsync(storageFile, str); } /// <summary> /// 動態創建一個類型的對象 /// </summary> /// <param name="t"></param> /// <param name="paramas"></param> /// <returns></returns> private object CreateInstance(Type t, object[] paramas) { int pCount = paramas == null ? 0 : paramas.Length; foreach (var item in t.GetTypeInfo().DeclaredConstructors) { var p = item.GetParameters(); if (p.Length == pCount) return item.Invoke(paramas); } throw new InvalidOperationException("沒有找到合適的構造函數"); } }
上面兩部分的代碼已經實現了我們上面提到的目標。下面我就介紹下如何使用這個自己做的偽數據庫。
假如我們數據庫中有一個用來存放用戶信息的表,叫UserInfo,里面有name和age兩個屬性,那么新建一個UserInfo的類。
[DataContract] public class UserInfo : BindableBase { private string name; [DataMember] public string Name { get { return name; } set { base.SetProperty(ref name, value, "Name"); } } private int age; [DataMember] public int Age { get { return age; } set { base.SetProperty(ref age, value, "Age"); } } }
然后就是創建我們的數據庫了,叫做TestDatabase
public class TestDataBase:FakeDataBase { private const string DBAddress = "TestDataBase.db"; public TestDataBase() : base(DBAddress) { } public Table<UserInfo> UserTable; }
至此,數據庫已經創建完畢。
下面講下如何操作這個數據庫。
首先我們在使用數據庫中數據庫的時候一定要先初始化數據庫,
Data.TestDataBase db; db = new Data.TestDataBase(); await db.InitialBatabaseAsync();
然后就是把數據庫中的表綁定到列表控件
listview_userlist.ItemsSource = db.UserTable;
往數據庫中添加數據:
db.UserTable.Add(addUser);
await db.SubmitChanges();
刪除數據代碼:
db.UserTable.Remove(user);
await db.SubmitChanges();
以上操作數據庫的代碼是不是看起來很熟悉?只要你用過WP7 的SQL CE,看到這些代碼你肯定會很熟悉的。
界面代碼我就不貼出來了,你可以直接下載源碼查看的,下面貼一張運行圖。
現在說下這個偽數據庫和WP7 SQLce比起來的優缺點:
先說優點:
1.數據庫中表格可以直接作為數據源幫頂到列表控件(WP7 SQL CE雖然能作為數據源,但是數據源如果有增刪的操作,那么列表控件是反應不出來的)
2.如果表的結構發生改變你不用擔心兼容性的問題(有一點需要注意,就是類型的改變需要考慮下的,比如原來是簡單類型的,后來改成復雜類型了,那么你還是另外加一個屬性吧)
3.表的結構是可以使用復雜類型作為屬性的,只要那個復雜類型標記了[DataContract]和[DataMenber]就行。
缺點:
1.雖然也支持一個數據庫中存在多個表,但是表之間不支持關聯。
2.數據表沒有索引和主鍵,你非要有主鍵的話可以在往表中添加數據的時候自己加上。
3.數據量比較大的時候會有性能問題(幾萬條數據那種)
4.數據的存儲沒有加密,不過這個問題其實可以解決的。
以上提到的缺點,說實話對於一般的數據存儲來說其實沒什么問題的。除非那些對性能(在數據量小的情況下幾乎不會有這個問題)和安全要求較高的就不適合使用這種方法。
以上所提到的方法是我在開發自己的APP中所用的方法,雖然不是完美的解決方案,但是作為應急用搓搓有余了。
說到底是一個山寨版的數據庫,歡迎拍磚!