Skip to main content

映射模块

映射模块是 Obase 的重要扩展机制,实现 IMappingModule 接口可以实现自定义的映射模块,订阅 Obase 内部的多个映射管道中的事件实现自定义的功能。

IMappingModule

/// <summary>
/// 映射模块
/// </summary>
public class MappingModule : IMappingModule
{
/// <summary>初始化映射模块。</summary>
/// <param name="savingPipeline">"保存"管道。</param>
/// <param name="deletingPipeline">"删除"管道。</param>
/// <param name="queryPipeline">"查询"管道。</param>
/// <param name="directlyChangingPipeline">"就地修改"管道。</param>
public void Init(ISavingPipeline savingPipeline, IDeletingPipeline deletingPipeline, IQueryPipeline queryPipeline,
IDirectlyChangingPipeline directlyChangingPipeline)
{
queryPipeline.PostExecuteCommand += QueryPipelineOnPostExecuteCommand;
}

/// <summary>
/// 处理PostExecuteCommand
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void QueryPipelineOnPostExecuteCommand(object sender, PostExecuteCommandEventArgs e)
{
//查询的PostExecuteCommand传来的Command为QuerySql
Console.WriteLine(((QuerySql)e.Command).ToSql(eDataSource.MySql));
}
}

在对象上下文配置提供者中如下配置即可

/// <summary>获取映射模块。</summary>
/// <returns>如果要扩展映射管道时,返回映射模块序列;否则返回null。</returns>
protected override IMappingModule[] GetMappingModules()
{
return new IMappingModule[] { new MappingModule() };
}

上面代码演示如何定义映射模块,继承 IMappingModule 接口后在 Init 方法中订阅要处理的映射管道事件即可。这里我们讲一下这些映射管道的具体事件是在何时被触发的,以及他们触发时会一并抛出的事件数据。

CURD 管道

/// <summary>
/// “保存”管道接口。
/// </summary>
public interface ISavingPipeline
{
/// <summary>
/// 为PreExecuteSql事件附加或移除事件处理程序。
/// </summary>
event EventHandler<PreExecuteCommandEventArgs> PreExecuteCommand;

/// <summary>
/// 为PostExecuteSql事件附加或移除事件处理程序。
/// </summary>
event EventHandler<PostExecuteCommandEventArgs> PostExecuteCommand;

/// <summary>
/// 为BeginSaving事件附加或移除事件处理程序。
/// </summary>
event EventHandler BeginSaving;

/// <summary>
/// 为PostGenerateQueue事件附加或移除事件处理程序。
/// </summary>
event EventHandler PostGenerateQueue;

/// <summary>
/// 为BeginSavingUnit事件附加或移除事件处理程序。
/// </summary>
event EventHandler<BeginSavingUnitEventArgs> BeginSavingUnit;

/// <summary>
/// 为EndSavingUnit事件附加或移除事件处理程序。
/// </summary>
event EventHandler<EndSavingUnitEventArgs> EndSavingUnit;

/// <summary>
/// 为EndSaving事件附加或移除事件处理程序。
/// </summary>
event EventHandler EndSaving;
}

/// <summary>
/// "删除"管道接口。
/// </summary>
public interface IDeletingPipeline
{
/// <summary>
/// 为PreExecuteSql事件附加或移除事件处理程序。
/// </summary>
event EventHandler<PreExecuteCommandEventArgs> PreExecuteCommand;

/// <summary>
/// 为PostExecuteSql事件附加或移除事件处理程序。
/// </summary>
event EventHandler<PostExecuteCommandEventArgs> PostExecuteCommand;

/// <summary>
/// 为BeginDeleting事件附加或移除事件处理程序。
/// </summary>
event EventHandler BeginDeleting;

/// <summary>
/// 为PostGenerateGroup事件附加或移除事件处理程序。
/// </summary>
event EventHandler PostGenerateGroup;

/// <summary>
/// 为BeginDeletingGroup事件附加或移除事件处理程序。
/// </summary>
event EventHandler<BeginDeletingGroupEventArgs> BeginDeletingGroup;

/// <summary>
/// 为EndDeletingGroup事件附加或移除事件处理程序。
/// </summary>
event EventHandler<EndDeletingGroupEventArgs> EndDeletingGroup;

/// <summary>
/// 为EndDeleting事件附加或移除事件处理程序。
/// </summary>
event EventHandler EndDeleting;
}

Obase 在保存对象时,会按照保存旧对象,删除对象和保存新对象的顺序来进行保存所有的对象,所以将保存管道和删除管道合并进行讲解。 Obase 的总体保存流程和触发事件顺序如下:

    1. 进入保存旧对象方法,并触发 ISavingPipeline.BeginSaving 事件。
    1. 根据要更新的对象生成包含数个更新映射的集合,循环集合对于每个更新映射都首先触发 ISavingPipeline.BeginSavingUnit,而后执行保存逻辑,并且在提交实际操作持久层命令前后依次触发 ISavingPipeline.PreExecuteCommand 和 ISavingPipeline.PostExecuteCommand,最后触发 ISavingPipeline.EndSavingUnit。
    1. 结束循环,结束保存旧对象方法。
    1. 进入删除方法,触发 IDeletingPipeline.BeginDeleting 事件。
    1. 对要删除的对象分组,并触发 IDeletingPipeline.PostGenerateGroup 事件。
    1. 循环分组后的对象,对于每个对象都首先触发 IDeletingPipeline.BeginDeletingGroup,而后执行删除逻辑,并在提交实际操作持久层命令前后依次触发 IDeletingPipeline.PreExecuteCommand 和 IDeletingPipeline.PostExecuteCommand,最后触发 IDeletingPipeline.EndDeletingGroup。
    1. 结束循环,触发 IDeletingPipeline.EndDeleting 事件。
    1. 进入保存新对象方法,根据要保存的新对象生成保存队列,并触发 ISavingPipeline.PostGenerateQueue 事件。
    1. 根据要新增的对象生成包含数个新增映射的集合,循环集合对于每个更新映射都首先触发 ISavingPipeline.BeginSavingUnit,而后执行保存逻辑,并且在提交实际操作持久层命令前后依次触发 ISavingPipeline.PreExecuteCommand 和 ISavingPipeline.PostExecuteCommand,最后触发 ISavingPipeline.EndSavingUnit。
    1. 结束循环,触发 ISavingPipeline.EndSaving 事件,结束保存流程。

其中,BeginSavingUnit 和 EndSavingUnit 事件触发时,会抛出事件数据,其定义如下:

/// <summary>
/// 与映射单元相关的事件的数据类。
/// </summary>
public abstract class MappingUnitEventArgs : EventArgs
{
/// <summary>
/// 映射单元主对象状态
/// </summary>
private readonly eObjectStatus _hostObjectStatus;

/// <summary>
/// 映射单元。
/// </summary>
private readonly MappingUnit _mappingUnit;


/// <summary>
/// 创建MappingUnitEventArgs实例,并指定映射单元。
/// </summary>
/// <param name="mappingUnit">映射单元。</param>
/// <param name="hostObjectStatus">映射单元主对象状态</param>
protected MappingUnitEventArgs(MappingUnit mappingUnit, eObjectStatus hostObjectStatus)
{
_mappingUnit = mappingUnit;
_hostObjectStatus = hostObjectStatus;
}

/// <summary>
/// 获取映射单元。
/// </summary>
public MappingUnit MappingUnit => _mappingUnit;

/// <summary>
/// 映射单元主对象状态
/// </summary>
public eObjectStatus HostObjectStatus => _hostObjectStatus;
}

/// <summary>
/// 开始保存单元事件数据类
/// </summary>
public class BeginSavingUnitEventArgs : MappingUnitEventArgs
{
/// <summary>
/// 创建BeginSavingUnitEventArgs实例,并指定要保存的映射单元。
/// </summary>
/// <param name="mappingUnit">映射单元。</param>
/// <param name="hostObjectStatus">映射单元主对象状态</param>
public BeginSavingUnitEventArgs(MappingUnit mappingUnit, eObjectStatus hostObjectStatus) : base(mappingUnit,
hostObjectStatus)
{
}
}


