obase社区Obase开发者QQ群:962698871
obase

欢迎查阅
OBASE帮助文档

之前在再攀高峰一节中定义过一个重写了所有可重写的属性和方法的上下文提供程序实例,本节我们逐一介绍其中重写的方法。

/// <summary>
///     重写所有可重写的Sql上下文配置提供程序
/// </summary>
public class FullSqlContextConfiger : SqlContexConfiger
{
    /// <summary>
    ///     获取特定于数据库服务器(SQL Server、Oracle等)的数据提供程序工厂
    /// </summary>
    protected override DbProviderFactory DbProviderFactory { get; }

    /// <summary>
    ///     连接字符串
    /// </summary>
    protected override string ConnectionString { get; }

    /// <summary>
    ///     获取一个值,该值指示是否自动创建对象集。
    /// </summary>
    protected override bool WhetherCreateSet { get; }

    /// <summary>
    ///     显式指示数据源的类型
    /// </summary>
    protected override eDataSource SourceType { get; }

    /// <summary>
    ///     使用指定的建模器创建对象数据模型。
    /// </summary>
    /// <param name="modelBuilder"></param>
    protected override void CreateModel(ModelBuilder modelBuilder)
    {
    }

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

    /// <summary>
    ///     获取用于发送对象变更通知的消息队列。
    /// </summary>
    /// <returns>如果要发送对象变更通知,返回一个消息队列实例;否则返回null。</returns>
    protected override IMessageQueue GetNoticeQueue()
    {
        return null;
    }
}
  • DbProviderFactory 重写此属性以指定使用的数据库连接工厂,此属性是必须重写的。
  • ConnectionString 重写此属性以指定使用的连接字符串,此属性是必须重写的。
  • WhetherCreateSet 重写此属性以指定是否为ObjectSet自动设置值,默认为True,如果为True且定义的ObjectSet无Set方法则会抛出异常,此属性是可选重写的。
  • SourceType 重写此属性指定数据源的类型,Obase会根据你使用的数据库连接工厂来推定此属性。如果你使用的连接工厂无法识别,则会抛出异常,此时就需要手动重写此属性。
  • CreateModel 重写此方法用于配置模型,此方法是可选重写的。
  • GetMappingModules 重写此方法返回你定义的映射模块实现特定的功能,此方法是可选重写的,在需要使用映射模块时重写即可。
  • GetNoticeQueue 重写此方法返回你定义的通知队列,此方法是可选重写的,在需要使用映射模块时重写即可。

在定义上下文一节中,我们提到ContextConfigProvider则是SQLContextConfigProvider的基类,目前定义的上下文都是从SQLContextConfigProvider,但我们也可以从ContextConfigProvider派生上下文配置提供者,以实现更复杂的扩展机制。

/// <summary>
///     继承于特定于功能的上下文配置提供者
/// </summary>
public class ObjectContextConfiger : ContextConfigProvider
{
    /// <summary>创建对象数据模型。</summary>
    /// <returns>
    ///     创建的对象数据模型。
    ///     实施说明
    ///     如果关联引用_model已初始化,直接返回其值。
    ///     否则,实例化一个ModelBuilder,然后调用CreateModel(ModelBuilder),最后调用ModelBuilder.
    ///     Build方法建造模型,并以建造的模型初始化关联引用_model。
    /// </returns>
    protected override ObjectDataModel CreateModel()
    {
        if (_model != null)
            return _model;

        var modelbuilder = new ModelBuilder();
        CreateModel(modelbuilder);
        _model = modelbuilder.Build();
        _model.DefaultStorageSymbol = StorageSymbols.Current.Redis;
        return _model;
    }

    /// <summary>
    /// 使用指定的建模器创建对象数据模型。
    /// </summary>
    /// <param name="modelBuilder"></param>
    protected override void CreateModel(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<JavaBean>().HasStorageSymbol(StorageSymbols.Current.Redis);
    }

    /// <summary>由派生类实现,获取指定存储标记对应的存储提供程序。</summary>
    /// <returns>存储提供程序。</returns>
    /// <param name="symbol"></param>
    protected override IStorageProvider GetStorageProvider(StorageSymbol symbol)
    {
        return null;
    }
}

