测试时,创建表失败,与自增有关
Stone authored at 2012-07-27 00:26:06
28.61 KiB
X
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Web;
using NewLife;
using NewLife.Collections;
using NewLife.Log;
using NewLife.Reflection;
using NewLife.Threading;
using XCode.Cache;
using XCode.Configuration;
using XCode.DataAccessLayer;

#if DEBUG
using XCode.Common;
#endif

namespace XCode
{
    partial class Entity<TEntity>
    {
        /// <summary>实体元数据</summary>
        public static class Meta
        {
            #region 基本属性
            private static Type _ThisType;
            /// <summary>实体类型</summary>
            public static Type ThisType { get { return _ThisType ?? (_ThisType = typeof(TEntity)); } set { _ThisType = value; } }

            /// <summary>表信息</summary>
            public static TableItem Table { get { return TableItem.Create(ThisType); } }

            [ThreadStatic]
            private static String _ConnName;
            /// <summary>链接名。线程内允许修改,修改者负责还原。若要还原默认值,设为null即可</summary>
            public static String ConnName
            {
                get { return _ConnName ?? (_ConnName = Table.ConnName); }
                set
                {
                    //修改链接名,挂载当前表
                    if (!String.IsNullOrEmpty(value) && !String.Equals(_ConnName, value, StringComparison.OrdinalIgnoreCase))
                    {
                        CheckTable(value, TableName);

                        // 清空记录数缓存
                        ClearCountCache();
                    }
                    _ConnName = value;

                    if (String.IsNullOrEmpty(_ConnName)) _ConnName = Table.ConnName;
                }
            }

            [ThreadStatic]
            private static String _TableName;
            /// <summary>表名。线程内允许修改,修改者负责还原</summary>
            public static String TableName
            {
                get { return _TableName ?? (_TableName = Table.TableName); }
                set
                {
                    //修改表名
                    if (!String.IsNullOrEmpty(value) && !String.Equals(_TableName, value, StringComparison.OrdinalIgnoreCase))
                    {
                        CheckTable(ConnName, value);

                        // 清空记录数缓存
                        ClearCountCache();
                    }
                    _TableName = value;

                    if (String.IsNullOrEmpty(_TableName)) _TableName = Table.TableName;
                }
            }

            private static ICollection<String> hasCheckedTables = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
            private static void CheckTable(String connName, String tableName)
            {
                var key = String.Format("{0}#{1}", connName, tableName);
                if (hasCheckedTables.Contains(key)) return;
                lock (hasCheckedTables)
                {
                    if (hasCheckedTables.Contains(key)) return;

                    // 检查新表名对应的数据表
                    var table = TableItem.Create(ThisType).DataTable;
                    // 克隆一份,防止修改
                    table = table.Clone() as IDataTable;

                    if (table.Name != tableName)
                    {
                        // 修改一下索引名,否则,可能因为同一个表里面不同的索引冲突
                        if (table.Indexes != null)
                        {
                            foreach (var di in table.Indexes)
                            {
                                var sb = new StringBuilder();
                                sb.AppendFormat("IX_{0}", tableName);
                                foreach (var item in di.Columns)
                                {
                                    sb.Append("_");
                                    sb.Append(item);
                                }

                                di.Name = sb.ToString();
                            }
                        }
                        table.Name = tableName;
                    }

                    //var set = new NegativeSetting();
                    //set.CheckOnly = DAL.NegativeCheckOnly;
                    //set.NoDelete = DAL.NegativeNoDelete;
                    //DAL.Create(connName).Db.CreateMetaData().SetTables(set, table);
                    DAL.Create(connName).SetTables(table);

                    hasCheckedTables.Add(key);
                }
            }

            /// <summary>所有数据属性</summary>
            public static FieldItem[] AllFields { get { return Table.AllFields; } }

            /// <summary>所有绑定到数据表的属性</summary>
            public static FieldItem[] Fields { get { return Table.Fields; } }

