Windows 8 Metro開發疑難雜症(四)——(偽)數據庫


做個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中所用的方法,雖然不是完美的解決方案,但是作為應急用搓搓有余了。

說到底是一個山寨版的數據庫,歡迎拍磚!

源碼下載

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM