Win8 牛人自制的 类 sql ce (伪)数据库

来源:互联网 发布:一级域名指向二级域名 编辑:程序博客网 时间:2024/06/03 21:27

做个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中所用的方法,虽然不是完美的解决方案,但是作为应急用搓搓有余了。

说到底是一个山寨版的数据库,欢迎拍砖!

源码下载

 

 

数据契约(DataContract)

服务契约定义了远程访问对象和可供调用的方法,数据契约则是服务端和客户端之间要传送的自定义数据类型。

一旦声明一个类型为DataContract,那么该类型就可以被序列化在服务端和客户端之间传送,如下所示。

      [DataContract]

     public class UserInfo

     {

          //….

}

只有声明为DataContract的类型的对象可以被传送,且只有成员属性会被传递,成员方法不会被传递。WCF对声明为DataContract的类型提供更加细节的控制,可以把一个成员排除在序列化范围以外,也就是说,客户端程序不会获得被排除在外的成员的任何信息,包括定义和数据。默认情况下,所有的成员属性都被排除在外,因此需要把每一个要传送的成员声明为DataMember,如下所示。

    [DataContract]

    public class UserInfo

    {

        [DataMember]

        public string UserName

        {

            get;

            set;

        }

        [DataMember]

        public int Age

        {

            get;

            set;

        }

        [DataMember]

        public string Location

        {

            get;

            set;

        }

        public string Zodiac

        {

            get;

            set;

        }

}

上面这段代码把UserInfo类声明为DataContract,将UserName、Age、Location这3个属性声明为DataMember(数据成员)。Zodiac成员没有被声明为DataMember,因此在交换数据时,不会传输Zodiac的任何信息。

DataContract也支持Name/Namespace属性,如同ServiceContract,Name和Namespace可以自定义名称和命名空间,客户端将使用自定义的名称和命名空间对DataContract类型进行访问。

声明为DataMember的成员也可以自定义客户端可见的名称,例如:

[DataMember(Name="Name")]

public string UserName

{

     get;

     set;

}

[DataMember(Name="Age")]

public int UserAge

{

          get;

          set;

}

除了Name和Namespace以外,DataMember还有以下参数,它们的含义分别如下。

(1)IsRequired:值为true时,要求序列化引擎检查对象是否存在该值;若无,则会有异常抛出。

(2)Order:bool类型值,值为true时,序列化和反序列化过程将会按成员定义的顺序进行,这对依赖于成员位置的反序列化过程无比重要。

(3)EmitDefaultvalue:为成员属性设置一个默认值。

一般情况下,将类型声明为DataContract就可以满足传送的需求了,不过特殊情况是难以避免的,这时就需要对要传送的SOAP消息进行更加精确的控制,MessageContract可以满足这种需求。

把一个类型声明为MessageContract,意味着它可以被序列化为SOAP消息,可以声明类型的成员为SOAP消息的各个部分,如Header、Body等,如下所示。

    [MessageContract]

    public class UserMessage

    {

        private string user = String.Empty;

        private string authKey = String.Empty;

        [MessageBodyMember(

          Name = "UserName",

          Namespace = "http://www.wcf.com")]

        public string User

        {

            get { return user; }

            set { user = value; }

        }

        [MessageHeader(

          Name = "AuthKey",

          Namespace = "http://www.wcf.com",

          MustUnderstand = true

        )]

        public string AuthKey

        {

            get { return authKey; }

            set { this.authKey = value; }

        }

}

User成员被声明为MessageBody(消息体)的一个成员,AuthKey被声明为消息头(MessageHeader)的一个成员。这个类将可以生成如下的SOAP消息。

<s:Envelope>

    <s:Header>

        <a:Action s:mustUnderstand="1">http://UserMessage/Action</a:Action>

        <h:AuthKey s:mustUnderstand="1" xmlns:h="http://www.wcf.com">xxxx</h:AuthKey>

    </s:Header>

    <s:Body>

        <UserMessage xmlns="Microsoft.WCF.Documentation">

             <User xmlns="http://www.wcf.com">abcd</User>

       </UserMessage>

    </s:Body>   

</s:Envelope>


MessageHeader中,MustUnderstand参数表示读取该头的程序必须能够识别头的内容,否则不能继续处理。Name/Namespace的作用与前面的元素相同。另有Relay参数,若为true,头的内容被接收到以后会在响应消息中回发给消息发送端。


 

 


原创粉丝点击