必须填写至少10个字的日志
Stone authored at 2015-03-09 13:35:04
24.80 KiB
X
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using XCode;

namespace NewLife.CommonEntity
{
    /// <summary>地区</summary>
    [ModelCheckMode(ModelCheckModes.CheckTableWhenFirstUse)]
    public partial class Area<TEntity> : EntityTree<TEntity> where TEntity : Area<TEntity>, new()
    {
        #region 构造
        static Area()
        {
            // 注册新的实体树操作
            Setting = new AreaSetting();
        }

        class AreaSetting : EntityTreeSetting<TEntity>
        {
            /// <summary>已重载。</summary>
            public override string Key { get { return _.Code; } }

            /// <summary>关联父键名,一般是Parent加主键,如ParentID</summary>
            public override string Parent { get { return _.ParentCode; } }
        }
        #endregion

        #region 扩展属性
        [NonSerialized]
        private String _FriendName;
        /// <summary>友好名</summary>
        [XmlIgnore]
        public virtual String FriendName
        {
            get
            {
                if (String.IsNullOrEmpty(_FriendName))
                {
                    var list = AllParents.Clone();
                    list.Add(this as TEntity);
                    var sb = new StringBuilder(list.Count * 5);
                    foreach (var item in list)
                    {
                        if (item.Name == "市辖区") continue;
                        if (item.Name == "县") continue;

                        sb.Append(item.Name);
                    }
                    _FriendName = sb.ToString();
                }
                return _FriendName;
            }
            set { _FriendName = value; }
        }

        private Dictionary<Int32, String> _OldArea;
        /// <summary>旧地区</summary>
        public Dictionary<Int32, String> OldArea
        {
            get
            {
                if (_OldArea == null)
                {
                    _OldArea = new Dictionary<Int32, String>();

                    if (!String.IsNullOrEmpty(Description))
                    {
                        String[] ss = Description.Split(";", ";");
                        foreach (var item in ss)
                        {
                            String[] ss2 = item.Split("|");
                            if (ss2 != null && ss2.Length > 0)
                            {
                                Int32 code = 0;
                                if (Int32.TryParse(ss2[0], out code)) _OldArea.Add(code, ss2.Length > 1 ? ss2[1] : null);
                            }
                        }
                    }
                }
                return _OldArea;
            }
        }
        #endregion

        #region 扩展查询
        /// <summary>根据主键查询一个地区实体对象用于表单编辑</summary>
        /// <param name="id">编号</param>
        /// <returns></returns>
        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public static TEntity FindByKeyForEdit(Int32 id)
        {
            TEntity entity = Find(new String[] { _.ID }, new Object[] { id });
            if (entity == null)
            {
                entity = new TEntity();
            }
            return entity;
        }

        /// <summary>根据编号查找。实体缓存</summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static TEntity FindByID(Int32 id)
        {
            if (id <= 0) return null;
            return Meta.Cache.Entities.Find(__.ID, id);
        }

        /// <summary>按Code查找。实体缓存</summary>
        /// <param name="code"></param>
        /// <returns></returns>
        public static TEntity FindByCode(Int32 code)
        {
            if (code <= 0) return null;

            TEntity entity = Meta.Cache.Entities.Find(a => a.Code == code || a.OldCode == code || a.OldCode2 == code || a.OldCode3 == code);
            if (entity != null) return entity;

            return Meta.Cache.Entities.Find(a => a.OldArea.ContainsKey(code));
        }

        /// <summary>按名称查找。实体缓存</summary>
        /// <param name="name">名称</param>
        /// <returns></returns>
        //[Obsolete("请改为使用指定地区下的FindAllByName或Root.FindAllByName!")]
        public static TEntity FindByName(String name)
        {
            if (String.IsNullOrEmpty(name)) return null;
            return Meta.Cache.Entities.Find(__.Name, name);

            //return FindAllByName(name);
            //return Root.FindAllByName(name);
        }