这是一个继承自ContextConfigProvider的上下文配置提供者,可以实现针对不同类型数据持久化提供者的配置。 与之前相同的是,都可以重写CreateModel方法来注册模型,不过这里增加了一个无参的CreateModel,在这里给出了一个标准的写法,自定义时可以参考。 与我们之前定义的上下文提供者最大的不同是增加了一个新的可重写方法IStorageProvider GetStorageProvider(StorageSymbol symbol),这个方法会接受存储标记StorageSymbol来表示不同的存储提供者,这些标记是预定义的,可以在StorageSymbols.Current中找到。 你可以在CreateModel中为model设置默认的存储标记,也可以为某个实体型或关联型设置单独的存储标记,就像上面的代码中示例的一样。 在设置存储标记后,需要在GetStorageProvider(StorageSymbol symbol)中为需要的存储标记返回特定的存储提供程序IStorageProvider。 接下来我们就来以Obase.Sql讲解一下具体的存储提供程序如何实现,这里我们新建了一个类SqlStorageProvider来实现IStorageProvider,这个类中维护了一个Sql执行器来实际执行Sql数据库命令。

/// <summary>
///     准备存储资源,如打开数据库连接。
/// </summary>
void PrepareResource();

PrepareResource准备存储资源,这个方法里我们应该对实际的存储服务连接等资源进行准备,对于Sql类关系型数据库,可以是打开连接;对于内存缓存,可以是申请一个字典;甚至对于文件,可以是创建一个文件流。在Obase.Sql里,自然是OpenConnection打开数据库连接。

/// <summary>
///     释放存储资源,如关闭数据库连接。
/// </summary>
void ReleaseResource();

ReleaseResource释放存储资源,与准备相反,这里要释放掉之前的资源,对于Sql类关系型数据库,是关闭连接;对于内存缓存,是释放之前的字典;甚至对于文件,是Dispose文件流。在Obase.Sql里,自然是CloseConnection关闭数据库连接。

/// <summary>
///     启动一个新的映射工作流。
/// </summary>
/// <returns>一个用于跟踪工作流的对象,它实现了IMappingWorkflow接口。</returns>
IMappingWorkflow CreateMappingWorkflow();

这个方法是存储提供程序中保存部分的核心方法,Obase框架会跟踪对象修改并调用 IMappingWorkflow中的方法指示当前的实施持久化应进行的操作,这里我们应当自定义实现IMappingWorkflow的类并加以返回。 接下来我们来介绍IMappingWorkflow工作流机制,仍使用Obase.Sql为例。

/// <summary>
///     跟踪对象修改并实施持久化的工作流机制。
/// </summary>
public interface IMappingWorkflow
{
    /// <summary>
    ///     开始跟踪修改。
    ///     实施说明
    ///     须清空之前跟踪到的所有修改。
    /// </summary>
    void Begin();

    /// <summary>
    ///     接受本次工作流的存储源名称(如数据库表名)。
    /// </summary>
    /// <param name="targetSource"></param>
    IMappingWorkflow SetSource(string targetSource);

    /// <summary>
    ///     指示本次工作流将向存储源插入新对象。
    /// </summary>
    IMappingWorkflow ForInserting();

    /// <summary>
    ///     指示本次工作流将修改存储源中已有的对象。
    /// </summary>
    IMappingWorkflow ForUpdating();

    /// <summary>
    ///     指示本次工作流将删除存储源中的对象。
    /// </summary>
    IMappingWorkflow ForDeleting();

    /// <summary>
    ///     设置指定域(如数据库表的字段)的值。
    /// </summary>
    /// <param name="field"></param>
    /// <param name="value"></param>
    IMappingWorkflow SetField(string field, object value);

    /// <summary>
    ///     对指定域(如数据库表的字段)的值施加一个增量。
    /// </summary>
    /// <param name="field"></param>
    /// <param name="increment"></param>
    IMappingWorkflow IncreaseField(string field, object increment);

    /// <summary>
    ///     指示本次工作流应当忽略指定域(如数据库表的字段),如果已跟踪到了该域的修改,应当将其排除。
    /// </summary>
    /// <param name="field"></param>
    IMappingWorkflow IgnoreField(string field);

    /// <summary>
    ///     为当前工作流新增一个映射筛选器,该筛选器与已存在的筛选器进行逻辑“与”运算。
    /// </summary>
    /// <returns>新增的映射筛选器。</returns>
    MappingFilter And();

    /// <summary>
    ///     为当前工作流新增一个映射筛选器,该筛选器与已存在的筛选器进行逻辑“或”运算。
    /// </summary>
    /// <returns>新增的映射筛选器。</returns>
    MappingFilter Or();