            /// <summary>字段名列表</summary>
            public static IList<String> FieldNames { get { return Table.FieldNames; } }

            /// <summary>唯一键,返回第一个标识列或者唯一的主键</summary>
            public static FieldItem Unique
            {
                get
                {
                    if (Table.Identity != null) return Table.Identity;
                    if (Table.PrimaryKeys != null && Table.PrimaryKeys.Length > 0) return Table.PrimaryKeys[0];
                    return null;
                }
            }

            /// <summary>实体操作者</summary>
            public static IEntityOperate Factory
            {
                get
                {
                    Type type = ThisType;
                    if (type.IsInterface) return null;

                    return EntityFactory.CreateOperate(type);
                }
            }
            #endregion

            #region 数据库操作
            /// <summary>数据操作对象。</summary>
            public static DAL DBO { get { return DAL.Create(ConnName); } }

            /// <summary>数据库类型</summary>
            public static DatabaseType DbType { get { return DBO.DbType; } }

            /// <summary>执行SQL查询,返回记录集</summary>
            /// <param name="builder">SQL语句</param>
            /// <param name="startRowIndex">开始行,0表示第一行</param>
            /// <param name="maximumRows">最大返回行数,0表示所有行</param>
            /// <returns></returns>
            public static DataSet Query(SelectBuilder builder, Int32 startRowIndex, Int32 maximumRows)
            {
                WaitForInitData();

                return DBO.Select(builder, startRowIndex, maximumRows, Meta.TableName);
            }

            /// <summary>查询</summary>
            /// <param name="sql">SQL语句</param>
            /// <returns>结果记录集</returns>
            //[Obsolete("请优先考虑使用SelectBuilder参数做查询!")]
            public static DataSet Query(String sql)
            {
                WaitForInitData();

                return DBO.Select(sql, Meta.TableName);
            }

            /// <summary>查询记录数</summary>
            /// <param name="sql">SQL语句</param>
            /// <returns>记录数</returns>
            [Obsolete("请优先考虑使用SelectBuilder参数做查询!")]
            public static Int32 QueryCount(String sql)
            {
                WaitForInitData();

                return DBO.SelectCount(sql, Meta.TableName);
            }

            /// <summary>查询记录数</summary>
            /// <param name="sb">查询生成器</param>
            /// <returns>记录数</returns>
            public static Int32 QueryCount(SelectBuilder sb)
            {
                WaitForInitData();

                return DBO.SelectCount(sb, new String[] { Meta.TableName });
            }

            /// <summary>执行</summary>
            /// <param name="sql">SQL语句</param>
            /// <returns>影响的结果</returns>
            public static Int32 Execute(String sql)
            {
                WaitForInitData();

                Int32 rs = DBO.Execute(sql, Meta.TableName);
                executeCount++;
                DataChange("修改数据");
                return rs;
            }

            /// <summary>执行插入语句并返回新增行的自动编号</summary>
            /// <param name="sql">SQL语句</param>
            /// <returns>新增行的自动编号</returns>
            public static Int64 InsertAndGetIdentity(String sql)
            {
                WaitForInitData();

                Int64 rs = DBO.InsertAndGetIdentity(sql, Meta.TableName);
                executeCount++;
                DataChange("修改数据");
                return rs;
            }

            /// <summary>执行</summary>
            /// <param name="sql">SQL语句</param>
            /// <param name="type">命令类型,默认SQL文本</param>
            /// <param name="ps">命令参数</param>
            /// <returns>影响的结果</returns>
            public static Int32 Execute(String sql, CommandType type = CommandType.Text, params DbParameter[] ps)
            {
                WaitForInitData();

                Int32 rs = DBO.Execute(sql, type, ps, Meta.TableName);
                executeCount++;
                DataChange("修改数据");
                return rs;
            }

