DDD(八)【基础设施层】

最近被DDD吸引了阿,在这里感谢一下小佟,呵呵,领域驱动设计是个不错的东西,帮助我们把问题清晰化,这候对于复杂业务逻辑是很重要的,今天这一讲主要说一下DDD中的基础设施层(Infrastructure)是如何被我实现的。

Infrastructure Layer:主要功能是对领域模块进行持久化的,在这个层中你需要把领域对象序列化到指定的元件中,可能是数据库,文件或者内存对象,当然它也要提供从物理元件取出数据到领域模型的功能,这是对应的。

目前的DDD项目结果如下

DDD(八)【基础设施层】_第1张图片
对于Infrastructure这个层我不去定义接口而是让它去实现Domain层的接口,即一切从领域出发,而Infrastructure只负责具体的数据持久化工作,下面我们主要介绍一下IRepository.csIExtensionRepository.csInfrastructure层是如何被实现的。

与传统DATA层的区别

一 传统Data层定义接口,为数据为导向,而不是以业务为导。

二 将Repository的方法中添加了领域规约,使它Infrastructure更有目的的去实现。

引入工作单元,使多方法形式一个事务的概念

IUnitOfWork接口也是在domain层实现的,不过在这里我们也介绍一下它的代码

    public interface IUnitOfWork
    {
        /// 
        /// 将操作提交到数据库,
        /// 
        void Save();
        /// 
        /// 是否需要显示进行提交(save())
        /// 默认为false,即在repository方法中自动完成提交,值为true时,表示需要显示调用save()方法
        /// 
        /// 
        bool IsExplicitSubmit { get; set; }
    }

工作单元会有保存save操作和是否显示提交的属性,开发人员可以根据业务情况去选择IsExplicitSubmit的状态,默认是自动提交,如果希望多个方法统一一次提交,可以将IsExplicitSubmit设为true,然后手动进行save(),这是有助于提升程序性能的。