        /// <summary>按名称查找。实体缓存</summary>
        /// <param name="name">地区名称</param>
        /// <param name="withLike">未找到时,是否查找相似的地区。因为地区可能有市、县、区等字样,而查询名称没填</param>
        /// <param name="deepth">地区路径的最大可能层次。内置地区数据库只有三层</param>
        /// <returns></returns>
        public EntityList<TEntity> FindAllByName(String name, Boolean withLike = true, Int32 deepth = 0)
        {
            if (String.IsNullOrEmpty(name)) return null;
            if (deepth <= 0)
            {
                if ((this as IEntity).IsNullKey)
                    deepth = 3;
                else if (this.Parent == null || (this.Parent as IEntity).IsNullKey)
                    deepth = 2;
                else
                    deepth = 1;
            }

            //EntityList<TEntity> list = Meta.Cache.Entities.FindAll(__.Name, name);
            //EntityList<TEntity> list = Childs.FindAll(__.Name, name);
            EntityList<TEntity> list = Childs.FindAll(a => CompAreaName(a.Name, name));
            if (list != null && list.Count > 0) return list;

            // 试试下一级
            if (deepth >= 2)
            {
                // 先用严格查找,再用模糊查找,避免在严格匹配项之前就匹配了模糊项
                foreach (var item in Childs)
                {
                    list = item.FindAllByName(name, false, deepth - 1);
                    if (list != null && list.Count > 0) return list;
                }
                if (withLike)
                {
                    foreach (var item in Childs)
                    {
                        list = item.FindAllByName(name, withLike, deepth - 1);
                        if (list != null && list.Count > 0) return list;
                    }
                }
            }

            if (!withLike) return list;

            // 未找到,开始模糊查找
            //String[] names = Meta.Cache.Entities.Select<TEntity, String>(e => e.Name).ToArray();
            String[] names = Childs.Select<TEntity, String>(e => e.Name).ToArray();
            String[] rs = StringHelper.LCSSearch(name, names);
            if (rs != null && rs.Length > 0)
            {
                list = new EntityList<TEntity>(Childs.Where<TEntity>(e => rs.Contains(e.Name, StringComparer.OrdinalIgnoreCase)));
                return list;
            }

            // 如果层次大于1,开始拆分。比如江苏省南京市鼓楼区,第一段至少一个字
            if (deepth > 1)
            {
                // 应该从右往左,这样子才能做到最大匹配,否则会因为模糊查找而混杂很多其它东西
                //for (int i = name.Length - 1; i >= 1; i--)
                for (int i = 0; i < name.Length; i++)
                {
                    String first = name.Substring(0, i);
                    String last = name.Substring(i);

                    // 必须找到左边的,否则这个匹配没有意义
                    //TEntity entity = Meta.Cache.Entities.Find(__.Name, first);
                    // 模糊查询一层
                    var list2 = FindAllByName(first, true, 1);
                    if (list2 != null && list2.Count > 0)
                    {
                        foreach (var item in list2)
                        {
                            list = item.FindAllByName(last, withLike, deepth - 1);
                            if (list != null && list.Count > 0) return list;
                        }
                    }
                }
            }

            return list;
        }

        static Dictionary<String, String> maps;
        /// <summary>比较地区名,考虑砍掉后缀的比较</summary>
        /// <param name="name1"></param>
        /// <param name="name2"></param>
        /// <returns></returns>
        static Boolean CompAreaName(String name1, String name2)
        {
            if (name1 == name2) return true;
            name1 = CutName(name1);
            name2 = CutName(name2);
            if (name1 == name2) return true;

            // 再来一次
            name1 = CutName(name1);
            name2 = CutName(name2);
            if (name1 == name2) return true;

            if (maps == null)
            {
                maps = new Dictionary<string, string>();
                //maps.Add("通州", "通");
                maps.Add("邱", "丘");
                //maps.Add("涿州", "涿");
                maps.Add("峨眉山", "峨眉");
                maps.Add("伊犁", "伊犁哈萨克");
            }

            String v = null;
            if (maps.TryGetValue(name1, out v) && v == name2) return true;
            if (maps.TryGetValue(name2, out v) && v == name1) return true;

            if (name1.Length < 1 || name2.Length < 1) return false;

            if (name1.Contains(name2) || name2.Contains(name1)) return true;

            return false;
        }

        static String[] suffixs = new String[] { "自治区", "自治州", "各族自治县", "自治县", "地区", "辖区", "区", "市", "盟", "州", "县", "旗" };
        static String CutName(String name)
        {
            if (name == "城区") return name;

            foreach (var item in suffixs)
            {
                //if (name.EndsWith(item)) return name = name.Substring(0, name.Length - item.Length);
                name = name.TrimEnd(item);
            }

            return name;
        }