            /// <summary>执行插入语句并返回新增行的自动编号</summary>
            /// <param name="sql">SQL语句</param>
            /// <param name="type">命令类型,默认SQL文本</param>
            /// <param name="ps">命令参数</param>
            /// <returns>新增行的自动编号</returns>
            public static Int64 InsertAndGetIdentity(String sql, CommandType type = CommandType.Text, params DbParameter[] ps)
            {
                WaitForInitData();

                Int64 rs = DBO.InsertAndGetIdentity(sql, type, ps, Meta.TableName);
                executeCount++;
                DataChange("修改数据");
                return rs;
            }

            static void DataChange(String reason = null)
            {
                // 还在事务保护里面,不更新缓存,最后提交或者回滚的时候再更新
                // 一般事务保护用于批量更新数据,次数频繁删除缓存将会打来巨大的性能损耗
                // 2012-07-17 当前实体类开启的事务保护,必须由当前类结束,否则可能导致缓存数据的错乱
                if (TransCount > 0) return;

                Cache.Clear(reason);
                //_Count = null;
                ClearCountCache();

                if (_OnDataChange != null) _OnDataChange(ThisType);
            }

            //private static WeakReference<Action<Type>> _OnDataChange = new WeakReference<Action<Type>>();
            private static Action<Type> _OnDataChange;
            /// <summary>数据改变后触发。参数指定触发该事件的实体类</summary>
            public static event Action<Type> OnDataChange
            {
                add
                {
                    if (value != null)
                    {
                        // 这里不能对委托进行弱引用,因为GC会回收委托,应该改为对对象进行弱引用
                        //WeakReference<Action<Type>> w = value;

                        _OnDataChange += new WeakAction<Type>(value, delegate(Action<Type> handler) { _OnDataChange -= handler; }, true);
                    }
                }
                remove { }
            }

            static Int32[] hasCheckModel = new Int32[] { 0 };
            private static void CheckModel()
            {
                //if (Interlocked.CompareExchange(ref hasCheckModel, 1, 0) != 0) return;
                if (hasCheckModel[0] > 0) return;
                lock (hasCheckModel)
                {
                    if (hasCheckModel[0] > 0) return;

                    if (!DAL.NegativeEnable || DAL.NegativeExclude.Contains(ConnName) || DAL.NegativeExclude.Contains(TableName) || IsGenerated)
                    {
                        hasCheckModel[0] = 1;
                        return;
                    }

                    // 输出调用者,方便调试
#if DEBUG
                    if (DAL.Debug) DAL.WriteLog("检查实体{0}的数据表架构,模式:{1},调用栈:{2}", ThisType.FullName, Table.ModelCheckMode, Helper.GetCaller());
#else
                    // CheckTableWhenFirstUse的实体类,在这里检查,有点意思,记下来
                    if (DAL.Debug && Table.ModelCheckMode == ModelCheckModes.CheckTableWhenFirstUse)
                        DAL.WriteLog("检查实体{0}的数据表架构,模式:{1}", ThisType.FullName, Table.ModelCheckMode);
#endif

                    // 第一次使用才检查的,此时检查
                    Boolean ck = false;
                    if (Table.ModelCheckMode == ModelCheckModes.CheckTableWhenFirstUse) ck = true;
                    // 或者前面初始化的时候没有涉及的,也在这个时候检查
                    if (!DBO.HasCheckTables.Contains(TableName))
                    {
                        DBO.HasCheckTables.Add(TableName);

#if DEBUG
                        if (!ck && DAL.Debug) DAL.WriteLog("集中初始化表架构时没赶上,现在补上!");
#endif

                        ck = true;
                    }
                    if (ck)
                    {
                        Func check = delegate
                        {
#if DEBUG
                            DAL.WriteLog("开始{2}检查表[{0}/{1}]的数据表架构……", Table.DataTable.Name, DbType, DAL.NegativeCheckOnly ? "异步" : "同步");
#endif

                            var sw = new Stopwatch();
                            sw.Start();

                            try
                            {
                                //var set = new NegativeSetting();
                                //set.CheckOnly = DAL.NegativeCheckOnly;
                                //set.NoDelete = DAL.NegativeNoDelete;
                                //DBO.Db.CreateMetaData().SetTables(set, Table.DataTable);
                                DBO.SetTables(Table.DataTable);
                            }
                            finally
                            {
                                sw.Stop();

#if DEBUG
                                DAL.WriteLog("检查表[{0}/{1}]的数据表架构耗时{2}", Table.DataTable.Name, DbType, sw.Elapsed);
#endif
                            }
                        };

                        // 打开了开关,并且设置为true时,使用同步方式检查
                        // 设置为false时,使用异步方式检查,因为上级的意思是不大关心数据库架构
                        if (!DAL.NegativeCheckOnly)
                            check();
                        else
                            ThreadPoolX.QueueUserWorkItem(check);
                    }

                    hasCheckModel[0] = 1;
                }
            }

