【转载】浅谈C#中的延迟加载(1)——善用委托

Linux下的XAMPP基本配置技巧(设置虚拟主机、添加FTP账户等)

http://www.cnblogs.com/youxin/archive/2011/07/26/2116787.html

 http://coolshell.cn/articles/3684.html

很久以前就听过“延迟加载”这个东西,不过没有理解是什么意思,现在算是了解一二了,写点文章作为读书笔记,把自己的想法记录一下,希望对初学者帮助,不管是初学者或者高手如果发现文章那里写得不好或者有更好的思路和做法记得告诉我哦^^。文章打算写成两三篇,这个是第一篇。

    在三层结构中我们通常会使用多一个叫做“模型层”的东西,这一层中最主要做的事情是把数据库中的表 (或者其他数据源,例如xml或者自己定义的一种数据格式)转成对应的类,例如有一个文章表,这时候在这一层就会有一个文章类;文章类的属性对应着文章表的列,例如文章标题属性对应文章标题列。 实体类和数据表一一对应是最简单的情况,这时候实体类和实体类是各自独立存在的,没有出现相互引用的关系。 但是,几乎每一个数据库中的表都是存在关联关系的(关系型数据库),例如除了文章表之外,还会有一个文章分类表,假如说每一篇文章都必须属于一个分类,那么在数据库中表现出来的就是文章表中有一个外键字段指向文章分类表的主键 ,在C#代码中表示出来的是文章类中有一个属性(文章分类ID),通过这个属性我们就可以知道文章所属的分类、并且可以准确地通过代码查询数据库,获取一个文章分类实体类的对象,读取到文章所属分类的相关信息。

    以上的过程看起来一点问题都没有,整理一下思路,就是读取数据库,获取一个文章类对象,通过文字类对象中的文章分类ID的值,以这个值为查询条件去数据库中读取数据,获取一个文章分类对象,当然对数据库的操作我们通常 是封装在“数据访问层”中。然而从面向对象的角度考虑,我们会希望从文章类包含有文章分类的信息,用代码表示“文章分类”和“文章”两个实体类如下:

c#代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace Model
{
    // 文章分类实体类
    public class ArticleCategory
    {
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }
    }
    // 文章实体类
    public class Article
    {
        public int ArticleID { get; set; }
        public string Title { get; set; }
        public string Cotnent{ get; set; }
        public DateTime CreateTime { get; set; }
        public int CategoryID { get; set; }
        // 文章所属分类
        public Model.ArticleCategory Category{ get; set; }
    }
}

 


从上面的代码可以看到,在文章实体类中出现了一个Model.ArticleCategory类型的属性Category,我们想要的就是通过这个属性直接读取文章所属分类的详细信息。问题出现了,在数据库访问层中我们从数据库中读取数据去实例化一个文章实体类对象之后, 要选择在什么时候去给Category赋值

  • 选择一:立刻给通过分类ID(CategoryID属性)去获取所属文章分类的对象,然后塞给“文章所属分类”属性(Category) ,然后再返回文章对象。这种方法在有一点不好,就是万一得到文章对象之后根本不用去使用到Category属性……显然这种做法不佳。
  • 选择二:在需要的时候再去读取文章分类,然后给文章类对象的Category属性赋值,但……这和没有这个属性其实也没有什么区别。
  • 选择三:在Category属性的get访问器中实现读取数据库获取文章分类的代码,这样如果没有使用到Category属性的 时候是不会调用到这些代码的,也就不会去访问数据库拿东西了,为了避免每次访问Category属性都去读取数据库, 我们给他增加一个所有字段,得到的代码如下:
c#代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected Model.ArticleCategory _category;
public Model.ArticleCategory Category
{
    get 
    {
        if(_category == null)
        {
            // 创建文章分类数据访问层对象
            Dal.ArticleCategory articleCategoryDal = new Dal.ArticleCategory();
            // 获取文章分类
            _category = articleCategoryDal.GetArticleCategoryByCategoryID(CategoryID);
        }
        return _category;
    }
    // set访问器就不需要了
}

    乍看起来似乎没问题,但要考虑一点,在三层结构中数据的传输靠的就是“模型层”,“模型层”处于三层之下, 换句话说,“模型层”不会去引用三层中的任何一层,而上面代码中的GetArticleCategoryByCategoryID很显然是在三层之中,也许是在“业务逻辑层”或者“数据访问层”,所以...循环引用了,这种做法也不佳。如何实现对在文章类中对Category属性的数据进行延时加载呢?整理思路,根据需求一步步分析:
