科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件用不到140行C-sharp代码开发面向对象的数据库(下篇)

用不到140行C-sharp代码开发面向对象的数据库(下篇)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

在开始深入代码的细节之前,我们需要看看我们的解决方案背后的体系结构。其基本的体系结构由两个类和一个接口构成。

作者:builder.com.cn 2007年3月2日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
删除对象

对象的删除由XmlDBState的两个函数来处理——DeleteRemoveCurrentObjectDelete的代码见列表E

列表E:

    public static void Delete(IXmlDBSerializable data, bool deep)
    {
        //If this is a "deep delete", we look through the object's
        // properties and delete all child objects from the database.
        if (deep)
        {
            PropertyInfo[] properties = data.GetType().GetProperties();

            foreach (PropertyInfo property in properties)
            {
                object propertyValue = property.GetValue(data, null);

                if (propertyValue is IXmlDBSerializable)
                    Delete((IXmlDBSerializable)propertyValue, true);
                else if (propertyValue is System.Collections.ICollection)
                {
                    IList propertyList = propertyValue as IList;

                    if (propertyList != null &&
                        propertyList.Count > 0 &&
                        propertyList[0] is IXmlDBSerializable)
                        foreach (object listObject in propertyList)
                            Delete((IXmlDBSerializable)listObject, true);
                }
            }
        }

        //Remove the object from the database.
        XmlDBState.RemoveCurrentObject(data.GetType().ToString(), data);

        //Persist the database to disk.
        XmlDBState.Database.Save(XmlDBState.Path);
    }

正如你看到的,Delete在内部使用RemoveCurrentObject,但是还提供了“深度删除(deep delete)”的选项。这意味着正在被删除的对象的每个子对象也要被从数据库里删除。RemoveCurrentObject的代码见列表F

列表F:

    public static XmlNode RemoveCurrentObject(string typeString, IXmlDBSerializable data)
    {
        //Find the node that holds this type's data.
        XmlNode typeNode = XmlDBState.MainNode.SelectSingleNode(typeString);

        //If the object has a node associated with it, remove
        // the node from the database.
        if (data.Node != null)
            typeNode.RemoveChild(data.Node);

        //Return the node that is responsible for this type's
        // data.
        return typeNode;
    }

RemoveCurrentObject的基本功能是发现数据库当前对象里的对象,并使用类型的XmlNodeRemoveChild方法从数据库里删除序列化对象。这是一个对内存进行的操作,这就是为什么Delete方法要用额外的步骤调用XmlDBState.Database.Save把更改的内容保持到磁盘上的原因。

谓词查询

通过向用户提供使用Predicate(谓词)方法搜索数据库的选项,我们能够实现一个类型安全的、集成C#的查询机制。这项功能通过Search(搜索)方法的重载在数据库里实现(列表G)。

列表G

    public static List<DynamicType> Search<DynamicType>(
                                    Predicate<DynamicType> searchFunction)
                                    where DynamicType : IXmlDBSerializable
    {
        //Get the Type of the object we're searching for.
        Type type = typeof(DynamicType);

        //Get the nodes of those objects in our database.
        XmlNodeList nodeList =
            XmlDBState.Database.SelectNodes(String.Format(@"/Database/{0}/{1}",
                                            type.FullName, type.Name));

        //Get a collection of DynamicType objects via the
        // ExtractObjectsFromNodes method.
        List<DynamicType> matches = ExtractObjectsFromNodes<DynamicType>(nodeList);

        //Use the List<T>.FindAll method to narrow our results
        // to only what was searched for.
        return matches.FindAll(searchFunction);
    }

这个函数会选择数据库里给定类型的所有节点,使用ExtractObjectsFromNodes将节点反序列化,然后使用.NET框架提供的List<T>.FindAll方法过滤集合。ExtractObjectsFromNodes的代码见列表H

列表H

    private static List<DynamicType> ExtractObjectsFromNodes<DynamicType>(
XmlNodeList nodeList)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(DynamicType));
        List<DynamicType> objects = new List<DynamicType>();

        foreach (XmlNode node in nodeList)
        {
            StringReader reader = new StringReader(node.OuterXml);
            DynamicType deserialized = (DynamicType)serializer.Deserialize(reader);
            ((IXmlDBSerializable)deserialized).Node = node;

            objects.Add(deserialized);
        }

        return objects;
    }

这个方法只会在我们XmlNodeList的每个节点里循环,将每个对象反序列化成为活的业务对象。它然后把对象添加到List<T>集合里,在循环结束的时候返回集合。

这种类型的查询要求我们将保存在数据库里的被请求类型的所有实例都反序列化,知道这一点是十分重要的。这意味着如果数据库里有10,000个Customer对象,我们要使用Predicate查询过滤它们,那么这10,000对象都必须被反序列化,然后数据库才能够开始过滤结果。这显然是一个十分耗时的过程,这就是为什么我们提供了一个基于XPath的替代查询机制的原因。

XPath查询

正如我们在本系列的上篇中提到的,使用XML保存对象的一个主要优势是我们可以将XPath用作是一种特别的查询机制。这一机制由XmlDBState类通过Search方法的重载来实现,见列表I