    /// <summary>
    ///     级联删除,即从基点类型开始沿关联关系递归删除。实施者制定具体的级联规则。
    /// </summary>
    /// <param name="initType"></param>
    void DeleteCascade(ObjectType initType);

    /// <summary>
    ///     提交工作流。
    /// </summary>
    /// <param name="preexecutionCallback">一个委托,代表在执行存储指令(如SQL语句)前回调的方法。</param>
    /// <param name="postexecutionCallback">一个委托,代表在执行存储指令(如SQL语句)后回调的方法。</param>
    void Commit(Action<PreExecuteCommandEventArgs> preexecutionCallback,
        Action<PostExecuteCommandEventArgs> postexecutionCallback);

    /// <summary>
    ///     提交工作流。
    /// </summary>
    /// <param name="preexecutionCallback">一个委托,代表在执行存储指令(如SQL语句)前回调的方法。</param>
    /// <param name="postexecutionCallback">一个委托,代表在执行存储指令(如SQL语句)后回调的方法。</param>
    /// <param name="identity">返回存储服务为新对象生成的标识。</param>
    void Commit(Action<PreExecuteCommandEventArgs> preexecutionCallback,
        Action<PostExecuteCommandEventArgs> postexecutionCallback, out object identity);
}

以上就是IMappingWorkflow的定义,我们定义了一个SqlMappingWorkflow来实现针对Sql的工作流,SqlMappingWorkflow的目的是最终生成Sql语句来操作Sql数据库,这个类内部维护了用于表示Sql语句的对象,并根据工作流中方法被调用而修改Sql语句对象,接下来以SqlMappingWorkflow为例逐一介绍工作流的方法(这里不展开介绍Sql语句对象,其可以理解为一个字符构造器,根据为他设置的语句类型,源,条件等部分最终生成Sql语句字符串)。

  • 开始跟踪修改Begin方法,此方法应开启一个新的工作流,并将之前的工作流内部维护的结果清空,对于SqlMappingWorkflow可以视作将Sql语句对象恢复为空白。
  • 设置源SetSource(string targetSource)方法,此方法应针对要拓展的存储持久化层生成为其指定源名称的部分,对于Sql语句来说就是指定表名。
  • ForInserting,ForUpdating,ForDeleting是设置当前工作流的类型,分别是设置为插入,更新,删除。对于Sql语句来说,就是设置是Insert,Update还是Delete语句。
  • SetField是设置某个字段的值,对于当前工作流,应覆盖之前设置的值。对于Sql语句来说,就是为字段设值,生成形如[ Filed]=Value的语句。
  • IncreaseField同SetField一样是覆盖之前设置的字段值,但不同的是,此方法是使用增量的方式设置此值。对于Sql语句来说,是生成形如[ Filed]=[ Filed]+value的语句。
  • IgnoreField则是将当前工作流中的某个字段排除,如果之前为这个字段设置过值,也应从字段列表内移除。对于Sql语句,就是去掉此此段。
  • And和Or方法,他们均要求返回MappingFilter,MappingFilter类已由Obase框架实现,但需要用户自定义FilterReady和SegmentReady委托来进行构造。其中FilterReady是一个代表映射筛选器制作完成时回调的方法,SegmentReady是代表映射筛选器片段制作完成时回调的方法。对于Sql语句而言,FilterReady应该是在最终构件的语句中的条件部分设值,SegmentReady则是在自己实现的工作流内的片段寄存器中暂存值,并在FilterReady方法内调用此寄存器取出其中的值为在最终构件的语句中的条件部分设值。
  • DeleteCascade是级联删除方法,此方法应根据传入的ObjectType查找他的具体类型(关联型或实体型),如果是关联型,则沿着他的关联端进行标记删除;对于实体型,应沿着关联引用查找关联型后沿着他的关联端进行标记删除;最后,将所有标记删除的目标生成用于操作命令。对于Sql语句来说,就是根据这些要删除的对象,生成一系列的条件语句。
  • Commit及他的重载,这里两个方法均是进行最终操作持久化层的语句进行提交,并对传入的委托进行调用即可,其中带有out参数的方法是需要返回新插入的ID值的,此方法仅会在主键自增时被调用。注意,这里的两个委托和映射管道有关,必须调用其Invoke方法,否则会影响映射管道的功能。对于Sql来说,就是将生成的语句字符串传入执行器,如果主键自增,则使用select last_insert_rowid()或者select @@identity方法返回新插入的主键值。

接下来,我们回到IStorageProvider,继续介绍其中的方法。