乍看起来似乎没问题,但要考虑一点,在三层结构中数据的传输靠的就是“模型层”,“模型层”处于三层之下,
        换句话说,“模型层”不会去引用三层中的任何一层,而上面代码中的GetArticleCategoryByCategoryID很显然
        是在三层之中,也行是在“业务逻辑层”或者“数据访问层”,所以...循环引用了,这种做法也不佳。
  • 首先:获取到一个文章类对象的时候,只有在读取了Category属性才去访问数据库,不读取是不访问的
  • 其次:读取同一个文章类对象的Category属性的时候只访问一次数据库
  • 最后:在Category属性的get访问器中我们不能调用三层中的方法(严格说是不直接显示调用)
    换个角度思考,我们能不能在数据访问层中读取数据、初始化一个文章类对象之后给它一个方法,告诉它如果你要 获取自己所属分类信息(文章分类对象)的时候就调用这个方法来拿,不用的时候就不去调用了,免得多链读取一次数据库。 “给它一个方法”,也就是说把方法传给它咯! 于是想到委托,我们可以在文章类中添加一个委托,这个委托的签名和“通过文章分类ID获取文章分类对象” 方法的签名一致,在Category属性的get访问器中调用这个委托,这样便解决了可以在get访问器中调用到方法去访问数据库, 也自然实现了延时加载!于是修改实体类代码如下:
c#代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
namespace Model
{
    // 文章分类实体类
    public class ArticleCategory
    {
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }
    }
    // 文章实体类
    public class Article
    {
        public int ArticleID { get; set; }
        public string Title { get; set; }
        public string Cotnent{ get; set; }
        public DateTime CreateTime { get; set; }
        public int CategoryID { get; set; }
          
        // 文章所属分类
        protected Model.ArticleCategory _category;
        public Model.ArticleCategory Category
        {
            get
            {
                if (_category == null)
                {
                    if (CategoryLazyLoader != null)
                    {
                        _category = CategoryLazyLoader(CategoryID);
                    }
                    else
                    {
                        _category = null;
                    }
                }
                return _category ;
            }
        }
        // 文章分类延时加载器(委托)
        public Func<int, Model.ArticleCategory> CategoryLazyLoader { get; set; }
    }
}


    在文章读取数据库得到数据然后创建一个文章类对象之后,我们对CategoryLazyLoader进行赋值就OK了! 文章数据访问类中获取文章的方法大致如下:
c#代码
1
2
3
4
5
6
7
8
9
10
11
12
// 根据文章ID获取文章实体类对象
public Model.Article GetArticleById(int articleId)
{
    // 从数据库中取出数据,得到一个DateRow或者DateRader之类的东东然后初始化一个文章实体类对象
    Model.Article article = ... // “...”是代码 - -!
    // 创建文章分类数据访问对象
    Dal.ArticleCategory articleCategory = new Dal.ArticleCategory();
    // 指定延时加载委托
    article.CategoryLazyLoader = articleCategory.GetArticleCategoryById;
    // 返回文章对象
    return article;
}

    通过上面方法得到的文章实体类对象中的Category属性就是实现了延时加载的了!
    文章写得不短,不过说的东西很简单,细想起来几乎没什么内容,一句话就是使用委托预先得到一个用于获取文章分类的方法,在文章分类属性的get选择器中调用委托返回结果。好了,告诉负责编写UI层代码的同事,调用了业务逻辑层的方法去获取文章实体类对象吧,! 已经帮你把文章分类给封装加进去了,而且使用了延迟加载,怎么实现你就不用管,用就行了!于是这个人用的时候囧了,文章实体类对象里面有个委托...... 委托啊!!!干嘛用的!!!???啥意思!@##¥%#¥……%¥&……%&#¥@!#

 

 

-----------------------------

 