        /// <summary>查找指定名称的父菜单下一级的子菜单</summary>
        /// <param name="parentname"></param>
        /// <returns></returns>
        [DataObjectMethod(DataObjectMethodType.Select)]
        public static EntityList<TEntity> FindAllByParent(String parentname)
        {
            var parent = Meta.Cache.Entities.Find(__.Name, parentname);
            if (parent == null) return null;

            var list = FindAllByParent(parent.Code);
            if (list != null) return list;

            list = FindAllByParent(parent.OldCode);
            if (list != null) return list;

            list = FindAllByParent(parent.OldCode2);
            if (list != null) return list;

            list = FindAllByParent(parent.OldCode3);
            if (list != null) return list;

            return null;
        }
        #endregion

        #region 对象操作
        /// <summary>已重载。基类先调用Valid(true)验证数据,然后在事务保护内调用OnInsert</summary>
        /// <returns></returns>
        public override Int32 Insert()
        {
            CheckOldArea();

            return base.Insert();
        }

        /// <summary>已重载</summary>
        /// <returns></returns>
        public override Int32 Update()
        {
            CheckOldArea();

            return base.Update();
        }

        void CheckOldArea()
        {
            if (OldArea.Count > 0)
            {
                var sb = new StringBuilder(OldArea.Count * 10);
                foreach (var item in OldArea)
                {
                    if (sb.Length > 0) sb.Append(";");
                    sb.AppendFormat("{0}|{1}", item.Key, item.Value);
                }
                Description = sb.ToString();
            }
        }

        ///// <summary>
        ///// 已重载。在事务保护范围内处理业务,位于Valid之后
        ///// </summary>
        ///// <returns></returns>
        //protected override Int32 OnInsert()
        //{
        //    return base.OnInsert();
        //}

        /// <summary>验证数据,通过抛出异常的方式提示验证失败。</summary>
        /// <param name="isNew"></param>
        public override void Valid(Boolean isNew)
        {
            // 建议先调用基类方法,基类方法会对唯一索引的数据进行验证
            base.Valid(isNew);

            // 这里验证参数范围,建议抛出参数异常,指定参数名,前端用户界面可以捕获参数异常并聚焦到对应的参数输入框
            if (String.IsNullOrEmpty(__.Name)) throw new ArgumentNullException(__.Name, _.Name.Description + "无效!");
            if (Code < 1) throw new ArgumentOutOfRangeException(__.Code, _.Code.Description + "必须大于0!");

            // 在新插入数据或者修改了指定字段时进行唯一性验证,CheckExist内部抛出参数异常
            //if (isNew || Dirtys[_.Name]) CheckExist(__.Name);
            if (isNew || Dirtys[_.Name] || Dirtys[_.ParentCode]) CheckExist(__.Name, _.ParentCode);
            //if ((isNew || Dirtys[_.Name]) && Exist(__.Name)) throw new ArgumentException(__.Name, "值为" + Name + "的" + _.Name.Description + "已存在!");
        }

        ///// <summary>
        ///// 首次连接数据库时初始化数据,仅用于实体类重载,用户不应该调用该方法
        ///// </summary>
        //[EditorBrowsable(EditorBrowsableState.Never)]
        //protected override void InitData()
        //{
        //    base.InitData();

        //    // InitData一般用于当数据表没有数据时添加一些默认数据,该实体类的任何第一次数据库操作都会触发该方法,默认异步调用
        //    // Meta.Count是快速取得表记录数
        //    if (Meta.Count > 0) return;

        //    // 需要注意的是,如果该方法调用了其它实体类的首次数据库操作,目标实体类的数据初始化将会在同一个线程完成
        //    if (XTrace.Debug) XTrace.WriteLine("开始初始化{0}管理员数据……", typeof(TEntity).Name);

        //    TEntity user = new TEntity();
        //    user.Name = "admin";
        //    user.Password = DataHelper.Hash("admin");
        //    user.DisplayName = "管理员";
        //    user.RoleID = 1;
        //    user.IsEnable = true;
        //    user.Insert();

        //    if (XTrace.Debug) XTrace.WriteLine("完成初始化{0}管理员数据!", typeof(TEntity).Name);
        //}
        #endregion

        #region 高级查询
        // 以下为自定义高级查询的例子

