zoukankan      html  css  js  c++  java
  • ORM映射框架总结实体分析器

    1.       什么是数据分析器

    前面一篇文章讲到过数据分析器,什么是数据分析器。其实很容易理解,就是对数据进行分析采集的一个工具,说白了就是一个小程序,在本ORM框架中对实体对象进行必要的数据分析,获得实体对象的各种信息缓存,以便在后续的工作中直接提取数据。这个是相对去年写的那个ORM有所改进的,在缓存实体信息的时候,在一定程度上可以提高该框架的性能

    2.       实体分析器的原理简单介绍

    简单的UML:

    图总是能给人最直观的感觉。从上面的图我们可以看出,这个分析器分为了几个过程:

    (1)       读取实体信息

    以上实体都是通过上篇介绍过的特性来描述的,他们定义了数据库和实体之间关系的代理桥梁。读取该些实体的信息,包括实体的描述的四个特性,实体的定义的变量,实体的属性等信息。

    (2)       缓存实体信息

    此个步骤是紧接上面一步的,可以看出将实体信息分析之后将信息缓存。多个实体信息,我们采用键值方式来存储。而这个缓存也是一个全局的缓存,因为这些信息在全局程序中共享,而非某个模块独享

    (3)       使用信息

    为什么读取缓存这些信息,必然是它有可用之处。数据读取肯定是用来使用的。这当然是废话。某个模块需要实体信息数据,就不必要去每次在分析这些数据,直接从缓存中读取

    3.       实体的定义

    在这个过程中我们定义数据库描述的代理实体是有特定规律的,而不是我们随随便便定义的一个实体对象。因为这些实体有着特殊的含义,所以相当于普通的实体来说这些还是有着重大意义的。

     

    从上图可以看出,在这个ORM中定义了一个公共接口IEntity,该接口有一个实现类BaseEntity。没有定义任何方法,IEntity但是继承了另外一个接口IDisposable。看到这大家应该都知道为什么这样定义了。而BaseEntity实现了接口IEntity并实现了Dispose()方法。

     IEntity源码

    代码
     1 /**
     2  * 2010-1-28
     3  * 
     4  * 情 缘
     5  * 
     6  * 所有实体模型的父类接口。
     7  * 
     8  * 该接口通过继承接口 IDisposable 可以实现自动实现
     9  * 垃圾回收的效果。实体的调用Dispose() 方法来手动
    10  * 释放占用的内存,同时也可以使用using 关键字来控
    11  * 制实体的作用域范围,并及时释放内存
    12  * 
    13  * */
    14 
    15 using System;
    16 
    17 namespace CommonData.Entity
    18 {
    19 
    20     /// <summary>
    21     /// 公共实体基类的公共接口,用于释放对象分配空间
    22     /// </summary>
    23     public interface IEntity:IDisposable
    24     {
    25     }
    26 }
    27 

    BaseEntity 源码

     代码

    /**
     * 2010-1-28
     * 
     * 情 缘
     * 
     * 这个实体类是所有实体模型类的父类,该实体类实现了
     * IEntity 接口,实现了手动释放内存方法。该实体类使
     * 用特性 Serializable修饰,说明该实体类要被序列化。
     * 而实体类使用abstract 关键字修饰,说明该类的实例
     * 不能通过自身来实例化,必须通过该实体类的子类来实
     * 现。使用该种模式,保证了设计在总体上的结构性
     * 
     * */

    using System;

    namespace CommonData.Entity
    {
        [Serializable]
        
    public abstract class BaseEntity:IEntity
        {
            
    /// <summary>
            
    /// 隐式实现接口方法,用于释放内存
            
    /// </summary>
            public void Dispose()
            {
                GC.SuppressFinalize(this);
            }
        }
    }

    普通实体源码

    代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using CommonData.Entity;
    using CommonData.Model.Core;

    namespace Office.Entity
    {
        [Serializable]
        [TableAttribute(DBName = "OA_DB", Name = "TabUser", PrimaryKeyName = "Id", IsInternal = false)]
        
    public class TabUser : BaseEntity
        {
            
    public TabUser()
            {
            }

            
    private int id;
            [ColumnAttribute(Name = "Id", IsPrimaryKey = true, AutoIncrement = true, DataType = DataType.Int, CanNull = false)]
            
    public int Id
            {
                
    get { return id; }
                
    set { id = value; }
            }

            
    private string userName;
            [ColumnAttribute(Name = "UserName", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Nvarchar, CanNull = false)]
            
    public string UserName
            {
                
    get { return userName; }
                
    set { userName = value; }
            }

            
    private string passWord;
            [ColumnAttribute(Name = "PassWord", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Nvarchar, CanNull = false)]
            
    public string PassWord
            {
                
    get { return passWord; }
                
    set { passWord = value; }
            }

            
    private int sex;
            [ColumnAttribute(Name = "Sex", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Int, CanNull = false)]
            
    public int Sex
            {
                
    get { return sex; }
                
    set { sex = value; }
            }

            
    private DateTime birthday;
            [ColumnAttribute(Name = "Birthday", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Datetime, CanNull = false)]
            
    public DateTime Birthday
            {
                
    get { return birthday; }
                
    set { birthday = value; }
            }

            
    private string cardID;
            [ColumnAttribute(Name = "CardID", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Varchar, CanNull = true)]
            
    public string CardID
            {
                
    get { return cardID; }
                
    set { cardID = value; }
            }

            
    private int age;
            [ColumnAttribute(Name = "Age", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Int, CanNull = true)]
            
    public int Age
            {
                
    get { return age; }
                
    set { age = value; }
            }

            
    private string address;
            [ColumnAttribute(Name = "Address", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Nvarchar, CanNull = true)]
            
    public string Address
            {
                
    get { return address; }
                
    set { address = value; }
            }

            
    private int isMarried;
            [ColumnAttribute(Name = "IsMarried", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Int, CanNull = true)]
            
    public int IsMarried
            {
                
    get { return isMarried; }
                
    set { isMarried = value; }
            }

            
    private int roleId;
            [ColumnAttribute(Name = "RoleId", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Int, CanNull = false)]
            
    public int RoleId
            {
                
    get { return roleId; }
                
    set { roleId = value; }
            }

            
    private int iSFobid;
            [ColumnAttribute(Name = "ISFobid", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Int, CanNull = false)]
            
    public int ISFobid
            {
                
    get { return iSFobid; }
                
    set { iSFobid = value; }
            }

            
    private string descript;
            [ColumnAttribute(Name = "Descript", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Nvarchar, CanNull = true)]
            
    public string Descript
            {
                
    get { return descript; }
                
    set { descript = value; }
            }

            
    private string remark;
            [ColumnAttribute(Name = "Remark", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Nvarchar, CanNull = true)]
            
    public string Remark
            {
                
    get { return remark; }
                
    set { remark = value; }
            }

            
    private string ext1;
            [ColumnAttribute(Name = "Ext1", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Nvarchar, CanNull = true)]
            
    public string Ext1
            {
                
    get { return ext1; }
                
    set { ext1 = value; }
            }

            
    private string ext2;
            [ColumnAttribute(Name = "Ext2", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Nvarchar, CanNull = true)]
            
    public string Ext2
            {
                
    get { return ext2; }
                
    set { ext2 = value; }
            }

            
    private string ext3;
            [ColumnAttribute(Name = "Ext3", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Nvarchar, CanNull = true)]
            
    public string Ext3
            {
                
    get { return ext3; }
                
    set { ext3 = value; }
            }

            
    private string ext4;
            [ColumnAttribute(Name = "Ext4", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Nvarchar, CanNull = true)]
            
    public string Ext4
            {
                
    get { return ext4; }
                
    set { ext4 = value; }
            }

            
    private string ext5;
            [ColumnAttribute(Name = "Ext5", IsPrimaryKey = false, AutoIncrement = false, DataType = DataType.Nvarchar, CanNull = true)]
            
    public string Ext5
            {
                
    get { return ext5; }
                
    set { ext5 = value; }
            }

        }
    }

    这里的实体比之前我们一般写的实体对象复杂多了,有了很多其他的属性描述。我们实现起来编写这些文件很麻烦,这里我们自然想到了代码生成器,对就是代码生成器。Codesimth 是一个可以自定义模板的代码生成器,我们可以很容易的实现这些代码的生成。这里我自己也写了一个针对于这个实体生成的代码生成器,前面有相关文章介绍过,有兴趣可以阅读一下。

    4.       实体信息的读取

    上面说到过实体信息的读取其实方法很简单,就是使用反射机制去读取实现属性方法,特性等信息。反射在于很多编程人员来说都是一件非常具有挑战性而且神秘的工作,有时候听到这两个字就望而生畏,其实不然,只要掌握了规律,理解了原理就可以了。无论是C#还是Java 原理都是一样的。但是反射是作为一个程序高手通往高境界的必经之路。所以掌握反射来说是对编程发展是很有好处的。

    下面是实体信息读取源码抽象类

    代码
    /**
     * 2010-2-1
     * 
     * 情 缘
     * 
     * 该类是一个抽象类,定义了一些获得表实体信息的方法。
     * 该类定义为抽象类,其所有的方法也是抽象方法。也就
     * 是说该类的所有方法都需要由其子类去实现。
     * 
     * */
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using CommonData.Model.Core;
    using System.Reflection;

    namespace CommonData.Entity
    {
        
    public abstract class BaseEntityAbstract
        {
            
    /// <summary>
            
    /// 根据实体的类型获得实体表信息
            
    /// </summary>
            
    /// <param name="type">实体类类型</param>
            
    /// <returns></returns>
            public abstract TableInfo GetTableInfo(Type type);

            
    /// <summary>
            
    /// 根据实体类的公共接口获得实体表信息
            
    /// </summary>
            
    /// <param name="entity">实体公共接口</param>
            
    /// <returns></returns>
            public abstract TableInfo GetTableInfo(IEntity entity);

            
    /// <summary>
            
    /// 根据实体泛型类型获得实体表信息
            
    /// </summary>
            
    /// <typeparam name="T">泛型类型</typeparam>
            
    /// <returns></returns>
            public abstract TableInfo GetTableInfo<T>() where T:IEntity;

            
    /// <summary>
            
    /// 根据实体的类型获得实体表列信息
            
    /// </summary>
            
    /// <param name="type">实体类类型</param>
            
    /// <returns></returns>
            public abstract ColumnAttribute[] GetColumnAttribute(Type type);

            
    /// <summary>
            
    /// 根据实体类的公共接口获得实体表列信息
            
    /// </summary>
            
    /// <param name="entity">实体公共接口</param>
            
    /// <returns></returns>
            public abstract ColumnAttribute[] GetColumnAttribute(IEntity entity);

            
    /// <summary>
            
    /// 根据实体泛型类型获得实体表列信息
            
    /// </summary>
            
    /// <typeparam name="T">泛型类型</typeparam>
            
    /// <returns></returns>
            public abstract ColumnAttribute[] GetColumnAttribute<T>() where T : IEntity;

            
    /// <summary>
            
    /// 根据实体的类型获得实体字段信息
            
    /// </summary>
            
    /// <param name="type">实体类类型</param>
            
    /// <returns></returns>
            public abstract FieldInfo[] GetFieldInfo(Type type);

            
    /// <summary>
            
    /// 根据实体类的公共接口获得实体字段信息
            
    /// </summary>
            
    /// <param name="entity">实体公共接口</param>
            
    /// <returns></returns>
            public abstract FieldInfo[] GetFieldInfo(IEntity entity);

            
    /// <summary>
            
    /// 根据实体泛型类型获得实体字段信息
            
    /// </summary>
            
    /// <typeparam name="T">泛型类型</typeparam>
            
    /// <returns></returns>
            public abstract FieldInfo[] GetFieldInfo<T>() where T : IEntity;

            
    /// <summary>
            
    /// 根据实体的类型获得实体属性信息
            
    /// </summary>
            
    /// <param name="type">实体类类型</param>
            
    /// <returns></returns>
            public abstract PropertyInfo[] GetPropertyInfo(Type type);

            
    /// <summary>
            
    /// 根据实体类的公共接口获得实体属性信息
            
    /// </summary>
            
    /// <param name="entity">实体公共接口</param>
            
    /// <returns></returns>
            public abstract PropertyInfo[] GetPropertyInfo(IEntity entity);

            
    /// <summary>
            
    /// 根据实体泛型类型获得实体属性信息
            
    /// </summary>
            
    /// <typeparam name="T">泛型类型</typeparam>
            
    /// <returns></returns>
            public abstract PropertyInfo[] GetPropertyInfo<T>() where T:IEntity;

            
    /// <summary>
            
    /// 根据实体的类型获得实体字段属性信息
            
    /// </summary>
            
    /// <param name="type">实体类类型</param>
            
    /// <returns></returns>
            public abstract LinkTableAttribute[] GetLinkTableAttribute(Type type);

            
    /// <summary>
            
    /// 根据实体类的公共接口获得实体字段属性信息
            
    /// </summary>
            
    /// <param name="entity">实体公共接口</param>
            
    /// <returns></returns>
            public abstract LinkTableAttribute[] GetLinkTableAttribute(IEntity entity);

            
    /// <summary>
            
    /// 根据实体泛型类型获得实体字段属性信息
            
    /// </summary>
            
    /// <typeparam name="T">泛型类型</typeparam>
            
    /// <returns></returns>
            public abstract LinkTableAttribute[] GetLinkTableAttribute<T>() where T:IEntity;

            
    /// <summary>
            
    /// 根据实体的类型获得实体字段集合属性信息
            
    /// </summary>
            
    /// <param name="type">实体类类型</param>
            
    /// <returns></returns>
            public abstract LinkTablesAttribute[] GetLinkTablesAttribute(Type type);

            
    /// <summary>
            
    /// 根据实体类的公共接口获得实体字段集合属性信息
            
    /// </summary>
            
    /// <param name="entity">实体公共接口</param>
            
    /// <returns></returns>
            public abstract LinkTablesAttribute[] GetLinkTablesAttribute(IEntity entity);

            
    /// <summary>
            
    /// 根据实体的类型获得实体字段集合属性信息
            
    /// </summary>
            
    /// <typeparam name="T">泛型类型</typeparam>
            
    /// <returns></returns>
            public abstract LinkTablesAttribute[] GetLinkTablesAttribute<T>() where T:IEntity;
        }
    }

    该抽象类定义了各种方式获得实体性的方式。而且有很多都是使用的重载方式,这样更能体现程序结构的灵活性,健壮性。这个过程中不乏使用了泛型,以及泛型约束,使用泛型约束很大程度上使得这个程序的针对的工作性质比较强,将实体分析约束到了某个特定的范围之内,这样可以省去很多不必要的麻烦

    获得实体的信息代码

    代码

    /// <summary>
            
    /// 根据实体的类型获得实体表信息
            
    /// </summary>
            
    /// <param name="type">实体类类型</param>
            
    /// <returns></returns>
            public override TableInfo GetTableInfo(Type type)
            {
                TableInfo tableInfo = EntityTypeCache.GetTableInfo(type);
                
    if (tableInfo == null)
                {
                    tableInfo = new TableInfo();
                    TableAttribute[] tableAttribute = type.GetCustomAttributes(typeof(TableAttribute),falseas TableAttribute[];
                    
                    PropertyInfo[] properties = type.GetProperties();
                    
    foreach (PropertyInfo property in properties)
                    {
                        tableInfo.DicProperties.Add(property.Name,property);
                        
    if (property.GetCustomAttributes(typeof(ColumnAttribute), false).Length == LENGTH)
                        {
                            tableInfo.DicColumns.Add(property.Name, property.GetCustomAttributes(typeof(ColumnAttribute), false)[0as ColumnAttribute);
                        }
                        
    if (property.GetCustomAttributes(typeof(LinkTableAttribute), false).Length == LENGTH)
                        {
                            tableInfo.DicLinkTable.Add(property.Name,property.GetCustomAttributes(typeof(LinkTableAttribute),false)[0as LinkTableAttribute);
                        }
                        
    if (property.GetCustomAttributes(typeof(LinkTablesAttribute), false).Length == LENGTH)
                        {
                            tableInfo.DicLinkTables.Add(property.Name, property.GetCustomAttributes(typeof(LinkTablesAttribute), false)[0as LinkTablesAttribute);
                        }
                    }
                    FieldInfo[] fields = type.GetFields();
                    
    foreach (FieldInfo field in fields)
                    {
                        tableInfo.DicFields.Add(field.Name,field);
                    }
                    
                    
    if (tableAttribute.Length == LENGTH)
                    {
                        tableInfo.Table = tableAttribute[0];
                    }
                    
    else
                    {
                        
    throw new Exception("一个实体类上不能有相同的特性");
                    }
                    tableInfo.Columns = tableInfo.DicColumns.Values.ToArray();
                    tableInfo.Fields = tableInfo.DicFields.Values.ToArray();
                    tableInfo.LinkTable = tableInfo.DicLinkTable.Values.ToArray();
                    tableInfo.LinkTables = tableInfo.DicLinkTables.Values.ToArray();
                    tableInfo.Properties = tableInfo.DicProperties.Values.ToArray();
                    EntityTypeCache.InsertTableInfo(type,tableInfo);
                }
                
    return tableInfo;
            }

    5.       缓存机制

    其实这个这个缓存机制很简单的,我们经常想到的缓存机制就是Session ApplicationViewState 等对象。这些都是web程序中的缓存对象,但是如果这些运用在控制台程序或者WinForm程序中呢,这些对象显然作用不大。于是我们需要一个能够公用的缓存机制。

    有些人一直使用static 类,static 方法,他们喜欢对这种方式百用不怠,因为他们感觉static 就是爽。深入的想一下,为什么要这样使用,因为这些东西是在程序加载的时候就缓存起来了。于是我们可以在这个方面下功夫来实现我们的缓存

    定义的缓存类:

    代码
    /**
     * 2010-1-30
     * 
     * 情 缘
     * 
     * 该类构建了一个内存缓存器,这个缓存器缓存了数据库
     * 对应实体类的特性信息,字段和属性。这些信息在程序
     * 加载或第一次使用的时候缓存到这个存储其中来。以后
     * 再吃读取这些信息的时候不需要重新去加载这些信息,
     * 直接从内存中读取即可
     * 
     * */

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    namespace CommonData.Entity
    {
        
    public static class EntityTypeCache
        {
            
    //表实体信息存储器,用于存储表实体信息
            private static IDictionary<Type, TableInfo> cache = null;

            
    /// <summary>
            
    /// 静态构造方法
            
    /// 使用静态构造方法,确保该构造方法只执行一次,不会还原初始化值
            
    /// 因为数据不会丢失,而且是一直保存在内存中,这样就达到了一个
            
    /// 临时存储器的功能
            
    /// </summary>
            static EntityTypeCache()
            {
                cache = new Dictionary<Type, TableInfo>();
            }


            
    /// <summary>
            
    /// 将表实体信息缓存到临时存储器中
            
    /// </summary>
            
    /// <param name="type">实体的类型</param>
            
    /// <param name="tableInfo">表实体信息</param>
            public static void InsertTableInfo(Type type,TableInfo tableInfo)
            {
                
    if (cache.ContainsKey(type))
                {
                    
    return;
                }
                
    else 
                {
                    cache.Add(type,tableInfo);
                }
            }

            
    /// <summary>
            
    /// 将表实体信息缓存到临时存储器中
            
    /// </summary>
            
    /// <param name="entity">实体公共接口</param>
            
    /// <param name="tableInfo">表实体信息</param>
            public static void InsertTableInfo(IEntity entity, TableInfo tableInfo)
            {
                Type type = entity.GetType();
                InsertTableInfo(type,tableInfo);
            }

            
    /// <summary>
            
    /// 根据实体类的类型获得表特性信息
            
    /// </summary>
            
    /// <param name="type">实体类的类型</param>
            
    /// <returns></returns>
            public static TableInfo GetTableInfo(Type type)
            {
                
    if (cache.ContainsKey(type))
                {
                    
    return cache[type];
                }
                
    else
                {
                    
    return null;
                }
            }

            
    /// <summary>
            
    /// 根据实体公共接口获得表特性信息
            
    /// </summary>
            
    /// <param name="entity">实体公共接口</param>
            
    /// <returns></returns>
            public static TableInfo GetTableInfo(IEntity entity)
            {
                Type type = entity.GetType();
                
    return GetTableInfo(type);
            }

            
    /// <summary>
            
    /// 根据泛型类型获得表实体信息
            
    /// </summary>
            
    /// <typeparam name="T">泛型类</typeparam>
            
    /// <returns></returns>
            public static TableInfo GetTableInfo<T>() where T:IEntity
            {
                Type type = typeof(T);
                
    return GetTableInfo(type);
            }
        }
    }

    使用静态构造方法,确保该构造方法只执行一次,不会还原初始化值,因为数据不会丢失,而且是一直保存在内存中,这样就达到了一个临时存储器的功能。

    这里和上面读取实体信息是一起使用的,每次读取实体信息的时候就从缓存中读取,如果缓存中没有这些数据,就重新使用实体分析器去读取这些数据

    6.       测试分析

    (1).数据读取分析

     测试代码

    代码

    BaseEntityAbstract cache = new BaseEntityHelper();
    cache.GetTableInfo(typeof(TabUser));
    foreach (PropertyInfo fi in cache.GetTableInfo(typeof(TabUser)).Properties)
    {
                        Console.WriteLine(fi.Name);
    }

     测试结果:

     

    从上面的代码中可以看到从缓存中获取了实体的相应信息。 cache.GetTableInfo(typeof(TabUser)) 这句代码是有特定信息的,我们可以使用这个方法来在程序启动的时候注册所有实体的信息

         (2).缓存性能分析

    测试代码

    代码

    static void Main(string[] args)
            {


                BaseEntityAbstract cache = new BaseEntityHelper();
                cache.GetTableInfo(typeof(TabUser));
                Console.WriteLine();
                Console.WriteLine("BeginTime: " + DateTime.Now.Millisecond);
                
    for (int i = 0; i < 10000; i++)
                {
                    
    foreach (PropertyInfo fi in typeof(TabUser).GetProperties())
                    {
                        
    //Console.WriteLine(fi.Name);
                    }
                }
                Console.WriteLine("BeginTime: " + DateTime.Now.Millisecond);
                Console.WriteLine();
                Console.WriteLine();
                Console.WriteLine("BeginTime: " + DateTime.Now.Millisecond);
                
    for (int i = 0; i < 10000; i++)
                {
                    
    foreach (PropertyInfo fi in cache.GetTableInfo(typeof(TabUser)).Properties)
                    {
                        
    //Console.WriteLine(fi.Name);
                    }
                }
                Console.WriteLine("BeginTime: " + DateTime.Now.Millisecond);
            }

    以上我们分别用反射获取实体信息,和在缓存中获取实体信息,然后分别循环10000次。我们看看他们的所用的时间比较

     

     看看上面的图,我们就之后了性能的高低,这里说明一个道理这个缓存器起到了作用。这也达到了先前的目的。

     (注: ORM涉及内容比较多,后续期待,有兴趣的可以与本人探讨) 

     

  • 相关阅读:
    ASCII 说明
    用GDB调试程序
    手把手教你使用Matplotlib绘图|实战
    什么!Python还能帮你找老婆?
    词云图的几种制作方法评测,你pick哪款
    我常用的10个Python实用小Trick
    爬虫代码详解Python多线程、多进程、协程
    [转载] tomcat集群基于redis共享session解决方案
    集群/分布式环境下5种session处理策略
    java7特性之 try-with-resources
  • 原文地址:https://www.cnblogs.com/qingyuan/p/1704546.html
Copyright © 2011-2022 走看看