/// <summary>
/// 结束保存单元事件数据类
/// </summary>
public class EndSavingUnitEventArgs : MappingUnitEventArgs
{
/// <summary>
/// 保存过程中发生的异常,如果执行成功则值为NULL。
/// </summary>
private readonly Exception _exception;


/// <summary>
/// 创建EndSavingUnitEventArgs实例,并指定尝试保存的映射单元和执行过程中发生的异常。
/// </summary>
/// <param name="mappingUnit">要保存的映射单元。</param>
/// <param name="hostObjectStatus">映射单元主对象状态</param>
/// <param name="exception">异常。</param>
public EndSavingUnitEventArgs(MappingUnit mappingUnit, eObjectStatus hostObjectStatus,
Exception exception = null) : base(mappingUnit, hostObjectStatus)
{
_exception = exception;
}

/// <summary>
/// 获取保存过程中发生的异常,如果执行成功则值为NULL。
/// </summary>
public Exception Exception => _exception;

/// <summary>
/// 获取一个值,该值指示保存操作是否执行失败。
/// </summary>
public bool Failed => _exception != null;
}

IQueryPipeline

接下来介绍 Obase 查询的流程和 IQueryPipeline 的事件触发。

/// <summary>
/// 查询管道接口。
/// </summary>
public interface IQueryPipeline
{
/// <summary>
/// 为PreExecuteSql事件附加或移除事件处理程序。
/// </summary>
event EventHandler<PreExecuteCommandEventArgs> PreExecuteCommand;

/// <summary>
/// 为PostExecuteSql事件附加或移除事件处理程序。
/// </summary>
event EventHandler<PostExecuteCommandEventArgs> PostExecuteCommand;

/// <summary>
/// 为BeginQuery事件附加或移除事件处理程序。
/// </summary>
event EventHandler<BeginQueryEventArgs> BeginQuery;

/// <summary>
/// 为EndQuery事件附加或移除事件处理程序。
/// </summary>
event EventHandler EndQuery;
}

Obase 在获得传入的查询表达式后,会按照如下流程进行查询:

    1. 触发 IQueryPipeline.BeginQuery 事件。
    1. 解析表达式,生成查询链,最终转换为操作持久化层的命令。并且在提交实际操作持久层命令前后依次触发 IQueryPipeline.PreExecuteCommand 和 IQueryPipeline.PostExecuteCommand 事件。
    1. 结束查询,触发 IQueryPipeline.EndQuery 事件。

其中,BeginQuery 时会抛出事件数据,其定义如下:

/// <summary>
/// BeginQuery事件数据类。
/// </summary>
public class BeginQueryEventArgs : EventArgs
{
/// <summary>
/// 查询表达式。
/// </summary>
private readonly Expression _expression;


/// <summary>
/// 创建BeginQueryEventArgs实例。
/// </summary>
/// <param name="expression">查询表达式。</param>
public BeginQueryEventArgs(Expression expression)
{
_expression = expression;
}

/// <summary>
/// 获取查询表达式。
/// </summary>
public Expression Expression => _expression;
}

最后是 IDirectlyChangingPipeline 直接修改管道,此管道内事件会在直接修改方法(Delete,SetAttributes,IncreaseAttributes)中触发,以下时此管道的定义:

/// <summary>
/// “就地修改”管道接口。
/// “就地修改”是指直接在数据库中修改符合条件的对象,而不是先将对象载入缓存、修改后再写回数据库。包含更改对象属性和删除对象。
/// </summary>
public interface IDirectlyChangingPipeline
{
/// <summary>
/// 为PreExecuteSql事件附加或移除事件处理程序。
/// </summary>
event EventHandler<PreExecuteCommandEventArgs> PreExecuteCommand;

/// <summary>
/// 为PostExecuteSql事件附加或移除事件处理程序。
/// </summary>
event EventHandler<PostExecuteCommandEventArgs> PostExecuteCommand;

/// <summary>
/// 为BeginDirectlyChanging事件附加或移除事件处理程序。
/// </summary>
event EventHandler<BeginDirectlyChangingEventArgs> BeginDirectlyChanging;

/// <summary>
/// 为EndDirectlyChanging事件附加或移除事件处理程序。
/// </summary>
event EventHandler<EndDirectlyChangingEventArgs> EndDirectlyChanging;
}