        ///// <summary>
        ///// 查询满足条件的记录集,分页、排序
        ///// </summary>
        ///// <param name="key">关键字</param>
        ///// <param name="orderClause">排序,不带Order By</param>
        ///// <param name="startRowIndex">开始行,0表示第一行</param>
        ///// <param name="maximumRows">最大返回行数,0表示所有行</param>
        ///// <returns>实体集</returns>
        //[DataObjectMethod(DataObjectMethodType.Select, true)]
        //public static EntityList<Area> Search(String key, String orderClause, Int32 startRowIndex, Int32 maximumRows)
        //{
        //    return FindAll(SearchWhere(key), orderClause, null, startRowIndex, maximumRows);
        //}

        ///// <summary>
        ///// 查询满足条件的记录总数,分页和排序无效,带参数是因为ObjectDataSource要求它跟Search统一
        ///// </summary>
        ///// <param name="key">关键字</param>
        ///// <param name="orderClause">排序,不带Order By</param>
        ///// <param name="startRowIndex">开始行,0表示第一行</param>
        ///// <param name="maximumRows">最大返回行数,0表示所有行</param>
        ///// <returns>记录数</returns>
        //public static Int32 SearchCount(String key, String orderClause, Int32 startRowIndex, Int32 maximumRows)
        //{
        //    return FindCount(SearchWhere(key), null, null, 0, 0);
        //}

        /// <summary>构造搜索条件</summary>
        /// <param name="key">关键字</param>
        /// <returns></returns>
        private static String SearchWhere(String key)
        {
            // WhereExpression重载&和|运算符,作为And和Or的替代
            var exp = SearchWhereByKeys(key);

            // 以下仅为演示,2、3行是同一个意思的不同写法,FieldItem重载了等于以外的运算符(第4行)
            //exp &= _.Name.Equal("testName")
            //    & !String.IsNullOrEmpty(key) & _.Name.Equal(key)
            //    .AndIf(!String.IsNullOrEmpty(key), _.Name.Equal(key))
            //    | _.ID > 0;

            return exp;
        }
        #endregion

        #region 扩展操作
        #endregion

        #region 业务
        /// <summary>查找地区。查找编码是否存在,若不存在,则按照名称匹配</summary>
        /// <param name="code"></param>
        /// <param name="name">名称</param>
        /// <returns></returns>
        public static TEntity FindByCodeAndName(Int32 code, String name)
        {
            TEntity entity = FindByCode(code);
            // 如果编码找不到,则从上级开始找名字
            if (entity == null)
            {
                // 直接上级
                TEntity parent = null;
                if (code % 100 != 0) parent = FindByCode(code / 100 * 100);
                if (parent != null)
                {
                    EntityList<TEntity> list = parent.FindAllByName(name, false);
                    if (list != null && list.Count > 0) entity = list[0];
                }
                if (entity == null)
                {
                    // 再上一级
                    parent = FindByCode(code / 10000 * 10000);

                    // 上级都找不到,就没有意义了
                    if (parent == null) return null;

                    EntityList<TEntity> list = parent.FindAllByName(name, false);
                    if (list != null && list.Count > 0) entity = list[0];
                }
                if (entity == null && code / 10000 * 10000 == 510000)
                {
                    // 四川省51划分出重庆直辖市50
                    parent = FindByCode(500000);

                    // 上级都找不到,就没有意义了
                    if (parent == null) return null;

                    EntityList<TEntity> list = parent.FindAllByName(name, false);
                    if (list != null && list.Count > 0) entity = list[0];
                }
            }

            return entity;
        }

