最近被DDD吸引了阿,在這里感謝一下小佟,呵呵,領域驅動設計是個不錯的東西,幫助我們把問題清晰化,這候對於復雜業務邏輯是很重要的,今天這一講主要說一下DDD中的基礎設施層(Infrastructure)是如何被我實現的。
Infrastructure Layer:主要功能是對領域模塊進行持久化的,在這個層中你需要把領域對象序列化到指定的元件中,可能是數據庫,文件或者內存對象,當然它也要提供從物理元件取出數據到領域模型的功能,這是對應的。
目前的DDD項目結果如下
對於Infrastructure這個層我不去定義接口而是讓它去實現Domain層的接口,即一切從領域出發,而Infrastructure只負責具體的數據持久化工作,下面我們主要介紹一下IRepository.cs和IExtensionRepository.cs在Infrastructure層是如何被實現的。
與傳統DATA層的區別
一 傳統Data層定義接口,為數據為導向,而不是以業務為導。
二 將Repository的方法中添加了領域規約,使它Infrastructure更有目的的去實現。
引入工作單元,使多方法形式一個事務的概念
IUnitOfWork接口也是在domain層實現的,不過在這里我們也介紹一下它的代碼
public interface IUnitOfWork { /// <summary> /// 將操作提交到數據庫, /// </summary> void Save(); /// <summary> /// 是否需要顯示進行提交(save()) /// 默認為false,即在repository方法中自動完成提交,值為true時,表示需要顯示調用save()方法 /// </summary> /// <returns></returns> 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 /// <summary> /// 數據上下文 /// </summary> protected DbContext _Db { get; private set; } /// <summary> /// 工作單元上下文,子類可以直接使用它 /// </summary> protected IUnitOfWork iUnitWork { get; set; } /// <summary> /// Action委托事例,在派生類可以操作它 /// </summary> protected Action<string> Logger { get; private set; } /// <summary> /// 得到上下文中表對象的所有記錄數 /// 當數據表記錄在千萬以上時,select count(1) from table /// 的速度會受到影響,所以擴展一個方法解決這個問題 /// </summary> /// <param name="queryable"></param> /// <param name="db"></param> /// <returns></returns> 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 /// <summary> /// 數據總數 /// </summary> int DataTotalCount = 0; /// <summary> /// 數據總頁數 /// </summary> int DataTotalPages = 0; /// <summary> /// 數據頁面大小(每次向數據庫提交的記錄數) /// </summary> int DataPageSize = 10000; #endregion #region IRepository<T> 成員 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<TEntity>(); } #endregion #region IExtensionRepository<T> 成員 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 /// <summary> /// 根據工作單元的IsUnitOfWork的屬性,去判斷是否提交到數據庫 /// 一般地,在多個repository類型進行組合時,這個IsUnitOfWork都會設為true,即不馬上提交, /// 而對於單個repository操作來說,它的值不需要設置,使用默認的false,將直接提交到數據庫,這也保證了操作的原子性。 /// </summary> 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); } } /// <summary> /// 計數更新,與SaveChange()是兩個SQL鏈接,走分布式事務 /// 子類可以根據自己的邏輯,去復寫 /// tableName:表名 /// param:索引0為主鍵名,1表主鍵值,2為要計數的字段,3為增量 /// </summary> /// <param name="tableName">表名</param> /// <param name="param">參數列表,索引0為主鍵名,1表主鍵值,2為要計數的字段,3為增量</param> 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 /// <summary> /// Called after data saved /// </summary> /// <param name="sender">The sender.</param> /// <param name="action">The action.</param> protected virtual void OnAfterSaved(SavedEventArgs e) { if (AfterSaved != null) { AfterSaved(e); } } /// <summary> /// Called before saved /// </summary> /// <param name="sender">The sender.</param> /// <param name="action">The action.</param> protected virtual void OnBeforeSaved(SavedEventArgs e) { if (BeforeSaved != null) { BeforeSaved(e); } } #endregion #region Private Methods /// <summary> /// 分頁進行數據提交的邏輯 /// </summary> /// <param name="item">原列表</param> /// <param name="method">處理方法</param> /// <param name="currentItem">要進行處理的新列表</param> 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 + "}"; } /// <summary> /// 得到實體鍵EntityKey /// </summary> /// <typeparam name="TEntity"></typeparam> /// <returns></returns> protected ReadOnlyMetadataCollection<EdmMember> GetPrimaryKey() { EntitySetBase primaryKey = ((IObjectContextAdapter)_Db).ObjectContext.GetEntitySet(typeof(TEntity)); ReadOnlyMetadataCollection<EdmMember> arr = primaryKey.ElementType.KeyMembers; return arr; } /// <summary> /// 構建Update語句串 /// 注意:如果本方法過濾了int,decimal類型更新為0的列,如果希望更新它們需要指定FieldParams參數 /// </summary> /// <param name="entity">實體列表</param> /// <param name="fieldParams">要更新的字段</param> /// <returns></returns> 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()); } /// <summary> /// 構建Update語句串 /// 注意:如果本方法過濾了int,decimal類型更新為0的列,如果希望更新它們需要指定FieldParams參數 /// </summary> /// <param name="entity">實體列表</param> /// <param name="fieldParams">要更新的字段</param> /// <returns></returns> 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()); } /// <summary> /// 構建Delete語句串 /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="entity"></param> /// <returns></returns> 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()); } /// <summary> /// 構建Insert語句串 /// 主鍵為自增時,如果主鍵值為0,我們將主鍵插入到SQL串中 /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="entity"></param> /// <returns></returns> 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()); } /// <summary> /// /// <summary> /// 執行SQL,根據SQL操作的類型 /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="list"></param> /// <param name="sqlType"></param> /// <returns></returns> /// </summary> /// <param name="list"></param> /// <param name="sqlType"></param> /// <returns></returns> private string DoSQL(IEnumerable<TEntity> list, SQLType sqlType) { return DoSQL(list, sqlType, null); } /// <summary> /// 執行SQL,根據SQL操作的類型 /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="list"></param> /// <param name="sqlType"></param> /// <returns></returns> 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(); } /// <summary> /// SQL操作類型 /// </summary> protected enum SQLType { /// <summary> /// 更新傳入的實體代碼去添加 /// </summary> Insert, /// <summary> /// 根據傳入的實體列表去更新 /// </summary> Update, /// <summary> /// 根據傳入的實體列表去刪除 /// </summary> Delete, } #endregion }
下面看一下對於基礎設施層所依賴的程序集
可以看到,它主要依賴於領域實體層與領域實體規約層。
OK,對於基礎設施層的搭建就說到這,下回我們將說一下領域層的搭建。