在直接修改方法(Delete,SetAttributes,IncreaseAttributes 中会按照如下顺序触发事件:

    1. 进入直接修改方法,触发 IDirectlyChangingPipeline.BeginDirectlyChanging 事件。
    1. 生成具体操作语句,并且在提交实际操作持久层命令前后依次触发 IDirectlyChangingPipeline.PreExecuteCommand 和 IDirectlyChangingPipeline.PostExecuteCommand 事件。
    1. 提交完成后,触发 IDirectlyChangingPipeline.EndDirectlyChanging 事件。

其中 BeginDirectlyChanging 和 EndDirectlyChanging 事件会传输如下的事件数据:

/// <summary>
/// 与就地修改相关的事件的数据类。
/// </summary>
public abstract class DirectlyChangingEventArgs : EventArgs
{
/// <summary>
/// 修改类型。
/// </summary>
private readonly eDirectlyChangeType _changeType;

/// <summary>
/// 条件表达式。
/// </summary>
private readonly Expression _expression;

/// <summary>
/// 存储属性新值的字典,键为属性名称,值为属性的新值。
/// </summary>
private readonly KeyValuePair<string, object>[] _newValues;

/// <summary>
/// 修改的对象类型
/// </summary>
private readonly Type _type;

/// <summary>
/// 获取条件表达式。
/// </summary>
public Expression Expression => _expression;

/// <summary>
/// 获取修改类型。
/// </summary>
public eDirectlyChangeType ChangeType => _changeType;

/// <summary>
/// 获取存储属性新值的字典,键为属性名称,值为属性的新值。
/// </summary>
public KeyValuePair<string, object>[] NewValues => _newValues;

/// <summary>
/// 修改的对象类型
/// </summary>
public Type Type => _type;

/// <summary>
/// 创建DirectlyChangingEventArgs实例,并指定条件表达式和属性新值字典。
/// </summary>
/// <param name="expression">条件表达式。</param>
/// <param name="changeType">修改类型</param>
/// <param name="objectType">修改的对象类型</param>
/// <param name="newValues">属性新值字典。</param>
protected DirectlyChangingEventArgs(Expression expression, eDirectlyChangeType changeType, Type objectType,
KeyValuePair<string, object>[] newValues = null)
{
_expression = expression;
_changeType = changeType;
_type = objectType;
_newValues = newValues;
}
}

/// <summary>
/// BeginDirectlyChanging事件数据类。
/// </summary>
public class BeginDirectlyChangingEventArgs : DirectlyChangingEventArgs
{
/// <summary>
/// 创建BeginDirectlyChangingEventArgs实例,并指定条件表达式和属性新值字典。
/// </summary>
/// <param name="expression">条件表达式。</param>
/// <param name="changeType">更改类型</param>
/// <param name="objectType">修改的对象类型</param>
/// <param name="attributes">属性新值字典。</param>
public BeginDirectlyChangingEventArgs(Expression expression, eDirectlyChangeType changeType, Type objectType,
KeyValuePair<string, object>[] attributes = null) : base(expression, changeType, objectType, attributes)
{
}
}

/// <summary>
/// 结束就地修改事件数据类
/// </summary>
public class EndDirectlyChangingEventArgs : DirectlyChangingEventArgs
{
/// <summary>
/// 影响的行数
/// </summary>
private readonly int _affectedCount;

/// <summary>
/// 执行过程中发生的异常,如果执行成功则值为NULL。
/// </summary>
private readonly Exception _exception;


/// <summary>
/// 创建EndDirectlyChangingEventArgs实例,并指定过滤条件表达式和执行过程中发生的异常。
/// </summary>
/// <param name="expression">条件表达式。</param>
/// <param name="changeType">修改类型</param>
/// <param name="objectType">修改的对象类型</param>
/// <param name="affectedCount">影响行数</param>
/// <param name="newValues">属性新值字典。</param>
/// <param name="exception">执行过程中发生的异常。</param>
public EndDirectlyChangingEventArgs(Expression expression, eDirectlyChangeType changeType, Type objectType,
int affectedCount,
KeyValuePair<string, object>[] newValues = null, Exception exception = null) : base(expression, changeType,
objectType,
newValues)
{
_affectedCount = affectedCount;
_exception = exception;
}


/// <summary>
/// 获取执行过程中发生的异常,如果执行成功则值为NULL。
/// </summary>
public Exception Exception => _exception;

/// <summary>
/// 获取一个值,该值指示执行过程中是否发生了异常。
/// </summary>
public bool Failed => _exception != null;

/// <summary>
/// 影响的行数
/// </summary>
public int AffectedCount => _affectedCount;
}

此外,四条映射管道都的 PreExecuteCommand 和 PostExecuteCommand 均会抛出如下定义的事件数据:

    /// <summary>
/// PreExecuteCommandEventArgs事件的数据类。
/// </summary>
public class PreExecuteCommandEventArgs : EventArgs
{
/// <summary>
/// 要执行的存储指令(如Sql语句)。
/// </summary>
private readonly object _command;

/// <summary>
/// 在查询管道中,表示查询表达式;对于其它管道,该属性为NULL。
/// </summary>
private Expression _expression;


/// <summary>
/// 创建PreExecuteCommandEventArgs实例。
/// </summary>
/// <param name="command">要执行的存储指令(如Sql语句)。</param>
public PreExecuteCommandEventArgs(object command)
{
_command = command;
}

/// <summary>
/// 在查询管道中,获取或设置查询表达式;对于其它管道,该属性为NULL。
/// </summary>
public Expression Expression
{
get => _expression;
set => _expression = value;
}

/// <summary>
/// 获取要执行的Sql语句。
/// </summary>
/// <summary>
/// 获取要执行的存储指令(如Sql语句)。
/// </summary>
public object Command => _command;
}

/// <summary>
/// PostExecuteSql事件数据类。
/// </summary>
public class PostExecuteCommandEventArgs : EventArgs
{
/// <summary>
/// 受影响的行数。
/// </summary>
private readonly int _affectedCount;

/// <summary>
/// 要执行的存储指令(如Sql语句)。
/// </summary>
private readonly object _command;

/// <summary>
/// 执行Sql语句的过程中发生的异常,未发生异常则为Null。
/// </summary>
private readonly Exception _exception;

/// <summary>
/// 执行Sql语句所消耗的时间,以毫秒为单位。
/// </summary>
private readonly int _timeConsumed;

/// <summary>
/// 在查询管道中,表示查询表达式;对于其它管道,该属性为NULL。
/// </summary>
private Expression _expression;

/// <summary>
/// 创建PostExecuteSqlEventArgs实例,并指定要执行的存储指令(如Sql语句)和执行消耗的时间。
/// </summary>
/// <param name="command">要执行的存储指令(如Sql语句)。</param>
/// <param name="timeConsumed">执行指令所消耗的时间,以毫秒为单位。</param>
/// <param name="affectedCount">受影响的行数。</param>
public PostExecuteCommandEventArgs(object command, int timeConsumed, int affectedCount)
{
_command = command;
_timeConsumed = timeConsumed;
_affectedCount = affectedCount;
}

/// <summary>
/// 创建PostExecuteSqlEventArgs实例,并指定要执行的存储指令(如Sql语句)、执行消耗的时间、以及执行过程中发生的异常。
/// </summary>
/// <param name="command">要执行的存储指令(如Sql语句)。</param>
/// <param name="timeConsumed">执行指令所消耗的时间,以毫秒为单位。</param>
/// <param name="exception">执行指令过程中发生的异常</param>
public PostExecuteCommandEventArgs(object command, int timeConsumed, Exception exception)
{
_command = command;
_timeConsumed = timeConsumed;
_exception = exception;
}

/// <summary>
/// 获取执行Sql语句所消耗的时间,以毫秒为单位。
/// </summary>
public int TimeConsumed => _timeConsumed;

/// <summary>
/// 获取执行Sql语句的过程中发生的异常,未发生异常则为Null。
/// </summary>
public Exception Exception => _exception;

/// <summary>
/// 在查询管道中,获取或设置查询表达式;对于其它管道,该属性为NULL。
/// </summary>
public Expression Expression
{
get => _expression;
set => _expression = value;
}

/// <summary>
/// 获取受影响的行数。
/// </summary>
public int AffectedCount => _affectedCount;

/// <summary>
/// 获取要执行的存储指令(如Sql语句)。
/// </summary>
public object Command => _command;
}

应用实例

在模拟学生入学场景中,我们需要确保在将学生信息存入数据库时,将入学年份自动设为当前年份。以下是相应的代码示例。

实体模型

/// <summary>
///学生
/// </summary>
public class Student
{
/// <summary>
///入学年
/// </summary>
private int _addmisionYear;

/// <summary>
///学生姓名
/// </summary>
private string _name;


/// <summary>
///学生ID
/// </summary>
private string _studentId;

/// <summary>
///学生ID
/// </summary>
public string StudentId
{
get => _studentId;
set => _studentId = value;
}

/// <summary>
///学生姓名
/// </summary>
public string Name
{
get => _name;
set => _name = value;
}

/// <summary>
///入学年
/// </summary>
public int AddmisionYear
{
get => _addmisionYear;
set => _addmisionYear = value;
}
}

映射模块

 /// <summary>
///入学时间映射模块
/// </summary>
public class AddmisionYearMappingModule : IMappingModule
{
/// <summary>初始化映射模块。</summary>
/// <param name="savingPipeline">"保存"管道。</param>
/// <param name="deletingPipeline">"删除"管道。</param>
/// <param name="queryPipeline">"查询"管道。</param>
/// <param name="directlyChangingPipeline">"就地修改"管道。</param>
/// <param name="objectContext">对象上下文</param>
public void Init(ISavingPipeline savingPipeline, IDeletingPipeline deletingPipeline,
IQueryPipeline queryPipeline,
IDirectlyChangingPipeline directlyChangingPipeline, ObjectContext objectContext)
{
savingPipeline.BeginSavingUnit += SavingPipelineOnBeginSavingUnit;
}

/// <summary>
///订阅保存管道 开始保存单元事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SavingPipelineOnBeginSavingUnit(object sender, BeginSavingUnitEventArgs e)
{
//作为宿主对象的Student
if (e.MappingUnit.HostObject is Student student) student.AddmisionYear = DateTime.Now.Year;
//作为参与映射对象的Student
if (e.MappingUnit.MappingObjects?.Count > 0)
foreach (var mappingObject in e.MappingUnit.MappingObjects)
if (mappingObject is Student mappingStudent)
mappingStudent.AddmisionYear = DateTime.Now.Year;
}
}

代码示例

    var context = new MappingPipelineContext();
//为此上下文对下注册AddmisionYearMappingModule 此模块的作用是将AddmisionYear改为当年
//此方法也可以在MappingPipelineContext的构造函数内调用 令所有构造的上下文对象都启用此模块
context.RegisterModule(new AddmisionYearMappingModule());

//初始化六个学生
for (var i = 0; i < 6; i++)
{
var student = new Student { Name = $"{i + 1}号学生", StudentId = $"{i + 1}", AddmisionYear = i };
context.Attach(student);
}
context.SaveChanges();
//此时所有的入学时间都是DateTime.Now.Year
var queryStudents = context.CreateSet<Student>().ToList();
//验证一下
Assert.IsTrue(queryStudents.All(p => p.AddmisionYear == DateTime.Now.Year));