/// <summary>
///     删除符合指定条件的对象。
/// </summary>
/// <param name="objType">要删除的对象的类型。</param>
/// <param name="filterExpression">用于测试对象是否符合条件的断言函数。</param>
/// <param name="preexecutionCallback">一个委托,代表在执行存储指令(如SQL语句)前回调的方法。</param>
/// <param name="postexecutionCallback">一个委托,代表在执行存储指令(如SQL语句)后回调的方法。</param>
int Delete(ObjectType objType, LambdaExpression filterExpression,
    Action<PreExecuteCommandEventArgs> preexecutionCallback,
    Action<PostExecuteCommandEventArgs> postexecutionCallback);

此方法即是直接修改方法Delete在存储提供程序中的具体调用,此方法实现时应根据传入的ObjectType获取要操作的源,并解析LambdaExpression获得条件,生成最终用于操作持久化层的命令,并在执行命令前后调用preexecutionCallback和postexecutionCallback委托。注意,这里的两个委托和映射管道有关,必须调用其Invoke方法,否则会影响应设管道的功能。对于Sql来说,就是根据ObjectType查找表名,将LambdaExpression生成Where后的条件,最终生成Delete语句。

/// <summary>
///     搜索符合指定条件的对象,为其属性(部分或全部)设置新值。
/// </summary>
/// <param name="objType">要修改其属性的对象的类型。</param>
/// <param name="filterExpression">用于测试对象是否符合条件的断言函数。</param>
/// <param name="newValues">存储属性新值的字典。</param>
/// <param name="preexecutionCallback">一个委托,代表在执行存储指令(如SQL语句)前回调的方法。</param>
/// <param name="postexecutionCallback">一个委托,代表在执行存储指令(如SQL语句)后回调的方法。</param>
int SetAttributes(ObjectType objType, LambdaExpression filterExpression,
    KeyValuePair<string, object>[] newValues,
    Action<PreExecuteCommandEventArgs> preexecutionCallback,
    Action<PostExecuteCommandEventArgs> postexecutionCallback);

此方法即是直接修改方法SetAttributes在存储提供程序中的具体调用,此方法实现时应根据传入的ObjectType获取要操作的源,并解析LambdaExpression获得条件,根据KeyValuePair< string, object>[]生成要设值的字段,最终用于操作持久化层的命令,并在执行命令前后调用preexecutionCallback和postexecutionCallback委托。注意,这里的两个委托和映射管道有关,必须调用其Invoke方法,否则会影响应设管道的功能。对于Sql来说,就是根据ObjectType查找表名,将LambdaExpression生成Where后的条件,根据KeyValuePair< string, object>[] 生成[ Filed]=Value最终生成Update语句。

/// <summary>
///     搜索符合指定条件的对象,为其属性(部分或全部)施加一个增量。
/// </summary>
/// <param name="objType">要修改其属性的对象的类型。</param>
/// <param name="filterExpression">用于测试对象是否符合条件的断言函数。</param>
/// <param name="increaseValues">存储增量值的字典。</param>
/// <param name="preexecutionCallback">一个委托,代表在执行存储指令(如SQL语句)前回调的方法。</param>
/// <param name="postexecutionCallback">一个委托,代表在执行存储指令(如SQL语句)后回调的方法。</param>
int IncreaseAttributes(ObjectType objType, LambdaExpression filterExpression,
    KeyValuePair<string, object>[] increaseValues, Action<PreExecuteCommandEventArgs> preexecutionCallback,
    Action<PostExecuteCommandEventArgs> postexecutionCallback);

此方法即是直接修改方法IncreaseAttributes在存储提供程序中的具体调用,此方法实现时应根据传入的ObjectType获取要操作的源,并解析LambdaExpression获得条件,根据KeyValuePair< string, object>[]生成要增量设值的字段,最终用于操作持久化层的命令,并在执行命令前后调用preexecutionCallback和postexecutionCallback委托。注意,这里的两个委托和映射管道有关,必须调用其Invoke方法,否则会影响映射管道的功能。对于Sql来说,就是根据ObjectType查找表名,将LambdaExpression生成Where后的条件,根据KeyValuePair< string, object>[ ]生成[ Filed]=[ Filed] + Value最终生成Update语句。

/// <summary>
///     为指定的查询生成运算管道。
/// </summary>
/// <param name="query">要执行的查询。</param>
/// <param
///     name="complement">
///     返回补充查询,即生成运算管道后剩余的一段查询链,该部分查询存储服务无法执行须以对象运算补充执行,简称补充查询或补充链。
/// </param>
OpExecutor GeneratePipeline(QueryOp query, out QueryOp complement);

