You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							366 lines
						
					
					
						
							13 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							366 lines
						
					
					
						
							13 KiB
						
					
					
				
								using System;
							 | 
						|
								using System.Collections.Generic;
							 | 
						|
								using System.Data;
							 | 
						|
								using System.Reflection;
							 | 
						|
								
							 | 
						|
								namespace Apewer.Source
							 | 
						|
								{
							 | 
						|
								
							 | 
						|
								    /// <summary>表结构。</summary>
							 | 
						|
								    [Serializable]
							 | 
						|
								    public sealed class TableStructure
							 | 
						|
								    {
							 | 
						|
								
							 | 
						|
								        #region Instance
							 | 
						|
								
							 | 
						|
								        Type _model = null;
							 | 
						|
								        TableAttribute _table = null;
							 | 
						|
								        ColumnAttribute _key = null;
							 | 
						|
								        ColumnAttribute _flag = null;
							 | 
						|
								        TableIndex[] _indexes = null;
							 | 
						|
								        ColumnAttribute[] _columns = null;
							 | 
						|
								        ColumnAttribute[] _fillable = null;
							 | 
						|
								
							 | 
						|
								        private TableStructure() { }
							 | 
						|
								
							 | 
						|
								        /// <summary>使用此结构的记录模型。</summary>
							 | 
						|
								        public Type Model { get => _model; }
							 | 
						|
								
							 | 
						|
								        /// <summary>表特性。</summary>
							 | 
						|
								        public TableAttribute Table { get => _table; }
							 | 
						|
								
							 | 
						|
								        /// <summary>索引。</summary>
							 | 
						|
								        public TableIndex[] Indexes { get => _indexes; }
							 | 
						|
								
							 | 
						|
								        /// <summary>列信息。</summary>
							 | 
						|
								        public ColumnAttribute[] Columns { get => _columns; }
							 | 
						|
								
							 | 
						|
								        /// <summary>可填充的列信息。</summary>
							 | 
						|
								        public ColumnAttribute[] Fillable { get => _fillable; }
							 | 
						|
								
							 | 
						|
								        /// <summary>主键。</summary>
							 | 
						|
								        public ColumnAttribute Key { get => _key; }
							 | 
						|
								
							 | 
						|
								        /// <summary>记录标记。</summary>
							 | 
						|
								        public ColumnAttribute Flag { get => _flag; }
							 | 
						|
								
							 | 
						|
								        /// <summary>从 <see cref="TableStructure"/> 到 Boolean 的隐式转换,判断 <see cref="TableStructure"/> 有效。</summary>
							 | 
						|
								        /// <remarks>True:存在有效结构时;<br />False:不存在有效结构。</remarks>
							 | 
						|
								        public static implicit operator bool(TableStructure instance)
							 | 
						|
								        {
							 | 
						|
								            if (instance == null) return false;
							 | 
						|
								            if (instance._model == null) return false;
							 | 
						|
								            if (instance._columns == null) return false;
							 | 
						|
								            return true;
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        #endregion
							 | 
						|
								
							 | 
						|
								        #region Obsolete
							 | 
						|
								
							 | 
						|
								        /// <summary>不依赖 Record 公共属性。</summary>
							 | 
						|
								        /// <remarks>注意:此属性即将弃用,应改为使用 Table.Independent 属性。</remarks>
							 | 
						|
								        public bool Independent { get => _table == null ? false : _table.Independent; }
							 | 
						|
								
							 | 
						|
								        /// <summary>表的说明信息。</summary>
							 | 
						|
								        /// <remarks>注意:此属性即将弃用,应改为使用 Table.Description 属性。</remarks>
							 | 
						|
								        public string Description { get => _table == null ? null : _table.Description; }
							 | 
						|
								
							 | 
						|
								        /// <summary>表特性。</summary>
							 | 
						|
								        /// <remarks>注意:此属性即将弃用,应改为使用 Table 属性。</remarks>
							 | 
						|
								        public TableAttribute Attribute { get => _table; }
							 | 
						|
								
							 | 
						|
								        /// <summary>使用模型的所有属性,对缺少 Column 特性的属性使用默认参数的 Column 特性。</summary>
							 | 
						|
								        /// <remarks>注意:此属性即将弃用,应改为使用 Table.AllProperties 属性。</remarks>
							 | 
						|
								        public bool AllProperties { get => _table == null ? false : _table.AllProperties; }
							 | 
						|
								
							 | 
						|
								        /// <summary>表名。</summary>
							 | 
						|
								        /// <remarks>注意:此属性即将弃用,应改为使用 Table.Name 属性。</remarks>
							 | 
						|
								        public string TableName { get => _table == null ? null : _table.Name; }
							 | 
						|
								
							 | 
						|
								        #endregion
							 | 
						|
								
							 | 
						|
								        #region TableStructure
							 | 
						|
								
							 | 
						|
								        private static Dictionary<string, TableStructure> _tsc = new Dictionary<string, TableStructure>();
							 | 
						|
								
							 | 
						|
								        /// <summary>解析表结构。</summary>
							 | 
						|
								        public static TableStructure Parse<T>(bool useCache = true, bool force = false) where T : IRecord => Parse(typeof(T), useCache, force);
							 | 
						|
								
							 | 
						|
								        /// <summary>解析表结构。</summary>
							 | 
						|
								        /// <returns>表结构。类型不可用于表结构时返回 NULL 值。</returns>
							 | 
						|
								        public static TableStructure Parse(Type model, bool useCache = true, bool force = false)
							 | 
						|
								        {
							 | 
						|
								            var type = model;
							 | 
						|
								            if (type == null || !type.IsClass || type.IsAbstract) return null;
							 | 
						|
								
							 | 
						|
								            // 使用缓存。
							 | 
						|
								            var cacheKey = type.FullName;
							 | 
						|
								            if (force) cacheKey = "[force] " + cacheKey;
							 | 
						|
								            if (useCache)
							 | 
						|
								            {
							 | 
						|
								                lock (_tsc)
							 | 
						|
								                {
							 | 
						|
								                    TableStructure cached;
							 | 
						|
								                    if (_tsc.TryGetValue(cacheKey, out cached)) return cached;
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            // 解析 TableAttribute。
							 | 
						|
								            var ta = TableAttribute.Parse(type, useCache, force);
							 | 
						|
								            if (!ta && !force) return null;
							 | 
						|
								
							 | 
						|
								            // 类型。
							 | 
						|
								            var isRecord = RuntimeUtility.IsInherits(type, typeof(Record));
							 | 
						|
								            var properties = GetProperties(type);
							 | 
						|
								            var total = properties.Length;
							 | 
						|
								
							 | 
						|
								            // 解析 ColumnAttribute。
							 | 
						|
								            var key = null as ColumnAttribute;
							 | 
						|
								            var flag = null as ColumnAttribute;
							 | 
						|
								            var columns = new ColumnAttribute[total];
							 | 
						|
								            var columnsCount = 0;
							 | 
						|
								            var fillable = new List<ColumnAttribute>(total);
							 | 
						|
								            var indexesDict = new Dictionary<string, List<IndexAttribute>>();
							 | 
						|
								            if (total > 0)
							 | 
						|
								            {
							 | 
						|
								                var caForce = force || (ta ? ta.AllProperties : false);
							 | 
						|
								                var addedFields = new List<string>(total);
							 | 
						|
								                foreach (var property in properties)
							 | 
						|
								                {
							 | 
						|
								                    var ca = ColumnAttribute.Parse(property, caForce);
							 | 
						|
								                    if (ca != null)
							 | 
						|
								                    {
							 | 
						|
								                        // 检查 field 重复,只保留第一个。
							 | 
						|
								                        var field = ca.Field;
							 | 
						|
								                        if (!addedFields.Contains(field))
							 | 
						|
								                        {
							 | 
						|
								                            addedFields.Add(field);
							 | 
						|
								                            columns[columnsCount] = ca;
							 | 
						|
								                            columnsCount += 1;
							 | 
						|
								
							 | 
						|
								                            if (isRecord)
							 | 
						|
								                            {
							 | 
						|
								                                if (property.Name == "Key")
							 | 
						|
								                                {
							 | 
						|
								                                    key = ca;
							 | 
						|
								                                    if (ta != null && ta.PrimaryKey) ca.SetPrimaryKey();
							 | 
						|
								                                }
							 | 
						|
								                                else if (property.Name == "Flag")
							 | 
						|
								                                {
							 | 
						|
								                                    flag = ca;
							 | 
						|
								                                }
							 | 
						|
								                            }
							 | 
						|
								                        }
							 | 
						|
								
							 | 
						|
								                        // 索引
							 | 
						|
								                        foreach (var index in ca.Indexes)
							 | 
						|
								                        {
							 | 
						|
								                            var indexName = index.Name.Lower();
							 | 
						|
								                            if (indexName.IsEmpty()) continue;
							 | 
						|
								
							 | 
						|
								                            if (indexesDict.TryGetValue(indexName, out var list))
							 | 
						|
								                            {
							 | 
						|
								                                list.Add(index);
							 | 
						|
								                            }
							 | 
						|
								                            else
							 | 
						|
								                            {
							 | 
						|
								                                list = new List<IndexAttribute>();
							 | 
						|
								                                list.Add(index);
							 | 
						|
								                                indexesDict.Add(indexName, list);
							 | 
						|
								                            }
							 | 
						|
								                        }
							 | 
						|
								
							 | 
						|
								                        // 可查询的列。
							 | 
						|
								                        fillable.Add(ca);
							 | 
						|
								                        continue;
							 | 
						|
								                    }
							 | 
						|
								
							 | 
						|
								                    // 可查询的列。
							 | 
						|
								                    if (!caForce)
							 | 
						|
								                    {
							 | 
						|
								                        ca = ColumnAttribute.Parse(property, caForce);
							 | 
						|
								                        if (ca) fillable.Add(ca);
							 | 
						|
								                    }
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								            if (columnsCount > 0 && columnsCount != columns.Length) columns = columns.Slice(0, columnsCount);
							 | 
						|
								
							 | 
						|
								            // 整理索引。
							 | 
						|
								            var indexes = new List<TableIndex>();
							 | 
						|
								            foreach (var indexesList in indexesDict.Values)
							 | 
						|
								            {
							 | 
						|
								                var indexName = indexesList[0].Name;
							 | 
						|
								                var tableIndex = new TableIndex(indexName, ta, indexesList.ToArray());
							 | 
						|
								                indexes.Add(tableIndex);
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            // 排序,将 Key 和 Flag 排在最前。
							 | 
						|
								            columns = ColumnAttribute.Sort(columns);
							 | 
						|
								
							 | 
						|
								            // 返回结果。
							 | 
						|
								            var ts = new TableStructure();
							 | 
						|
								            ts._table = ta;
							 | 
						|
								            ts._key = key;
							 | 
						|
								            ts._flag = flag;
							 | 
						|
								            ts._indexes = indexes.ToArray();
							 | 
						|
								            ts._columns = columns;
							 | 
						|
								            ts._model = model;
							 | 
						|
								            ts._fillable = fillable.ToArray();
							 | 
						|
								
							 | 
						|
								            // 加入缓存。
							 | 
						|
								            if (useCache)
							 | 
						|
								            {
							 | 
						|
								                lock (_tsc)
							 | 
						|
								                {
							 | 
						|
								                    if (!_tsc.ContainsKey(cacheKey)) _tsc.Add(cacheKey, ts);
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            return ts;
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        // 获取属性,基类的属性排在前面。
							 | 
						|
								        static PropertyInfo[] GetProperties(Type type)
							 | 
						|
								        {
							 | 
						|
								            if (type == null) throw new ArgumentNullException(nameof(type));
							 | 
						|
								
							 | 
						|
								            // 需要获取属性的类型。
							 | 
						|
								            var types = new List<Type>();
							 | 
						|
								            {
							 | 
						|
								                var target = type;
							 | 
						|
								                while (true)
							 | 
						|
								                {
							 | 
						|
								                    if (target.Equals(RuntimeUtility.ObjectType)) break;
							 | 
						|
								                    types.Add(target);
							 | 
						|
								                    target = target.BaseType;
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            // 所有属性。
							 | 
						|
								            var list = new List<PropertyInfo>();
							 | 
						|
								            for (var i = types.Count - 1; i >= 0; i--)
							 | 
						|
								            {
							 | 
						|
								                var properties = types[i].GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
							 | 
						|
								                list.Add(properties);
							 | 
						|
								            }
							 | 
						|
								            return list.ToArray();
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        #endregion
							 | 
						|
								
							 | 
						|
								        #region TableAttribute
							 | 
						|
								
							 | 
						|
								        // 限定表名称/列名称。
							 | 
						|
								        static string RestrictName(string name, bool underline = false, bool english = false)
							 | 
						|
								        {
							 | 
						|
								            if (string.IsNullOrEmpty(name)) return null;
							 | 
						|
								            var str = name;
							 | 
						|
								
							 | 
						|
								            // 限定名称仅使用英文和数字。
							 | 
						|
								            if (english)
							 | 
						|
								            {
							 | 
						|
								                const string available = "_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
							 | 
						|
								                var chars = new ArrayBuilder<char>();
							 | 
						|
								                var strChars = str.ToCharArray();
							 | 
						|
								                var strLength = strChars.Length > 255 ? 255 : strChars.Length;
							 | 
						|
								                for (var i = 0; i < strLength; i++)
							 | 
						|
								                {
							 | 
						|
								                    if (available.IndexOf(strChars[i]) > -1) chars.Add(strChars[i]);
							 | 
						|
								                }
							 | 
						|
								                str = new string(chars.Export());
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            // 以下划线开始。
							 | 
						|
								            if (underline)
							 | 
						|
								            {
							 | 
						|
								                if (!str.StartsWith("_")) str = TextUtility.Merge("_", str);
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            return str;
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        static IDataParameter CreateParameter(object record, ColumnAttribute ca, Func<Parameter, IDataParameter> callback)
							 | 
						|
								        {
							 | 
						|
								            var property = ca.Property;
							 | 
						|
								            if (property == null) return null;
							 | 
						|
								
							 | 
						|
								            var getter = property.GetGetMethod();
							 | 
						|
								            if (getter == null) return null;
							 | 
						|
								
							 | 
						|
								            var value = getter.Invoke(record, null);
							 | 
						|
								
							 | 
						|
								            if (ca.Type == ColumnType.Bytes || ca.Type == ColumnType.Integer || ca.Type == ColumnType.Float)
							 | 
						|
								            {
							 | 
						|
								                return callback(new Parameter(ca.Field, value, ca.Type, ca.Length));
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            if (ca.Type == ColumnType.DateTime)
							 | 
						|
								            {
							 | 
						|
								                return callback(new Parameter(ca.Field, value, ca.Type, 0));
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            if (property.PropertyType.Equals(typeof(String)))
							 | 
						|
								            {
							 | 
						|
								                var text = value as string;
							 | 
						|
								                if (text == null) text = "";
							 | 
						|
								                if (ca.Length > 0)
							 | 
						|
								                {
							 | 
						|
								                    switch (ca.Type)
							 | 
						|
								                    {
							 | 
						|
								                        case ColumnType.VarChar:
							 | 
						|
								                        case ColumnType.NVarChar:
							 | 
						|
								                            text = TextUtility.Left(text, ca.Length);
							 | 
						|
								                            break;
							 | 
						|
								                        case ColumnType.VarChar191:
							 | 
						|
								                        case ColumnType.NVarChar191:
							 | 
						|
								                            text = TextUtility.Left(text, 191);
							 | 
						|
								                            break;
							 | 
						|
								                    }
							 | 
						|
								                }
							 | 
						|
								                return callback(new Parameter(ca.Field, text, ca.Type, ca.Length));
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            var defaultText = (value == null) ? TextUtility.Empty : value.ToString();
							 | 
						|
								            return callback(new Parameter(ca.Field, defaultText, ca.Type, ca.Length));
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        /// <summary>生成 IDataParameter 列表,用于 Insert 和 Update 方法。</summary>
							 | 
						|
								        public IDataParameter[] CreateParameters(object record, Func<Parameter, IDataParameter> callback, IEnumerable<string> excludeds)
							 | 
						|
								        {
							 | 
						|
								            if (record == null || callback == null) return null;
							 | 
						|
								
							 | 
						|
								            var list = new List<IDataParameter>(_columns.Length);
							 | 
						|
								            foreach (var ca in Columns)
							 | 
						|
								            {
							 | 
						|
								                if (ca == null) continue;
							 | 
						|
								
							 | 
						|
								                var parameter = CreateParameter(record, ca, callback);
							 | 
						|
								                if (parameter == null) continue;
							 | 
						|
								
							 | 
						|
								                var add = true;
							 | 
						|
								                if (excludeds != null)
							 | 
						|
								                {
							 | 
						|
								                    var lower = parameter.ParameterName.ToLower();
							 | 
						|
								                    foreach (var excluded in excludeds)
							 | 
						|
								                    {
							 | 
						|
								                        if (string.IsNullOrEmpty(excluded)) continue;
							 | 
						|
								                        if (lower == excluded.ToLower())
							 | 
						|
								                        {
							 | 
						|
								                            add = false;
							 | 
						|
								                            break;
							 | 
						|
								                        }
							 | 
						|
								                    }
							 | 
						|
								                }
							 | 
						|
								
							 | 
						|
								                if (add) list.Add(parameter);
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            return list.ToArray();
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        #endregion
							 | 
						|
								
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								}
							 | 
						|
								
							 |