v9.7.2018.0421   支持运行时修改DAL连接字符串
大石头 authored at 2018-04-21 14:00:47
19.06 KiB
X
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using NewLife.Collections;
using NewLife.Reflection;
using XCode.Common;
using XCode.Exceptions;

namespace XCode.DataAccessLayer
{
    /* 正向工程层次结构:
     *  GetTables
     *      OnGetTables
     *          GetSchema
     *          GetTables
     *              GetFields
     *                  GetFields
     *                      FixField
     *                          GetDataType
     *              GetIndexes
     *                  FixIndex
     *              FixTable
     */

    partial class DbMetaData
    {
        #region 常量
        protected static class _
        {
            public static readonly String Tables = "Tables";
            public static readonly String Views = "Views";
            public static readonly String Indexes = "Indexes";
            public static readonly String IndexColumns = "IndexColumns";
            public static readonly String Databases = "Databases";
            public static readonly String Columns = "Columns";
            public static readonly String ID = "ID";
            public static readonly String OrdinalPosition = "ORDINAL_POSITION";
            public static readonly String ColumnPosition = "COLUMN_POSITION";
            public static readonly String TalbeName = "table_name";
            public static readonly String ColumnName = "COLUMN_NAME";
            public static readonly String IndexName = "INDEX_NAME";
            public static readonly String PrimaryKeys = "PrimaryKeys";
        }
        #endregion

        #region 表构架
        /// <summary>取得所有表构架</summary>
        /// <returns></returns>
        public List<IDataTable> GetTables()
        {
            try
            {
                return OnGetTables(null);
            }
            catch (DbException ex)
            {
                throw new XDbMetaDataException(this, "取得所有表构架出错!", ex);
            }
        }

        /// <summary>取得所有表构架</summary>
        /// <returns></returns>
        protected virtual List<IDataTable> OnGetTables(String[] names)
        {
            var list = new List<IDataTable>();

            var dt = GetSchema(_.Tables, null);
            if (dt?.Rows == null || dt.Rows.Count < 1) return list;

            // 默认列出所有表
            var rows = dt?.Rows.ToArray();

            return GetTables(rows, names);
        }

        /// <summary>根据数据行取得数据表</summary>
        /// <param name="rows">数据行</param>
        /// <param name="names">指定表名</param>
        /// <param name="data">扩展</param>
        /// <returns></returns>
        protected List<IDataTable> GetTables(DataRow[] rows, String[] names, IDictionary<String, DataTable> data = null)
        {
            if (rows == null || rows.Length == 0) return new List<IDataTable>();

            // 表名过滤
            if (names != null && names.Length > 0)
            {
                var hs = new HashSet<String>(names, StringComparer.OrdinalIgnoreCase);
                rows = rows.Where(dr => TryGetDataRowValue(dr, _.TalbeName, out String name) && hs.Contains(name)).ToArray();
            }

            var columns = data?["Columns"];
            var indexes = data?["Indexes"];
            var indexColumns = data?["IndexColumns"];

            if (columns == null) columns = GetSchema(_.Columns, null);
            if (indexes == null) indexes = GetSchema(_.Indexes, null);
            if (indexColumns == null) indexColumns = GetSchema(_.IndexColumns, null);

            var list = new List<IDataTable>();
            foreach (var dr in rows)
            {
                #region 基本属性
                var table = DAL.CreateTable();
                table.TableName = GetDataRowValue<String>(dr, _.TalbeName);

                // 描述
                table.Description = GetDataRowValue<String>(dr, "DESCRIPTION");

                // 拥有者
                table.Owner = GetDataRowValue<String>(dr, "OWNER");

                // 是否视图
                table.IsView = "View".EqualIgnoreCase(GetDataRowValue<String>(dr, "TABLE_TYPE"));

                table.DbType = Database.Type;
                #endregion

                #region 字段及修正
                var cs = GetFields(table, columns, data);
                if (cs != null && cs.Count > 0) table.Columns.AddRange(cs);

                var dis = GetIndexes(table, indexes, indexColumns);
                if (dis != null && dis.Count > 0) table.Indexes.AddRange(dis);

                FixTable(table, dr, data);

                // 修正关系数据
                table.Fix();

                list.Add(table);
                #endregion
            }

            return list;
        }