            private static Boolean IsGenerated { get { return ThisType.GetCustomAttribute<CompilerGeneratedAttribute>(true) != null; } }

            /// <summary>记录已进行数据初始化的表</summary>
            static Dictionary<String, AutoResetEvent> hasCheckInitData = new Dictionary<string, AutoResetEvent>(StringComparer.OrdinalIgnoreCase);
            //static List<String> hasCheckInitData = new List<String>();

            /// <summary>检查并初始化数据。参数等待时间为0表示不等待</summary>
            /// <param name="millisecondsTimeout">等待时间,-1表示不限,0表示不等待</param>
            /// <returns>如果等待,返回是否收到信号</returns>
            public static Boolean WaitForInitData(Int32 millisecondsTimeout = 0)
            {
                String key = ConnName + "$$$" + TableName;
                AutoResetEvent e;
                if (hasCheckInitData.TryGetValue(key, out e))
                {
                    // 是否需要等待
                    if (millisecondsTimeout != 0 && e != null)
                    {
#if DEBUG
                        if (DAL.Debug) DAL.WriteLog("开始等待初始化{0}数据{1}ms,调用栈:{2}", ThisType.FullName, millisecondsTimeout, Helper.GetCaller());
#else
                        if (DAL.Debug) DAL.WriteLog("开始等待初始化{0}数据{1}ms", ThisType.FullName, millisecondsTimeout);
#endif
                        try
                        {
                            // 如果未收到信号,表示超时
                            if (!e.WaitOne(millisecondsTimeout, false)) return false;
                        }
                        finally
                        {
#if DEBUG
                            if (DAL.Debug) DAL.WriteLog("结束等待初始化{0}数据,调用栈:{1}", ThisType.FullName, Helper.GetCaller());
#else
                            if (DAL.Debug) DAL.WriteLog("结束等待初始化{0}数据", ThisType.FullName);
#endif
                        }
                    }
                    return true;
                }

                e = new AutoResetEvent(false);
                lock (hasCheckInitData)
                {
                    if (hasCheckInitData.ContainsKey(key)) return true;
                    hasCheckInitData.Add(key, e);
                }

                // 如果该实体类是首次使用检查模型,则在这个时候检查
                CheckModel();

                // 输出调用者,方便调试
#if DEBUG
                if (DAL.Debug) DAL.WriteLog("初始化{0}数据,调用栈:{1}", ThisType.FullName, Helper.GetCaller());
#else
                if (DAL.Debug) DAL.WriteLog("初始化{0}数据", ThisType.FullName);
#endif

                try
                {
                    EntityBase entity = Factory.Default as EntityBase;
                    if (entity != null) entity.InitData();
                }
                catch (Exception ex)
                {
                    if (XTrace.Debug) XTrace.WriteLine("初始化数据出错!" + ex.ToString());
                }
                finally { e.Set(); }

                return true;
            }
            #endregion

            #region 事务保护
            [ThreadStatic]
            private static Int32 TransCount = 0;
            [ThreadStatic]
            private static Int32 executeCount = 0;

            /// <summary>开始事务</summary>
            /// <returns></returns>
            public static Int32 BeginTrans()
            {
                // 可能存在多层事务,这里不能把这个清零
                //executeCount = 0;
                return TransCount = DBO.BeginTransaction();
            }