Infrastructure层的核心代码

    public class DbContextRepository<TEntity> :
        IExtensionRepository<TEntity>
        where TEntity : class
    {
        #region Constructors
        public DbContextRepository(IUnitOfWork db, Action<string> logger)
        {
            iUnitWork = db;
            _Db = (DbContext)db;
            Logger = logger;
            ((IObjectContextAdapter)_Db).ObjectContext.CommandTimeout = 0;
        }

        public DbContextRepository(IUnitOfWork db)
            : this(db, null)
        { }

        #endregion

        #region Properties
        /// 
        /// 数据上下文
        /// 
        protected DbContext _Db { get; private set; }
        /// 
        /// 工作单元上下文,子类可以直接使用它
        /// 
        protected IUnitOfWork iUnitWork { get; set; }

        /// 
        /// Action委托事例,在派生类可以操作它
        /// 
        protected Action<string> Logger { get; private set; }

        /// 
        /// 得到上下文中表对象的所有记录数
        /// 当数据表记录在千万以上时,select count(1) from table
        /// 的速度会受到影响,所以扩展一个方法解决这个问题
        /// 
        /// 
        /// 
        /// 
        public int RecordCount
        {
            get
            {
                return Convert.ToInt32(_Db.Database.SqlQuery<long>(
                    "SELECT ROWCNT FROM SYSINDEXES WHERE ID=OBJECT_ID('" + typeof(TEntity).Name + "') AND INDID<2").First());
            }
        }
        #endregion

        #region Fields
        /// 
        /// 数据总数
        /// 
        int DataTotalCount = 0;

        /// 
        /// 数据总页数
        /// 
        int DataTotalPages = 0;

        /// 
        /// 数据页面大小(每次向数据库提交的记录数)
        /// 
        int DataPageSize = 10000;
        #endregion

        #region IRepository 成员

        public virtual void Insert(TEntity item)
        {
            OnBeforeSaved(new SavedEventArgs(item, SaveAction.Insert));
            _Db.Entry<TEntity>(item);
            _Db.Set<TEntity>().Add(item);
            this.SaveChanges();
            OnAfterSaved(new SavedEventArgs(item, SaveAction.Insert));
        }

        public virtual void Delete(TEntity item)
        {
            OnBeforeSaved(new SavedEventArgs(item, SaveAction.Delete));
            _Db.Set<TEntity>().Attach(item);
            _Db.Set<TEntity>().Remove(item);
            this.SaveChanges();
            OnAfterSaved(new SavedEventArgs(item, SaveAction.Delete));
        }

        public virtual void Update(TEntity item)
        {
            OnBeforeSaved(new SavedEventArgs(item, SaveAction.Update));
            _Db.Set<TEntity>().Attach(item);
            _Db.Entry(item).State = EntityState.Modified;
            this.SaveChanges();
            OnAfterSaved(new SavedEventArgs(item, SaveAction.Update));
        }

        public IQueryable<TEntity> GetModel()
        {
            return _Db.Set<TEntity>().AsNoTracking();//对象无法自动添加到上下文中,因为它是使用 NoTracking 合并选项检索的。请在定义此关系之前,将该实体显式附加到 ObjectContext。
            // return _Db.Set();
        }

        #endregion

        #region IExtensionRepository 成员

        public virtual void Insert(IEnumerable<TEntity> item)
        {
            item.ToList().ForEach(i =>
            {
                _Db.Entry<TEntity>(i);
                _Db.Set<TEntity>().Add(i);
            });
            this.SaveChanges();
        }

        public virtual void Delete(IEnumerable<TEntity> item)
        {
            item.ToList().ForEach(i =>
            {
                _Db.Set<TEntity>().Attach(i);
                _Db.Set<TEntity>().Remove(i);
            });
            this.SaveChanges();
        }

        public virtual void Update(IEnumerable<TEntity> item)
        {
            item.ToList().ForEach(i =>
            {
                _Db.Set<TEntity>().Attach(i);
                _Db.Entry(i).State = EntityState.Modified;
            });
            this.SaveChanges();
        }

        public void Update<T>(Expression<Action<T>> entity) where T : class
        {

            T newEntity = typeof(T).GetConstructor(Type.EmptyTypes).Invoke(null) as T;//建立指定类型的实例
            List<string> propertyNameList = new List<string>();
            MemberInitExpression param = entity.Body as MemberInitExpression;
            foreach (var item in param.Bindings)
            {
                string propertyName = item.Member.Name;
                object propertyValue;
                var memberAssignment = item as MemberAssignment;
                if (memberAssignment.Expression.NodeType == ExpressionType.Constant)
                {
                    propertyValue = (memberAssignment.Expression as ConstantExpression).Value;
                }
                else
                {
                    propertyValue = Expression.Lambda(memberAssignment.Expression, null).Compile().DynamicInvoke();
                }
                typeof(T).GetProperty(propertyName).SetValue(newEntity, propertyValue, null);
                propertyNameList.Add(propertyName);
            }
            _Db.Set<T>().Attach(newEntity);
            _Db.Configuration.ValidateOnSaveEnabled = false;
            var ObjectStateEntry = ((IObjectContextAdapter)_Db).ObjectContext.ObjectStateManager.GetObjectStateEntry(newEntity);
            propertyNameList.ForEach(x => ObjectStateEntry.SetModifiedProperty(x.Trim()));
            this.SaveChanges();
            // ((IObjectContextAdapter)_Db).ObjectContext.Detach(newEntity);
        }

        public TEntity Find(params object[] id)
        {
            return _Db.Set<TEntity>().Find(id);
        }

        public IQueryable<TEntity> GetModel(ISpecification<TEntity> specification)
        {
            return GetModel().Where(specification.SatisfiedBy());
        }

        public IQueryable<TEntity> GetModel(Expression<Func<TEntity, bool>> predicate)
        {
            return GetModel().Where(predicate);
        }

        public IQueryable<TEntity> GetModel<S>(Expression<Func<TEntity, S>> orderByExpression, bool asc)
        {
            Orderable<TEntity> order = new Orderable<TEntity>(this.GetModel());
            if (asc)
                order.Asc(orderByExpression);
            else
                order.Desc(orderByExpression);
            return order.Queryable;
        }

        public TEntity Find(Expression<Func<TEntity, bool>> predicate)
        {
            return GetModel(predicate).FirstOrDefault();
        }

        public TEntity Find(ISpecification<TEntity> specification)
        {
            return GetModel(specification).FirstOrDefault();
        }

        public void BulkInsert(IEnumerable<TEntity> item)
        {
            DataPageProcess(item, (currentItems) =>
            {
                ((IObjectContextAdapter)_Db).ObjectContext.CommandTimeout = 0;//永不超时
                _Db.Database.ExecuteSqlCommand(DoSQL(currentItems, SQLType.Insert));
            });
        }

        public void BulkDelete(IEnumerable<TEntity> item)
        {
            DataPageProcess(item, (currentItems) =>
            {
                ((IObjectContextAdapter)_Db).ObjectContext.CommandTimeout = 0;//永不超时
                _Db.Database.ExecuteSqlCommand(DoSQL(currentItems, SQLType.Delete));
            });
        }

        public void BulkUpdate(IEnumerable<TEntity> item, params string[] fieldParams)
        {
            DataPageProcess(item, (currentItems) =>
            {
                ((IObjectContextAdapter)_Db).ObjectContext.CommandTimeout = 0;//永不超时
                _Db.Database.ExecuteSqlCommand(DoSQL(currentItems, SQLType.Update, fieldParams));
            });
        }

        public void BulkUpdate(IEnumerable<Expression<Action<TEntity>>> expressionList)
        {
            DataPageProcess(expressionList, (currentItems) =>
            {
                StringBuilder sqlstr = new StringBuilder();
                currentItems.ToList().ForEach(i =>
                {
                    Tuple<string, object[]> sql = CreateUpdateSQL(i);
                    sqlstr.AppendFormat(sql.Item1, sql.Item2);
                });
                ((IObjectContextAdapter)_Db).ObjectContext.CommandTimeout = 0;//永不超时
                _Db.Database.ExecuteSqlCommand(sqlstr.ToString());
            });
        }

        public event Action<SavedEventArgs> AfterSaved;

        public event Action<SavedEventArgs> BeforeSaved;

        #endregion

        #region Protected Methods
        /// 
        /// 根据工作单元的IsUnitOfWork的属性,去判断是否提交到数据库
        /// 一般地,在多个repository类型进行组合时,这个IsUnitOfWork都会设为true,即不马上提交,
        /// 而对于单个repository操作来说,它的值不需要设置,使用默认的false,将直接提交到数据库,这也保证了操作的原子性。
        /// 
        protected void SaveChanges()
        {
            try
            {
                if (!iUnitWork.IsExplicitSubmit)// if (iUnitWork.IsUnitOfWork ^ true)
                    iUnitWork.Save();
            }
            catch (System.Data.Entity.Validation.DbEntityValidationException dbEx)
            {
                if (Logger == null)
                    throw dbEx;
                Logger(dbEx.Message);
            }
            catch (Exception ex)
            {
                if (Logger == null)//如果没有定义日志功能,就把异常抛出来吧
                    throw ex;
                Logger(ex.Message);
            }

        }

        /// 
        ///  计数更新,与SaveChange()是两个SQL链接,走分布式事务
        ///  子类可以根据自己的逻辑,去复写
        ///  tableName:表名
        ///  param:索引0为主键名,1表主键值,2为要计数的字段,3为增量
        /// 
        /// 表名
        /// 参数列表,索引0为主键名,1表主键值,2为要计数的字段,3为增量
        protected virtual void UpdateForCount(string tableName, params object[] param)
        {
            string sql = "UPDATE [" + tableName + "] SET [{2}]=ISNULL([{2}],0)+{3} WHERE [{0}]={1}";
            List<object> listParasm = new List<object>
            {
                param[0],
                param[1],
                param[2],
                param[3],
            };
            _Db.Database.ExecuteSqlCommand(string.Format(sql, listParasm.ToArray()));
        }
        #endregion

        #region Virtual Methods

        /// 
        /// Called after data saved
        /// 
        /// The sender.
        /// The action.
        protected virtual void OnAfterSaved(SavedEventArgs e)
        {
            if (AfterSaved != null)
            {
                AfterSaved(e);
            }
        }

        /// 
        /// Called before saved
        /// 
        /// The sender.
        /// The action.
        protected virtual void OnBeforeSaved(SavedEventArgs e)
        {
            if (BeforeSaved != null)
            {
                BeforeSaved(e);
            }
        }

        #endregion

        #region Private Methods

        /// 
        /// 分页进行数据提交的逻辑
        /// 
        /// 原列表
        /// 处理方法
        /// 要进行处理的新列表
        private void DataPageProcess(
            IEnumerable<TEntity> item,
            Action<IEnumerable<TEntity>> method)
        {
            if (item != null && item.Count() > 0)
            {
                DataTotalCount = item.Count();
                this.DataTotalPages = item.Count() / DataPageSize;
                if (DataTotalCount % DataPageSize > 0)
                    DataTotalPages += 1;
                for (int pageIndex = 1; pageIndex <= DataTotalPages; pageIndex++)
                {
                    var currentItems = item.Skip((pageIndex - 1) * DataPageSize).Take(DataPageSize).ToList();
                    method(currentItems);
                }
            }
        }

        private void DataPageProcess(
            IEnumerable<Expression<Action<TEntity>>> item,
            Action<IEnumerable<Expression<Action<TEntity>>>> method)
        {
            if (item != null && item.Count() > 0)
            {
                DataTotalCount = item.Count();
                this.DataTotalPages = item.Count() / DataPageSize;
                if (DataTotalCount % DataPageSize > 0)
                    DataTotalPages += 1;
                for (int pageIndex = 1; pageIndex <= DataTotalPages; pageIndex++)
                {
                    var currentItems = item.Skip((pageIndex - 1) * DataPageSize).Take(DataPageSize).ToList();
                    method(currentItems);
                }
            }
        }

        private static string GetEqualStatment(string fieldName, int paramId, Type pkType)
        {
            if (pkType.IsValueType)
                return string.Format("{0} = {1}", fieldName, GetParamTag(paramId));
            return string.Format("{0} = '{1}'", fieldName, GetParamTag(paramId));

        }

        private static string GetParamTag(int paramId)
        {
            return "{" + paramId + "}";
        }

        /// 
        /// 得到实体键EntityKey
        /// 
        /// 
        /// 
        protected ReadOnlyMetadataCollection<EdmMember> GetPrimaryKey()
        {
            EntitySetBase primaryKey = ((IObjectContextAdapter)_Db).ObjectContext.GetEntitySet(typeof(TEntity));
            ReadOnlyMetadataCollection<EdmMember> arr = primaryKey.ElementType.KeyMembers;
            return arr;
        }

        /// 
        /// 构建Update语句串
        /// 注意:如果本方法过滤了int,decimal类型更新为0的列,如果希望更新它们需要指定FieldParams参数
        /// 
        /// 实体列表
        /// 要更新的字段
        /// 
        private Tuple<string, object[]> CreateUpdateSQL(Expression<Action<TEntity>> expression)
        {

            TEntity entity = typeof(TEntity).GetConstructor(Type.EmptyTypes).Invoke(null) as TEntity;//建立指定类型的实例
            List<string> propertyNameList = new List<string>();
            MemberInitExpression param = expression.Body as MemberInitExpression;
            foreach (var item in param.Bindings)
            {
                string propertyName = item.Member.Name;
                object propertyValue;
                var memberAssignment = item as MemberAssignment;
                if (memberAssignment.Expression.NodeType == ExpressionType.Constant)
                {
                    propertyValue = (memberAssignment.Expression as ConstantExpression).Value;
                }
                else
                {
                    propertyValue = Expression.Lambda(memberAssignment.Expression, null).Compile().DynamicInvoke();
                }
                typeof(TEntity).GetProperty(propertyName).SetValue(entity, propertyValue, null);
                propertyNameList.Add(propertyName);
            }
            return CreateUpdateSQL(entity, propertyNameList.ToArray());
        }

        /// 
        /// 构建Update语句串
        /// 注意:如果本方法过滤了int,decimal类型更新为0的列,如果希望更新它们需要指定FieldParams参数
        /// 
        /// 实体列表
        /// 要更新的字段
        /// 
        private Tuple<string, object[]> CreateUpdateSQL(TEntity entity, params string[] fieldParams)
        {
            if (entity == null)
                throw new ArgumentException("The database entity can not be null.");
            List<string> pkList = GetPrimaryKey().Select(i => i.Name).ToList();

            Type entityType = entity.GetType();
            List<PropertyInfo> tableFields = new List<PropertyInfo>();
            if (fieldParams != null && fieldParams.Count() > 0)
            {
                tableFields = entityType.GetProperties().Where(i => fieldParams.Contains(i.Name, new StringComparisonIgnoreCase())).ToList();
            }
            else
            {
                tableFields = entityType.GetProperties().Where(i =>
                              !pkList.Contains(i.Name)
                              && i.GetValue(entity, null) != null
                              && !(i.PropertyType == typeof(ValueType) && Convert.ToInt64(i.GetValue(entity, null)) == 0)
                              && !(i.PropertyType == typeof(DateTime) && Convert.ToDateTime(i.GetValue(entity, null)) == DateTime.MinValue)
                              && i.PropertyType != typeof(EntityState)
                              && !(i.GetCustomAttributes(false).Length > 0
                              && i.GetCustomAttributes(false).Where(j => j.GetType() == typeof(NavigationAttribute)) != null)//过滤导航属性
                              && (i.PropertyType.IsValueType || i.PropertyType == typeof(string))
                               ).ToList();
            }




            //过滤主键,航行属性,状态属性等
            if (pkList == null || pkList.Count == 0)
                throw new ArgumentException("The Table entity have not a primary key.");
            List<object> arguments = new List<object>();
            StringBuilder builder = new StringBuilder();

            foreach (var change in tableFields)
            {
                if (pkList.Contains(change.Name))
                    continue;
                if (arguments.Count != 0)
                    builder.Append(", ");
                builder.Append(change.Name + " = {" + arguments.Count + "}");
                if (change.PropertyType == typeof(string)
                    || change.PropertyType == typeof(DateTime)
                    || change.PropertyType == typeof(Nullable<DateTime>))
                    arguments.Add("'" + change.GetValue(entity, null).ToString().Replace("'", "char(39)") + "'");
                else
                    arguments.Add(change.GetValue(entity, null));
            }

            if (builder.Length == 0)
                throw new Exception("没有任何属性进行更新");

            builder.Insert(0, " UPDATE " + string.Format("[{0}]", entityType.Name) + " SET ");

            builder.Append(" WHERE ");
            bool firstPrimaryKey = true;

            foreach (var primaryField in pkList)
            {
                if (firstPrimaryKey)
                    firstPrimaryKey = false;
                else
                    builder.Append(" AND ");

                object val = entityType.GetProperty(primaryField).GetValue(entity, null);
                Type pkType = entityType.GetProperty(primaryField).GetType();
                builder.Append(GetEqualStatment(primaryField, arguments.Count, pkType));
                arguments.Add(val);
            }
            return new Tuple<string, object[]>(builder.ToString(), arguments.ToArray());

        }

        /// 
        /// 构建Delete语句串
        /// 
        /// 
        /// 
        /// 
        private Tuple<string, object[]> CreateDeleteSQL(TEntity entity)
        {
            if (entity == null)
                throw new ArgumentException("The database entity can not be null.");

            Type entityType = entity.GetType();
            List<string> pkList = GetPrimaryKey().Select(i => i.Name).ToList();
            if (pkList == null || pkList.Count == 0)
                throw new ArgumentException("The Table entity have not a primary key.");

            List<object> arguments = new List<object>();
            StringBuilder builder = new StringBuilder();
            builder.Append(" Delete from " + string.Format("[{0}]", entityType.Name));

            builder.Append(" WHERE ");
            bool firstPrimaryKey = true;

            foreach (var primaryField in pkList)
            {
                if (firstPrimaryKey)
                    firstPrimaryKey = false;
                else
                    builder.Append(" AND ");

                Type pkType = entityType.GetProperty(primaryField).GetType();
                object val = entityType.GetProperty(primaryField).GetValue(entity, null);
                builder.Append(GetEqualStatment(primaryField, arguments.Count, pkType));
                arguments.Add(val);
            }
            return new Tuple<string, object[]>(builder.ToString(), arguments.ToArray());
        }

        /// 
        /// 构建Insert语句串
        /// 主键为自增时,如果主键值为0,我们将主键插入到SQL串中
        /// 
        /// 
        /// 
        /// 
        private Tuple<string, object[]> CreateInsertSQL(TEntity entity)
        {
            if (entity == null)
                throw new ArgumentException("The database entity can not be null.");

            Type entityType = entity.GetType();
            var table = entityType.GetProperties().Where(i => i.PropertyType != typeof(EntityKey)
                 && i.PropertyType != typeof(EntityState)
                 && i.Name != "IsValid"
                 && i.GetValue(entity, null) != null
                 && !(i.GetCustomAttributes(false).Length > 0
                 && i.GetCustomAttributes(false).Where(j => j.GetType() == typeof(NavigationAttribute)) != null)
                 && (i.PropertyType.IsValueType || i.PropertyType == typeof(string))).ToArray();//过滤主键,航行属性,状态属性等

            List<string> pkList = GetPrimaryKey().Select(i => i.Name).ToList();
            List<object> arguments = new List<object>();
            StringBuilder fieldbuilder = new StringBuilder();
            StringBuilder valuebuilder = new StringBuilder();

            fieldbuilder.Append(" INSERT INTO " + string.Format("[{0}]", entityType.Name) + " (");

            foreach (var member in table)
            {
                if (pkList.Contains(member.Name) && Convert.ToString(member.GetValue(entity, null)) == "0")
                    continue;
                object value = member.GetValue(entity, null);
                if (value != null)
                {
                    if (arguments.Count != 0)
                    {
                        fieldbuilder.Append(", ");
                        valuebuilder.Append(", ");
                    }

                    fieldbuilder.Append(member.Name);
                    if (member.PropertyType == typeof(string)
                        || member.PropertyType == typeof(DateTime)
                        || member.PropertyType == typeof(Nullable<DateTime>)
                        )
                        valuebuilder.Append("'{" + arguments.Count + "}'");
                    else
                        valuebuilder.Append("{" + arguments.Count + "}");
                    if (value.GetType() == typeof(string))
                        value = value.ToString().Replace("'", "char(39)");
                    arguments.Add(value);

                }
            }


            fieldbuilder.Append(") Values (");

            fieldbuilder.Append(valuebuilder.ToString());
            fieldbuilder.Append(");");
            return new Tuple<string, object[]>(fieldbuilder.ToString(), arguments.ToArray());
        }

        /// 
        /// /// 
        /// 执行SQL,根据SQL操作的类型
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        private string DoSQL(IEnumerable<TEntity> list, SQLType sqlType)
        {
            return DoSQL(list, sqlType, null);
        }
        /// 
        /// 执行SQL,根据SQL操作的类型
        /// 
        /// 
        /// 
        /// 
        /// 
        private string DoSQL(IEnumerable<TEntity> list, SQLType sqlType, params string[] fieldParams)
        {
            StringBuilder sqlstr = new StringBuilder();
            switch (sqlType)
            {
                case SQLType.Insert:
                    list.ToList().ForEach(i =>
                    {
                        Tuple<string, object[]> sql = CreateInsertSQL(i);
                        sqlstr.AppendFormat(sql.Item1, sql.Item2);
                    });
                    break;
                case SQLType.Update:
                    list.ToList().ForEach(i =>
                    {
                        Tuple<string, object[]> sql = CreateUpdateSQL(i, fieldParams);
                        sqlstr.AppendFormat(sql.Item1, sql.Item2);
                    });
                    break;
                case SQLType.Delete:
                    list.ToList().ForEach(i =>
                    {
                        Tuple<string, object[]> sql = CreateDeleteSQL(i);
                        sqlstr.AppendFormat(sql.Item1, sql.Item2);
                    });
                    break;
                default:
                    throw new ArgumentException("请输入正确的参数");
            }
            return sqlstr.ToString();
        }

        /// 
        /// SQL操作类型
        /// 
        protected enum SQLType
        {
            /// 
            /// 更新传入的实体代码去添加
            /// 
            Insert,
            /// 
            /// 根据传入的实体列表去更新
            /// 
            Update,
            /// 
            /// 根据传入的实体列表去删除
            /// 
            Delete,
        }
        #endregion
    }

下面看一下对于基础设施层所依赖的程序集
DDD(八)【基础设施层】_第2张图片
可以看到,它主要依赖于领域实体层与领域实体规约层。

OK,对于基础设施层的搭建就说到这,下回我们将说一下领域层的搭建。

你可能感兴趣的:(架构,领域驱动设计,DDD,领域驱动设计)