        /// <summary>修正表</summary>
        /// <param name="table"></param>
        /// <param name="dr"></param>
        /// <param name="data"></param>
        protected virtual void FixTable(IDataTable table, DataRow dr, IDictionary<String, DataTable> data) { }
        #endregion

        #region 字段架构
        /// <summary>取得指定表的所有列构架</summary>
        /// <param name="table"></param>
        /// <param name="columns">列</param>
        /// <param name="data"></param>
        /// <returns></returns>
        protected virtual List<IDataColumn> GetFields(IDataTable table, DataTable columns, IDictionary<String, DataTable> data)
        {
            var dt = columns;
            if (dt == null) return null;

            // 找到该表所有字段,注意排序
            DataRow[] drs = null;
            var where = String.Format("{0}='{1}'", _.TalbeName, table.TableName);
            if (dt.Columns.Contains(_.OrdinalPosition))
                drs = dt.Select(where, _.OrdinalPosition);
            else if (dt.Columns.Contains(_.ID))
                drs = dt.Select(where, _.ID);
            else
                drs = dt.Select(where);

            return GetFields(table, drs);
        }

        /// <summary>获取指定表的字段</summary>
        /// <param name="table"></param>
        /// <param name="rows"></param>
        /// <returns></returns>
        protected virtual List<IDataColumn> GetFields(IDataTable table, DataRow[] rows)
        {
            var list = new List<IDataColumn>();
            foreach (var dr in rows)
            {
                var field = table.CreateColumn();

                // 名称
                field.ColumnName = GetDataRowValue<String>(dr, _.ColumnName);

                // 标识、主键
                if (TryGetDataRowValue(dr, "AUTOINCREMENT", out Boolean b))
                    field.Identity = b;

                if (TryGetDataRowValue(dr, "PRIMARY_KEY", out b))
                    field.PrimaryKey = b;

                // 原始数据类型
                field.RawType = GetDataRowValue<String>(dr, "DATA_TYPE", "DATATYPE", "COLUMN_DATA_TYPE");
                // 长度
                field.Length = GetDataRowValue<Int32>(dr, "CHARACTER_MAXIMUM_LENGTH", "LENGTH", "COLUMN_SIZE");

                if (field is XField fi)
                {
                    // 精度 与 位数
                    fi.Precision = GetDataRowValue<Int32>(dr, "NUMERIC_PRECISION", "DATETIME_PRECISION", "PRECISION");
                    fi.Scale = GetDataRowValue<Int32>(dr, "NUMERIC_SCALE", "SCALE");
                    if (field.Length == 0) field.Length = fi.Precision;
                }

                // 允许空
                if (TryGetDataRowValue(dr, "IS_NULLABLE", out b))
                    field.Nullable = b;
                else if (TryGetDataRowValue(dr, "IS_NULLABLE", out String str))
                {
                    if (!String.IsNullOrEmpty(str)) field.Nullable = "YES".EqualIgnoreCase(str);
                }
                else if (TryGetDataRowValue(dr, "NULLABLE", out str))
                {
                    if (!String.IsNullOrEmpty(str)) field.Nullable = "Y".EqualIgnoreCase(str);
                }

                // 描述
                field.Description = GetDataRowValue<String>(dr, "DESCRIPTION");

                FixField(field, dr);

                // 检查是否已正确识别类型
                if (field.DataType == null)
                    WriteLog("无法识别{0}.{1}的类型{2}!", table.TableName, field.ColumnName, field.RawType);
                // 非字符串字段,长度没有意义
                //else if (field.DataType != typeof(String))
                //    field.Length = 0;

                field.Fix();
                list.Add(field);
            }

            return list;
        }

        /// <summary>修正指定字段</summary>
        /// <param name="field">字段</param>
        /// <param name="dr"></param>
        protected virtual void FixField(IDataColumn field, DataRow dr)
        {
            // 修正数据类型 +++重点+++
            if (field.DataType == null) field.DataType = GetDataType(field);
        }
        #endregion