此方法是Obase生成的查询在持久化层的具体调用,这里的传入参数QueryOp是经过Obase框架处理后的查询链。 查询链就是将外部的Linq方法的调用处理后的链式结构,每个QueryOp均有Next访问器指示他的下一个查询,如果Next为null,则表示此查询链终结。Obase内部定义了诸多不同类型的QueryOp用以描述不同的操作,并存储此操作的参数等信息.以形如Student.Skip(2).Take(3)查询为例,他的QueryOp就是SkipOp { Next = TakeOp { Next = null }。 在此方法内,我们应将查询链进行处理,处理为OpExecutor并返回补充运算QueryOp complement. OpExecutor是一个类似于查询链的结构,可以称之为执行器管道或者执行器链。每个OpExecutor均有Next访问器指示他的下一个执行器,但表示此执行器管道终结时,建议使用一种特殊的执行器来表示。 补充运算QueryOp complement则是针对那些持久化层不能直接给出结果的运算时,应在主运算完成后调用的补充运算。 对于Sql来说,OpExecutor可以分为Rop和Oop,即可以在关系型数据库内完成的和只能通过对象运算完成的,在Obase.Sql中,我们定义了RopExecutor来表示这可以在关系型数据库内完成的部分,并最终构造用于执行的Sql字符串。

/// <summary>
///     执行运算管道。
/// </summary>
/// <param name="pipeline">要执行的运算管道。</param>
/// <param name="including">指定由运算管道加载的对象须包含的引用,必须是同构的。</param>
/// <param name="attachObject">用于在对象上下文中附加对象的委托</param>
/// <param name="preexecutionCallback">一个委托,代表在执行存储指令(如SQL语句)前回调的方法。</param>
/// <param name="postexecutionCallback">一个委托,代表在执行存储指令(如SQL语句)后回调的方法。</param>
object ExecutePipeline(OpExecutor pipeline, AssociationTree including,AttachObject attachObject,
    Action<PreExecuteCommandEventArgs> preexecutionCallback,
    Action<PostExecuteCommandEventArgs> postexecutionCallback);

此方法是在查询时的具体执行方法,在此方法中我们应当先针对AssociationTree来解析包含树中要求包含的对象,使用适当的方式在此次查询中要求一并查询这些对象;之后,调用pipeline.Execute()方法获得用于操作持久化层的命令;而后在具体向久化层发出命令的前后调用preexecutionCallback和postexecutionCallback委托。注意,这里的委托必须进行调用,否则会影响映射管道的功能。最后,根据持久化层返回的数据和pipeline中的对象类型,使用IObjectDataSet和ObjectBuilder系统创建对象,并使用AttachObject委托将他们附加至Obase框架内。 下面我们来具体讲解前三个参数在此方法内如何使用:

  1. AssociationTree是根据关联关系生成的树形结构,根节点为当前查询结果类型(ResultType),节点表示包含运算的目标。在此方法中,传入的AssociationTree是要额外查询出的对象,我们可以将其在pipeline中处理(即使用联结表等方式)与主查询一并查出,也可以使用额外查询等方式进行补充。
  2. OpExecutor是之前GeneratePipeline生成的执行器链,如果要与额外查询对象一并查询,此执行器链内也应有AssociationTree在终结执行器中处理。此执行器链最终应生成用于操作持久化层的具体命令作为Execute方法的返回值。
  3. AttachObject attachObject 此参数表示一个附加至数据上下文的委托,我们在最终构造对象时,可以使用Obase内建的对象构造系统。此系统首先应使用DataRowAssigner(new DataRowAssignmentSet())来对持久化层的数据进行分配,在每次读入数据时我们应使用DataRowAssigner.ContainEquivalent方法确认此数据是否重复,如不重复则使用SetDataRow方法放入数据。之后,在完整读取数据后,构造ObjectBuilder(dataRowAssignmentSet, attachObject)用于重建对象。ObjectBuilder支持使用观察者模式对要构造的对象进行处理,一般由AssociationTree,Accept(ObjectBuilder)发起即可。当然,用户也可以自定义自己的对象构造系统,只需要在构造对象后将对象使用AttachObject附加至数据上下文即可。

没有找到您需要的文档?

您还可以通过人工服务在线咨询,服务时间为每天上午9点至下午6点。

If you can't find required answer, get in touch with us online. We provide service from 9:00 to 18:00.

让编程成为一件快乐的事
现在开始