DDD~基础设施层

时间:2022-08-31 18:21:39

回到目录

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

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

目前的DDD项目结果如下

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
}

下面看一下对于基础设施层所依赖的程序集

DDD~基础设施层

可以看到,它主要依赖于领域实体层与领域实体规约层。

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

回到目录