        #region 索引架构
        /// <summary>获取索引</summary>
        /// <param name="table"></param>
        /// <param name="indexes">索引</param>
        /// <param name="indexColumns">索引列</param>
        /// <returns></returns>
        protected virtual List<IDataIndex> GetIndexes(IDataTable table, DataTable indexes, DataTable indexColumns)
        {
            if (indexes == null) return null;

            var drs = indexes.Select(String.Format("{0}='{1}'", _.TalbeName, table.TableName));
            if (drs == null || drs.Length < 1) return null;

            var list = new List<IDataIndex>();
            foreach (var dr in drs)
            {
                if (!TryGetDataRowValue(dr, _.IndexName, out String name)) continue;

                var di = table.CreateIndex();
                di.Name = name;

                if (TryGetDataRowValue(dr, _.ColumnName, out name) && !String.IsNullOrEmpty(name))
                    di.Columns = name.Split(",");
                else if (indexColumns != null)
                {
                    String orderby = null;
                    // Oracle数据库用ColumnPosition,其它数据库用OrdinalPosition
                    if (indexColumns.Columns.Contains(_.OrdinalPosition))
                        orderby = _.OrdinalPosition;
                    else if (indexColumns.Columns.Contains(_.ColumnPosition))
                        orderby = _.ColumnPosition;

                    var dics = indexColumns.Select(String.Format("{0}='{1}' And {2}='{3}'", _.TalbeName, table.TableName, _.IndexName, di.Name), orderby);
                    if (dics != null && dics.Length > 0)
                    {
                        var ns = new List<String>();
                        foreach (var item in dics)
                        {
                            if (TryGetDataRowValue(item, _.ColumnName, out String dcname) && !dcname.IsNullOrEmpty() && !ns.Contains(dcname)) ns.Add(dcname);
                        }
                        if (ns.Count < 1) DAL.WriteLog("表{0}的索引{1}无法取得字段列表!", table, di.Name);
                        di.Columns = ns.ToArray();
                    }
                }

                if (TryGetDataRowValue(dr, "UNIQUE", out Boolean b))
                    di.Unique = b;

                if (TryGetDataRowValue(dr, "PRIMARY", out b))
                    di.PrimaryKey = b;
                else if (TryGetDataRowValue(dr, "PRIMARY_KEY", out b))
                    di.PrimaryKey = b;

                FixIndex(di, dr);

                list.Add(di);
            }
            return list != null && list.Count > 0 ? list : null;
        }

        /// <summary>修正索引</summary>
        /// <param name="index"></param>
        /// <param name="dr"></param>
        protected virtual void FixIndex(IDataIndex index, DataRow dr) { }
        #endregion

        #region 数据类型
        /// <summary>类型映射</summary>
        protected IDictionary<Type, String[]> Types { get; set; }

        protected List<KeyValuePair<Type, Type>> _FieldTypeMaps;
        /// <summary>字段类型映射</summary>
        protected virtual List<KeyValuePair<Type, Type>> FieldTypeMaps
        {
            get
            {
                if (_FieldTypeMaps == null)
                {
                    // 把不常用的类型映射到常用类型,比如数据库SByte映射到实体类Byte,UInt32映射到Int32,而不需要重新修改数据库
                    var list = new List<KeyValuePair<Type, Type>>
                    {
                        new KeyValuePair<Type, Type>(typeof(SByte), typeof(Byte)),
                        //list.Add(new KeyValuePair<Type, Type>(typeof(SByte), typeof(Int16)));
                        // 因为等价,字节需要能够互相映射
                        new KeyValuePair<Type, Type>(typeof(Byte), typeof(SByte)),

                        new KeyValuePair<Type, Type>(typeof(UInt16), typeof(Int16)),
                        new KeyValuePair<Type, Type>(typeof(Int16), typeof(UInt16)),
                        //list.Add(new KeyValuePair<Type, Type>(typeof(UInt16), typeof(Int32)));
                        //list.Add(new KeyValuePair<Type, Type>(typeof(Int16), typeof(Int32)));

                        new KeyValuePair<Type, Type>(typeof(UInt32), typeof(Int32)),
                        new KeyValuePair<Type, Type>(typeof(Int32), typeof(UInt32)),
                        //// 因为自增的原因,某些字段需要被映射到Int32里面来
                        //list.Add(new KeyValuePair<Type, Type>(typeof(SByte), typeof(Int32)));

                        new KeyValuePair<Type, Type>(typeof(UInt64), typeof(Int64)),
                        new KeyValuePair<Type, Type>(typeof(Int64), typeof(UInt64)),
                        //list.Add(new KeyValuePair<Type, Type>(typeof(UInt64), typeof(Int32)));
                        //list.Add(new KeyValuePair<Type, Type>(typeof(Int64), typeof(Int32)));

                        //// 根据常用行,从不常用到常用排序,然后配对进入映射表
                        //var types = new Type[] { typeof(SByte), typeof(Byte), typeof(UInt16), typeof(Int16), typeof(UInt64), typeof(Int64), typeof(UInt32), typeof(Int32) };

                        //for (int i = 0; i < types.Length; i++)
                        //{
                        //    for (int j = i + 1; j < types.Length; j++)
                        //    {
                        //        list.Add(new KeyValuePair<Type, Type>(types[i], types[j]));
                        //    }
                        //}
                        //// 因为自增的原因,某些字段需要被映射到Int64里面来
                        //list.Add(new KeyValuePair<Type, Type>(typeof(UInt32), typeof(Int64)));
                        //list.Add(new KeyValuePair<Type, Type>(typeof(Int32), typeof(Int64)));
                        new KeyValuePair<Type, Type>(typeof(Guid), typeof(String))
                    };
                    _FieldTypeMaps = list;
                }
                return _FieldTypeMaps;
            }
        }