            /// <summary>提交事务</summary>
            /// <returns></returns>
            public static Int32 Commit()
            {
                TransCount = DBO.Commit();
                // 提交事务时更新数据,虽然不是绝对准确,但没有更好的办法
                // 即使提交了事务,但只要事务内没有执行更新数据的操作,也不更新
                // 2012-06-13 测试证明,修改数据后,提交事务后会更新缓存等数据
                if (TransCount <= 0 && executeCount > 0)
                {
                    DataChange("修改数据后提交事务");
                    // 回滚到顶层才更新数据
                    executeCount = 0;
                }
                return TransCount;
            }

            /// <summary>回滚事务</summary>
            /// <returns></returns>
            public static Int32 Rollback()
            {
                TransCount = DBO.Rollback();
                // 回滚的时候貌似不需要更新缓存
                //if (TransCount <= 0 && executeCount > 0) DataChange();
                if (TransCount <= 0 && executeCount > 0)
                {
                    // 因为在事务保护中添加或删除实体时直接操作了实体缓存,所以需要更新
                    DataChange("修改数据后回滚事务");
                    executeCount = 0;
                }
                return TransCount;
            }

            /// <summary>是否在事务保护中</summary>
            internal static Boolean UsingTrans { get { return TransCount > 0; } }
            #endregion

            #region 参数化
            /// <summary>创建参数</summary>
            /// <returns></returns>
            public static DbParameter CreateParameter() { return DBO.Db.Factory.CreateParameter(); }

            /// <summary>格式化参数名</summary>
            /// <param name="name"></param>
            /// <returns></returns>
            public static String FormatParameterName(String name) { return DBO.Db.FormatParameterName(name); }
            #endregion

            #region 辅助方法
            /// <summary>格式化关键字</summary>
            /// <param name="name"></param>
            /// <returns></returns>
            public static String FormatName(String name)
            {
                return DBO.Db.FormatName(name);
            }

            /// <summary>格式化关键字</summary>
            /// <param name="name"></param>
            /// <returns></returns>
            [Obsolete("改为使用FormatName")]
            public static String FormatKeyWord(String name)
            {
                return FormatName(name);
            }

            /// <summary>格式化时间</summary>
            /// <param name="dateTime"></param>
            /// <returns></returns>
            public static String FormatDateTime(DateTime dateTime)
            {
                return DBO.Db.FormatDateTime(dateTime);
            }

            /// <summary>格式化数据为SQL数据</summary>
            /// <param name="name"></param>
            /// <param name="value"></param>
            /// <returns></returns>
            public static String FormatValue(String name, Object value)
            {
                return FormatValue(Table.FindByName(name), value);
            }

            /// <summary>格式化数据为SQL数据</summary>
            /// <param name="field"></param>
            /// <param name="value"></param>
            /// <returns></returns>
            public static String FormatValue(FieldItem field, Object value)
            {
                return DBO.Db.FormatValue(field != null ? field.Field : null, value);
            }
            #endregion

            #region 缓存
            private static DictionaryCache<String, EntityCache<TEntity>> _cache = new DictionaryCache<string, EntityCache<TEntity>>();
            /// <summary>实体缓存</summary>
            /// <returns></returns>
            public static EntityCache<TEntity> Cache
            {
                get
                {
                    // 以连接名和表名为key,因为不同的库不同的表,缓存也不一样
                    //String key = String.Format("{0}_{1}", ConnName, TableName);
                    //if (_cache.ContainsKey(key)) return _cache[key];
                    //lock (_cache)
                    //{
                    //    if (_cache.ContainsKey(key)) return _cache[key];

                    return _cache.GetItem(String.Format("{0}_{1}", ConnName, TableName), delegate(String key)
                    {
                        var ec = new EntityCache<TEntity>();
                        ec.ConnName = ConnName;
                        ec.TableName = TableName;
                        //_cache.Add(key, ec);

                        return ec;
                    });
                }
            }

