v9.7.2018.0421   支持运行时修改DAL连接字符串
大石头 编写于 2018-04-21 14:00:47
X
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.Script.Serialization;
using System.Xml.Serialization;
using NewLife.Reflection;
using XCode.Common;
using XCode.Configuration;
using XCode.Model;

namespace XCode
{
    /// <summary>数据实体基类的基类</summary>
    [Serializable]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public abstract partial class EntityBase : /*BinaryAccessor,*/ IEntity, ICloneable
    {
        #region 初始化数据
        /// <summary>首次连接数据库时初始化数据,仅用于实体类重载,用户不应该调用该方法</summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        internal protected virtual void InitData() { }
        #endregion

        #region 填充数据
        /// <summary>填充数据完成时调用。默认设定标记<see cref="_IsFromDatabase"/></summary>
        internal protected virtual void OnLoad() { MarkDb(true); }

        internal void MarkDb(Boolean flag) { _IsFromDatabase = flag; }
        #endregion

        #region 操作
        /// <summary>把该对象持久化到数据库</summary>
        /// <returns></returns>
        public abstract Int32 Insert();

        /// <summary>更新数据库</summary>
        /// <returns></returns>
        public abstract Int32 Update();

        /// <summary>从数据库中删除该对象</summary>
        /// <returns></returns>
        public abstract Int32 Delete();

        /// <summary>保存。根据主键检查数据库中是否已存在该对象,再决定调用Insert或Update</summary>
        /// <returns></returns>
        public abstract Int32 Save();

        /// <summary>不需要验证的保存</summary>
        /// <returns></returns>
        public abstract Int32 SaveWithoutValid();

        /// <summary>异步保存。实现延迟保存,大事务保存。主要面向日志表和频繁更新的在线记录表</summary>
        /// <param name="msDelay">延迟保存的时间。默认0ms近实时保存</param>
        /// <returns>是否成功加入异步队列</returns>
        public abstract Boolean SaveAsync(Int32 msDelay = 0);
        #endregion

        #region 获取/设置 字段值
        /// <summary>
        /// 获取/设置 字段值。
        /// 一个索引,反射实现。
        /// 派生实体类可重写该索引,以避免发射带来的性能损耗。
        /// 基类已经实现了通用的快速访问,但是这里仍然重写,以增加控制,
        /// 比如字段名是属性名前面加上_,并且要求是实体字段才允许这样访问,否则一律按属性处理。
        /// </summary>
        /// <param name="name">字段名</param>
        /// <returns></returns>
        public abstract Object this[String name] { get; set; }

        /// <summary>设置字段值,该方法影响脏数据。</summary>
        /// <param name="name">字段名</param>
        /// <param name="value">值</param>
        /// <returns>返回是否成功设置了数据</returns>
        public Boolean SetItem(String name, Object value)
        {
            var fact = EntityFactory.CreateOperate(GetType());
            FieldItem fi = fact.Table.FindByName(name);
            // 确保数据类型一致
            if (fi != null) value = value.ChangeType(fi.Type);

            var b = OnPropertyChanging(name, value);
            if (b)
            {
                // OnPropertyChanging中根据新旧值是否相同来影响脏数据
                // SetItem作为必定影响脏数据的代替者
                this[name] = value;
                Dirtys[name] = true;
            }
            return b;
        }

        /// <summary>设置脏数据项。如果某个键存在并且数据没有脏,则设置</summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns>返回是否成功设置了数据</returns>
        public Boolean SetNoDirtyItem(String name, Object value)
        {
            var fact = EntityFactory.CreateOperate(GetType());
            if (fact.FieldNames.Contains(name) && !Dirtys[name]) return SetItem(name, value);

            return false;
        }
        #endregion

        #region 克隆
        /// <summary>创建当前对象的克隆对象,仅拷贝基本字段</summary>
        /// <returns></returns>
        public abstract Object Clone();

        /// <summary>克隆实体。创建当前对象的克隆对象,仅拷贝基本字段</summary>
        /// <param name="setDirty">是否设置脏数据</param>
        /// <returns></returns>
        IEntity IEntity.CloneEntity(Boolean setDirty) { return CloneEntityInternal(setDirty); }

        /// <summary>克隆实体</summary>
        /// <param name="setDirty"></param>
        /// <returns></returns>
        internal protected abstract IEntity CloneEntityInternal(Boolean setDirty = true);

        /// <summary>复制来自指定实体的成员,可以是不同类型的实体,只复制共有的基本字段,影响脏数据</summary>
        /// <param name="entity">来源实体对象</param>
        /// <param name="setDirty">是否设置脏数据</param>
        /// <returns>实际复制成员数</returns>
        public virtual Int32 CopyFrom(IEntity entity, Boolean setDirty = true)
        {
            if (entity == this) return 0;

            IEntity src = this;
            var nsSrc = EntityFactory.CreateOperate(src.GetType()).FieldNames;
            //if (nsSrc == null || nsSrc.Count < 1) return 0;
            var nsDes = EntityFactory.CreateOperate(entity.GetType()).FieldNames;
            if (nsDes == null || nsDes.Count < 1) return 0;

            var n = 0;
            foreach (var item in nsDes)
            {
                if (nsSrc.Contains(item))
                {
                    if (setDirty)
                        src.SetItem(item, entity[item]);
                    else
                        src[item] = entity[item];
                }
                else
                {
                    // 如果没有该字段,则写入到扩展属性里面去
                    src.Extends[item] = entity[item];
                    if (setDirty) Dirtys[item] = true;
                }

                n++;
            }
            // 赋值扩展数据
            entity.Extends.CopyTo(src.Extends);

            return n;
        }
        #endregion

        #region 脏数据
        [NonSerialized]
        private DirtyCollection _Dirtys;
        /// <summary>脏属性。存储哪些属性的数据被修改过了。</summary>
        [XmlIgnore, ScriptIgnore]
        internal protected IDictionary<String, Boolean> Dirtys
        {
            get
            {
                if (_Dirtys == null) _Dirtys = new DirtyCollection();
                return _Dirtys;
            }
        }

        /// <summary>脏属性。存储哪些属性的数据被修改过了。</summary>
        IDictionary<String, Boolean> IEntity.Dirtys { get { return Dirtys; } }

        /// <summary>设置所有数据的脏属性</summary>
        /// <param name="isDirty">改变脏属性的属性个数</param>
        /// <returns></returns>
        protected virtual Int32 SetDirty(Boolean isDirty)
        {
            var ds = _Dirtys;
            if (ds == null || ds.IsEmpty) return 0;

            var count = 0;
            foreach (var item in ds.Keys)
            {
                if (ds[item] != isDirty)
                {
                    ds[item] = isDirty;
                    count++;
                }
            }
            return count;
        }
        #endregion

        #region 扩展属性
        [NonSerialized]
        private EntityExtend _Extends;
        /// <summary>扩展属性</summary>
        //[NonSerialized]
        [XmlIgnore, ScriptIgnore]
        public EntityExtend Extends { get { return _Extends ?? (_Extends = new EntityExtend()); } set { _Extends = value; } }
        #endregion

        #region 累加
        [NonSerialized]
        private IEntityAddition _Addition;
        /// <summary>累加</summary>
        [XmlIgnore, ScriptIgnore]
        internal IEntityAddition Addition
        {
            get
            {
                if (_Addition == null)
                {
                    _Addition = XCodeService.Container.Resolve<IEntityAddition>();
                    _Addition.Entity = this;
                }
                return _Addition;
            }
        }
        #endregion

        #region 主键为空
        /// <summary>主键是否为空</summary>
        [XmlIgnore, ScriptIgnore]
        public Boolean IsNullKey { get { return Helper.IsEntityNullKey(this); } }

        // JsonNet支持下面这种特殊用法
        //public Boolean ShouldSerializeIsNullKey()
        //{
        //    return false;
        //}

        /// <summary>设置主键为空。Save将调用Insert</summary>
        void IEntity.SetNullKey()
        {
            var eop = EntityFactory.CreateOperate(GetType());
            foreach (var item in eop.Fields)
            {
                if (item.PrimaryKey || item.IsIdentity) this[item.Name] = null;
            }
        }
        #endregion

        #region 实体相等
        /// <summary>判断两个实体是否相等。有可能是同一条数据的两个实体对象</summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        Boolean IEntity.EqualTo(IEntity entity)
        {
            if (entity == null || GetType() != entity.GetType()) return false;
            if (this == entity) return true;

            // 判断是否所有主键相等
            var op = EntityFactory.CreateOperate(GetType());
            var ps = op.Table.PrimaryKeys;
            // 如果没有主键,则判断所有字段
            if (ps == null || ps.Length < 1) ps = op.Table.Fields;
            foreach (var item in ps)
            {
                var v1 = this[item.Name];
                var v2 = entity[item.Name];
                //// 特殊处理整数类型,避免出现相同值不同整型而导致结果不同
                //if (item.Type.IsIntType() && Convert.ToInt64(v1) != Convert.ToInt64(v2)) return false;

                //if (item.Type == typeof(String)) { v1 += ""; v2 += ""; }

                //if (!Object.Equals(v1, v2)) return false;
                if (!CheckEqual(v1, v2)) return false;
            }

            return true;
        }
        #endregion
    }
}