        /// <summary>取字段类型</summary>
        /// <param name="field">字段</param>
        /// <returns></returns>
        protected virtual String GetFieldType(IDataColumn field)
        {
            var type = field.DataType;
            if (type == null) return null;

            // 处理枚举
            if (type.IsEnum) type = typeof(Int32);

            if (!Types.TryGetValue(type, out var ns)) return null;

            var typeName = ns.FirstOrDefault();
            // 大文本选第二个类型
            if (ns.Length > 1 && type == typeof(String) && (field.Length <= 0 || field.Length >= Database.LongTextLength)) typeName = ns[1];
            if (typeName.Contains("{0}")) typeName = typeName.F(field.Length);

            return typeName;
        }

        /// <summary>获取数据类型</summary>
        /// <param name="field"></param>
        /// <returns></returns>
        protected virtual Type GetDataType(IDataColumn field)
        {
            var rawType = field.RawType;
            if (rawType.Contains("(")) rawType = rawType.Substring(null, "(");
            var rawType2 = rawType + "(";

            foreach (var item in Types)
            {
                String dbtype = null;
                if (rawType.EqualIgnoreCase(item.Value))
                {
                    dbtype = item.Value[0];

                    // 大文本选第二个类型
                    if (item.Value.Length > 1 && item.Key == typeof(String) && (field.Length <= 0 || field.Length >= Database.LongTextLength)) dbtype = item.Value[1];
                }
                else
                {
                    dbtype = item.Value.FirstOrDefault(e => e.StartsWithIgnoreCase(rawType2));
                }
                if (!dbtype.IsNullOrEmpty())
                {
                    // 修正原始类型
                    if (dbtype.Contains("{0}"))
                    {
                        // 某些字段有精度需要格式化
                        if (dbtype.Contains("{1}"))
                        {
                            if (field is XField xf)
                                field.RawType = dbtype.F(xf.Precision, xf.Scale);
                        }
                        else
                            field.RawType = dbtype.F(field.Length);
                    }

                    return item.Key;
                }
            }

            return null;
        }

        /// <summary>获取数据类型字符串</summary>
        /// <returns></returns>
        public String GetDataTypes()
        {
            var dt = GetSchema(DbMetaDataCollectionNames.DataTypes, null);
            var rows = dt.Select("", "DataType Asc, IsBestMatch Desc");
            var dic = new Dictionary<String, List<String>>();
            foreach (var dr in rows)
            {
                var tname = (dr["DataType"] + "").TrimStart("System.");
                if (!dic.TryGetValue(tname, out var list)) dic[tname] = list = new List<String>();
                var v = dr["CreateFormat"] + "";
                if (v.IsNullOrEmpty()) v = dr["TypeName"] + "";
                list.Add(v);
            }

            dic = dic.OrderBy(e => (Int32)e.Key.GetTypeEx().GetTypeCode()).ToDictionary(e => e.Key, e => e.Value);

            var sb = new StringBuilder();
            foreach (var item in dic)
            {
                if (sb.Length > 0) sb.AppendLine(",");

                sb.Append(new String(' ', 12));
                sb.AppendFormat("{{ typeof({0}), new String[] {{ {1} }} }}", item.Key, item.Value.Select(e => "\"" + e + "\"").Join(", "));
            }

            return Environment.NewLine + sb.ToString();
        }
        #endregion
    }
}