开源的C#组件——RSS.NET


 

    之前为了给博客加上RSS功能便了解了一下RSS的相关。小研究了一些RSS是什么、怎么用、有什么规范之后在网上找到很多别人封装的RSS操作类,但是都觉得似乎有点简单了,而且很多代码写得很死,于是打算研究清楚后自己写个RSS组件来完成工作,后来在同事介绍了一个外国的开源RSS组件开源项目,写得实在是好,于是下载了源码来学习,并且使用到自己的博客里面,下载的版本不知道是不是最新的,使用的时候做了点微型的修改。网上有不少文件介绍RSS的,这篇文章我试图用自己的话介绍一下我自己的理解,顺便和大家分享RSS.NET这个优秀的组件 ^_^


    首先了解一下RSS是什么,有什么用,然后了解如何用,有什么可以方法可以节省我们的开发和学习成本:

    RSS是什么,按照我个人的理解RSS是一个xml格式的字符串,这个字符串通过一个url地址来获取(这个地址可能是一个格式的文本文件、或者是一个动态网页或者能动态生成需要的文本的东东- -!),必须注意的是,xml的格式是被固定下来的,有自己的一套节点和节点属性定义(根据不同的版本会有不同的规范),哪些节点是必须有的,那些可有可无都是被规定下来的。

    RSS有什么用,简单来说就是通过RSS阅读器(IE、FF等浏览器本身就是,强力些的可以用outlook)收藏你喜欢的RSS(成为RssFeed),阅读RSS,RSS更新的时候会主动通知你。我发现我试图说明白但是似乎说不明白,下面是在W3CSchool抄下来的:
    • 通过使用 RSS,您可以有选择地浏览您感兴趣的以及与您的工作相关的新闻。
    • 通过使用 RSS,您可以把需要的信息从不需要的信息(兜售信息,垃圾邮件等)中分离出来。
    • 通过使用 RSS,您可以创建自己的新闻频道,并将之发布到因特网。
    RSS怎么使用,对于发布RSS的人(准确来说应该是代码人员)需要通过编写代码等途径把网站中希望推送给那些订阅你这个RSS的用户(可能是你网络上的好友、商业上的伙伴甚至是敌人)。所谓推送就是写好一个符合RSS格式的XML文件(当然可以用一个动态页面或者一个asp.net的一般处理程序,都行!),给出一个URL地址来访问这个文件(例如我的网站RSS地址是http://www.youguanbumen.net/Rss/feed.xml),需要订阅你这个RSS的用户可以通过这个地址获取到这个RSS了,至于他们怎么用,这个就不管了。对于使用RSS的人来说,可以找一个自己喜欢的RSS阅读器订阅自己的喜欢的RSS,如果是自己编码实现RSS阅读的话,大致做法就是通过代码去把自己喜欢的RSS从指定地址读取下来,然后按照RSS格式去解析它(读取到的是一个xml字符串),要怎么显示就看个人喜欢了^_^

    节省开发和学习成本,学习技术上的东西,最简介的方法是看文档,当然最好是中文的并且翻译得专业点的;减少开发成本的话一般是找现成的东西,不过还是那句话,用别人的东西最好不要拿来就立刻用,特别是开源的东西,最好看一下别人怎么写,整体看一下也好,这对灵活地使用已经日后可能的扩展都是很有帮助的,并且有可能在看的过程中能吸收到一些好的编码技巧。这里我找了RSS.NET这个组件。


    RSS.NET组件:

    这个组件主要做的事情其实不多,就是读取和生成RSS,不过是以面向对象的思想把RSS中的各个元素(xml节点)做成对象,在使用的时候我们只需要知道RSS的地址和版本,然后调用这个组件的相关方法读取这个RSS,得到的不是一个需要我们去解析的xml字符串,而是这个组件为我们提供的对象(一个RssFeed类对象),以对象-属性的形式我们可以读取一个RSSChannel,在Channel中读取到RssItem集合,接着很方便地在代码里面读取RSS的各个元素,按照自己的需要对内容进行数据呈现,这一过程和我们在用.NET给我们提供的类(例如和数据库访问有关的类)一样方便。生成RSS是一个逆向的过程,我们要做的是创建需要的对象(这些对象由该组件提供),一般做法是实例化一个RssFeed类对象,给他添加RSSChannel,给RSSChannel添加RssItem,最后调用RssFeed的方法生成一个xml文件或者获取xml字符串然后自己处理(这是个符合RSS格式的xml字符串)。
    下面节选一段我博客里面的代码,完成生成RSS的工作(生成一个xml文件),只是一个代码片段(关于这个组件的时候文章最后给出官方网站的地址和下载地址)

c#代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/// <summary>
/// 创建Rss文件
/// </summary>
/// <param name="fileName">文件全路径(物理路径)</param>
/// <param name="isAutoUpdate">是否是系统自动更新</param>
public En_OperResult UpdateRss(string fileName,bool isAutoUpdate)
{
    En_OperResult re = En_OperResult.UpdateRss_Fail;
    try
    {
        // 获取所有文章
        IList<Model.MO_Article> allArticles = GetAllArticle();
        if (allArticles.Count <= 0)
        {
            // 没有文章,直接返回结果(没有内容)
            return En_OperResult.UpdateRss_NoItem;
        }
        // 获取所有用户
        IList<Model.MO_MyUser> allUsers = _userBll.GetAllUser();
        // 获取所有文章分类
        IList<Model.MO_Category> allArticleCates = _categoryBll.GetAllCategories(Model.MO_Category.En_CategoryType.Artical);
        // 创建RssFeed
        Rss.RssFeed feed = new Rss.RssFeed(System.Text.Encoding.UTF8);
        // 创建RssChannel
        Rss.RssChannel channel = CreateRssChannel();
        for (int i = allArticles.Count - 1; i >= 0; i--)
        {
            Model.MO_Article article = allArticles[i];
            channel.Items.Add(CreateRssItem(article, allUsers, allArticleCates));
        }
        // 把RssChannel添加到Feed,生成Rss文件
        feed.Channels.Add(channel);
        feed.Write(fileName);
        re = En_OperResult.UpdateRss_Success;
        if (!isAutoUpdate)
        {
            // 写日志
            WriteOperLog("更新Rss");
        }
    }
    catch (Exception ex)
    {
        WriteErrorLog(ex.Message);
        re = En_OperResult.UpdateRss_Fail;
    }
    return re;
}
/// <summary>
/// 创建Rss条目
/// </summary>
/// <param name="article">文章</param>
/// <param name="allUser">所有用户</param>
/// <param name="articleCategories">所有文章分类</param>
/// <returns></returns>
private Rss.RssItem CreateRssItem(Model.MO_Article article, IList<Model.MO_MyUser> allUser, IList<Model.MO_Category> articleCategories)
{
    string link = string.Format("{0}/{1}?id={2}"
            , Common.CommonTools.GetRootUrl()
            , "Article.aspx"
            , article.ID);
    Uri linkUrl = new Uri(link);
    string comments = string.Format("{0}&temp=#lwfield", link);
    Model.MO_MyUser user = allUser.First(u => u.ID == article.UserID);
    Model.MO_Category articleCate = articleCategories.First(c => c.ID == article.ArticleCategoryID);
    Rss.RssItem item = new Rss.RssItem()
    {
        Title = article.ArticleTitle,
        Link = linkUrl,
        Description = article.ArticleSummary,
        Comments = comments,
        PubDate = article.CreateTime,
        Author = user != null ? user.UserAccount : "Tiu",
        Guid = new Rss.RssGuid() { Name = link }
    };
    item.Categories.Add(new Rss.RssCategory()
    {
        Name = articleCate != null ? articleCate.CategoryName : string.Empty
    });
    return item;
}
/// <summary>
/// 创建Rss频道(所有文章)
/// </summary>
/// <returns></returns>
private Rss.RssChannel CreateRssChannel()
{
    string title = Common.AppSetting.SiteName;
    string desc = title;
    Uri link = new Uri(Common.CommonTools.GetRootUrl());
    string email = Common.AppSetting.MasterEmail;
    DateTime now = DateTime.Now;
    string copyRight = "Serafin.Tiu | All rights reserved";
    Rss.RssChannel channel = new Rss.RssChannel()
    {
        Title = title,
        Link = link,
        Description = desc,
        PubDate = now,
        LastBuildDate = now,
        Language = "zh-cn",
        WebMaster = email,
        ManagingEditor = email,
        Copyright = copyRight
    };
    return channel;
}

    以上!

    PS:关于RSS的介绍在W3CSchool上面已经介绍的比较详细,以下是本文内容有关的参考资料地址。
        W3CSchool-XML-RSS : http://www.w3school.com.cn/rss/index.asp
        RSS.NET组件官方地址:http://www.rssdotnet.com/
        RSS.NET组件下载地址:http://www.rssdotnet.com/RSS.NET.tar.gz


 

posted @ 2011-12-08 11:39  火腿骑士  阅读(201)  评论(0编辑  收藏  举报