列表I

    public static List<DynamicType> Search<DynamicType>(
                                    string query)
                                    where DynamicType : IXmlDBSerializable
    {
        //Get the Type of the object we're searching for.
        Type type = typeof(DynamicType);
        //Create a List<DynamicType> collection to hold the results.
        List<DynamicType> matches = new List<DynamicType>();

        //Change single quotes to double quotes in the query.
        query = query.Replace("'", "\"");

        //Build our XPath query.
        string xpath = "Database/" + type.FullName + "/" +
                        type.Name + "[" + query + "]";

        try
        {
            //Select all nodes which match out XPath query.
            XmlNodeList nodes = XmlDBState.Database.SelectNodes(xpath);

            //If we have results, extract objects from those nodes.
            if (nodes != null)
                matches = ExtractObjectsFromNodes<DynamicType>(nodes);
        }
        catch (Exception exception)
        {
            throw new Exception("Could not search. Possible bad query syntax?",
exception);
        }

        return matches;
    }

要注意的是,在使用XPath查询的时候我们只将我们知道符合查询条件的对象反序列化。这会极大地提高查询执行的速度。在测试中,Predicate查询返回10,000个对象需要一秒钟,而XPath查询只需要百分之一秒。

还要注意的是,在这个方法的开始,我们用双引号替换掉了单引号。这样做的目的是用户不用在需要在它们的查询里转义双引号。但是这种做法存在一些副作用,因为单引号是合法的XPath字符,某些实例可能会要求使用它们。使用它只是为了实现可用性而做出的一种让步。

IXmlDBSerializable接口

IXmlDBSerializable接口必须通过要被保存到数据库里的任何对象来实现。这使得数据库能够对所有对象都一视同仁,避免了人们去猜测某个对象是否能够被保存到数据库里。IXmlDBSerializable的代码见列表J

列表J

namespace XmlDBLibrary
{
    public interface IXmlDBSerializable
    {
        System.Guid Identity { get; set; }
        System.Xml.XmlNode Node { get; set; }
        void Save(bool persistData);
    }
}

我们需要Identity(标识)属性是因为每个被保存到数据库的对象都必须有唯一能够被识别的标记。这一属性应该自动地被生成,用于实现IXmlDBSerializable的任何对象。

Node属性被用来把XmlNode保存到与当前对象对应的数据库里。这让数据库能够快速地删除对象,而不需要搜索对象的标识,还没有被保存到数据库里的对象的这一属性为空。

我们还需要Save(保存)方法,这样XmlDBState.SaveObject方法就能够保存正在被保存的对象的子对象。通过将把子对象传给IXmlDBSerializable并对子对象调用Save,我们就能够实现这一目标。

XmlDBBase类

当对象需要符合IXmlDBSerializable 要求的时候,XmlDBBase类被当为开发人员所使用的一种快捷方式。这并不是一个必需的类,因为IXmlDBSerializable可以手动实现。当被用作一个基类时,XmlDBBase还提供了保存、搜索和删除功能。XmlDBBase的代码见列表K

列表K

    public class XmlDBBase : IXmlDBSerializable
    {
        private XmlNode _node = null;
        private System.Guid _identity = System.Guid.NewGuid();

        public XmlDBBase()
        {
        }

        public void Delete()
        {
            this.Delete(false);
        }

        public void Delete(bool deep)
        {
            XmlDBState.Delete(this, deep);
        }

        public void Save()
        {
            Save(true);
        }

        public void Save(bool persistData)
        {
            XmlDBState.SaveObject(this, persistData);
        }

        public static List<DynamicType> Search<DynamicType>(
                            System.Predicate<DynamicType> searchFunction)
                            where DynamicType : IXmlDBSerializable
        {
            return XmlDBState.Search<DynamicType>(searchFunction);
        }

        public static List<DynamicType> Search<DynamicType>(
                            string query)
                            where DynamicType : IXmlDBSerializable
        {
            return XmlDBState.Search<DynamicType>(query);
        }

        public static DynamicType GetSingle<DynamicType>(
                            System.Predicate<DynamicType> searchFunction)
                            where DynamicType : IXmlDBSerializable
        {
            List<DynamicType> results =
                  XmlDBState.Search<DynamicType>(searchFunction);

            return (results.Count == 0) ? default(DynamicType) : results[0];
        }

        public System.Guid Identity
        {
            get { return _identity; }
            set { _identity = value; }
        }

        [System.Xml.Serialization.XmlIgnore]
        public XmlNode Node
        {
            get { return _node; }
            set { _node = value; }
        }
    }

要注意的是,这些方法都可以同时用来进行XPath和Predicate查询。它们也提供了Delete(删除)和Save功能。这些方法中的大多数基本上都是传给XmlDBState对象的传递方法。

有一个很有意思的事情需要指出,那就是对Node属性使用XmlIgnore,使用它是因为我们不希望Node属性被序列化到数据库里。如果将这个属性序列化,我们事实上就是在把序列化对象的一个副本保存在这个对象里。

最终的想法

虽然这个数据库不是一个企业级的面向对象的数据库,但是我相信这是体现.NET框架灵活性的一个好例子。让这个数据库工作起来只需要非常少的代码,所需要的代码基本上只是用来将各种不同的.NET框架的功能关联起来。这个数据库的每个主要函数都已经由.NET框架提供——从用于查询的Predicates/XPath,到用于数据管理的XmlDocument对象。

如果你对本文所表达的思路存在疑问或者想法,请留下你的评论,我很乐意回答。

责任编辑:德东

查看本文的国际来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章