            ///// <summary>是否在使用缓存</summary>
            //internal static Boolean UsingCache { get { return _cache.ContainsKey(String.Format("{0}_{1}", ConnName, TableName)); } }

            private static DictionaryCache<String, SingleEntityCache<Object, TEntity>> _singleCache = new DictionaryCache<String, SingleEntityCache<Object, TEntity>>();
            /// <summary>
            /// 单对象实体缓存。
            /// 建议自定义查询数据方法,并从二级缓存中获取实体数据,以抵消因初次填充而带来的消耗。
            /// </summary>
            public static SingleEntityCache<Object, TEntity> SingleCache
            {
                get
                {
                    // 以连接名和表名为key,因为不同的库不同的表,缓存也不一样
                    return _singleCache.GetItem(String.Format("{0}_{1}", ConnName, TableName), delegate(String key)
                    {
                        var ec = new SingleEntityCache<Object, TEntity>();
                        ec.ConnName = ConnName;
                        ec.TableName = TableName;
                        return ec;
                    });
                }
            }

            /// <summary>总记录数,小于1000时是精确的,大于1000时缓存10分钟</summary>
            public static Int32 Count { get { return (Int32)LongCount; } }

            /// <summary>总记录数较小时,使用静态字段,较大时增加使用Cache</summary>
            private static Int64? _Count;
            /// <summary>总记录数,小于1000时是精确的,大于1000时缓存10分钟</summary>
            public static Int64 LongCount
            {
                get
                {
                    String key = String.Format("{0}_{1}_{2}_Count", ConnName, TableName, ThisType.Name);

                    // By 大石头 2012-05-27
                    // 这里好像有问题,不明白上次为什么要注释
                    Int64? n = _Count;
                    if (n != null && n.HasValue)
                    {
                        if (n.Value > 0 && n.Value < 1000) return n.Value;

                        // 大于1000,使用HttpCache
                        Int64? k = (Int64?)HttpRuntime.Cache[key];
                        if (k != null && k.HasValue) return k.Value;
                    }

                    CheckModel();

                    Int64 m = 0;
                    //if (n != null && n.HasValue && n.Value < 1000)
                    //Int64? n = _Count;
                    if (n != null && n.HasValue && n.Value < 1000)
                        m = FindCount();
                    else
                        m = DBO.Session.QueryCountFast(TableName);
                    _Count = m;

                    if (m >= 1000) HttpRuntime.Cache.Insert(key, m, null, DateTime.Now.AddMinutes(10), System.Web.Caching.Cache.NoSlidingExpiration);

                    // 先拿到记录数再初始化,因为初始化时会用到记录数,同时也避免了死循环
                    WaitForInitData();

                    return m;
                }
            }

            private static void ClearCountCache()
            {
                Int64? n = _Count;
                if (n == null || !n.HasValue) return;

                // 只有小于1000时才清空_Count,因为大于1000时它要作为HttpCache的见证
                if (n.Value < 1000)
                {
                    _Count = null;
                    return;
                }

                //String key = ThisType.Name + "_Count";
                String key = String.Format("{0}_{1}_{2}_Count", ConnName, TableName, ThisType.Name);
                HttpRuntime.Cache.Remove(key);
            }
            #endregion

            #region 一些设置
            [ThreadStatic]
            private static Boolean _AllowInsertIdentity;
            /// <summary>是否允许向自增列插入数据。为免冲突,仅本线程有效</summary>
            public static Boolean AllowInsertIdentity { get { return _AllowInsertIdentity; } set { _AllowInsertIdentity = value; } }

            private static FieldItem _AutoSetGuidField;
            /// <summary>自动设置Guid的字段。对实体类有效,可在实体类类型构造函数里面设置</summary>
            public static FieldItem AutoSetGuidField { get { return _AutoSetGuidField; } set { _AutoSetGuidField = value; } }
            #endregion
        }
    }
}