        /// <summary>检查并附件到现有地区,如果没有找到匹配地区,则附加到最近的顶级地区,不会新增地区</summary>
        /// <param name="code"></param>
        /// <param name="name">名称</param>
        /// <param name="fullname"></param>
        /// <returns></returns>
        public static TEntity CheckAndAppend(Int32 code, String name, String fullname = null)
        {
            TEntity entity = FindByCodeAndName(code, name);
            if (entity != null)
            {
                if (entity.Code == entity.OldCode) entity.OldCode = 0;
                if (entity.Code == entity.OldCode2) entity.OldCode2 = 0;
                if (entity.Code == entity.OldCode3) entity.OldCode3 = 0;
                if (entity.Code != code && entity.OldCode != code && entity.OldCode2 != code && entity.OldCode3 != code && !entity.OldArea.ContainsKey(code))
                {
                    if (entity.OldCode == 0)
                        entity.OldCode = code;
                    else if (entity.OldCode2 == 0)
                        entity.OldCode2 = code;
                    else if (entity.OldCode3 == 0)
                        entity.OldCode3 = code;
                    else
                        entity.OldArea.Add(code, fullname);

                    entity.SaveWithoutValid();
                }

                return entity;
            }

            // 直接上级
            TEntity parent = null;
            if (code % 100 != 0) parent = FindByCode(code / 100 * 100);
            // 再上一级
            if (parent == null) parent = FindByCode(code / 10000 * 10000);

            // 上级都找不到,就没有意义了
            if (parent == null) return null;

            // 挂在直接上级的扩展里面
            if (!parent.OldArea.ContainsKey(code))
            {
                parent.OldArea.Add(code, fullname);
                parent.SaveWithoutValid();
            }

            return parent;
        }

        /// <summary>已重载。</summary>
        /// <returns></returns>
        public override string ToString()
        {
            return String.Format("{0} {1} {2} {3} {4} {5} {6}", Code, OldCode, OldCode2, OldCode3, Name, FriendName, Description);

            //StringBuilder sb = new StringBuilder();
            //sb.AppendFormat("{0} {1}", Code, Name);

            //if (OldCode2 > 0) sb.AppendFormat(" {0}", OldCode2);
            //if (OldCode3 > 0) sb.AppendFormat(" {0}", OldCode3);

            //sb.AppendFormat(" {0}", FriendName);

            //return sb.ToString();
        }
        #endregion

        #region 导入导出
        /// <summary>导入</summary>
        /// <param name="reader"></param>
        public static void Import(StreamReader reader)
        {
            using (var trans = new EntityTransaction<TEntity>())
            {
                while (!reader.EndOfStream)
                {
                    String context = reader.ReadLine();
                    if (String.IsNullOrEmpty(context)) break;

                    String[] ss = context.Split(new Char[] { ' ' });

                    TEntity entity = new TEntity();
                    Int32 code = Int32.Parse(ss[0]);
                    entity.Code = code;

                    Int32 oldcode = Int32.Parse(ss[1]);
                    if (code != oldcode) entity.OldCode = oldcode;

                    Int32 oldcode2 = Int32.Parse(ss[2]);
                    if (code != oldcode2) entity.OldCode2 = oldcode2;

                    Int32 oldcode3 = Int32.Parse(ss[3]);
                    if (code != oldcode3) entity.OldCode3 = oldcode3;

                    entity.Name = ss[4];

                    if (ss.Length > 5) entity.Description = ss[5];

                    // 查找父级地区
                    if (code % 10000 == 0)
                        entity.ParentCode = 0;
                    else if (code % 100 == 0)
                    {
                        entity.ParentCode = code / 10000;
                        entity.ParentCode = entity.ParentCode * 10000;
                    }
                    else
                    {
                        entity.ParentCode = code / 100;
                        entity.ParentCode = entity.ParentCode * 100;
                    }

                    entity.SaveWithoutValid();
                }
                trans.Commit();
            }
        }

        /// <summary>从文本文件导入</summary>
        /// <param name="fileName"></param>
        public static void Import(String fileName = "AreaCode.txt")
        {
            using (StreamReader reader = new StreamReader(fileName))
            {
                Import(reader);
            }
        }

        /// <summary>导出</summary>
        /// <param name="writer"></param>
        public static void Export(StreamWriter writer)
        {
            var list = FindAllByName(null, null, _.Code, 0, 0);
            if (list == null || list.Count < 1) return;

            foreach (var item in list)
            {
                writer.Write("{0} {1} {2} {3} {4}", item.Code, item.OldCode, item.OldCode2, item.OldCode3, item.Name);
                if (!String.IsNullOrEmpty(item.Description)) writer.Write(" {0}", item.Description);

                writer.WriteLine();
            }
        }

        /// <summary>导出到文本文件</summary>
        /// <param name="fileName"></param>
        public static void Export(String fileName = "AreaCode.txt")
        {
            using (StreamWriter writer = new StreamWriter(fileName, false))
            {
                Export(writer);
            }
        }
        #endregion
    }
}