博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
LLBL Gen 3.x 源代码追踪与解析 存储过程的执行
阅读量:6669 次
发布时间:2019-06-25

本文共 7633 字,大约阅读时间需要 25 分钟。

AdventureWorks的存储过程uspGetEmployeeManagers,调用方法如下

DECLARE    @return_value int

EXEC    @return_value = [dbo].[uspGetEmployeeManagers]

        @EmployeeID = 1
执行的结果所示

在测试工程中,创建如下的测试脚本

[TestMethod]

public void TestStoredProcedureCall()
{
           int employeeId = 1;
           DataTable tbl = RetrievalProcedures.UspGetEmployeeManagers(employeeId);
           int rows = tbl.Rows.Count;
}

Stored Procedure是数据库特定的,不同的数据库创建方式不一样,所以生成的代码文件会放到DBSpecific项目中。

查看生成的RetrievalProcedures.cs的源代码,先来分析一下它的模板文件。LLBL Gen 3.x把Template editor集成到ORM设计器中,通过Windows的Show Templates Binding Viewer启动模板编辑器
点击Edit selected来编辑当前的模板,进入SD_RetrievalProceduresTemplate模板,很像ASP.NET的语法
对于存储过程,它会生成三个overload的C#方法
public static DataTable UspGetEmployeeManagers(System.Int32 employeeId);
public static DataTable UspGetEmployeeManagers(System.Int32 employeeId, IDataAccessCore dataAccessProvider);
public static IRetrievalQuery GetUspGetEmployeeManagersCallAsQuery(System.Int32 employeeId);
第一个方法重载方法定义如下,它会call第二个方法

public static DataTable UspGetEmployeeManagers(System.Int32 employeeId)

{
            using(DataAccessAdapter dataAccessProvider = new DataAccessAdapter())
            {
                return UspGetEmployeeManagers(employeeId, dataAccessProvider);
            }
}
第二个方法的定义如下,它调用CreateUspGetEmployeeManagersCall来返回StoredProcedureCall
public static DataTable UspGetEmployeeManagers(System.Int32 employeeId, IDataAccessCore dataAccessProvider)
{
   using(StoredProcedureCall call = CreateUspGetEmployeeManagersCall(dataAccessProvider, employeeId))
            {
                DataTable toReturn = call.FillDataTable();               
                return toReturn;
            }
}
CreateUspGetEmployeeManagersCall是一个私有方法,用来生成StoredProcedureCall对象

private static StoredProcedureCall CreateUspGetEmployeeManagersCall(IDataAccessCore dataAccessProvider, System.Int32 employeeId)

        {
            return new StoredProcedureCall(dataAccessProvider, "[AdventureWorks].[dbo].[uspGetEmployeeManagers]", "UspGetEmployeeManagers")
                            .AddParameter("@EmployeeID", "Int", 0, ParameterDirection.Input, true, 10, 0, employeeId);
        }

StoredProcedureCall用来调用存储过程,在这里,把存储过程分两类:Action procedure执行命令型,Retrieval stored procedure 查询型,对于查询类型的存储过程,可以把结果FillDataSet,也可以FillDataTable。

FillDataSet的代友码如下

public DataSet FillDataSet()

{
     DataSet toReturn = new DataSet(_mappedCallName);
      _dataAccessProvider.CallRetrievalStoredProcedure(_storedProcedureName, _parameters.ToArray(), toReturn);
      return toReturn;
}
FillDataTable的代码如下

public DataTable FillDataTable()

{
     DataTable toReturn = new DataTable(_mappedCallName);
     _dataAccessProvider.CallRetrievalStoredProcedure(_storedProcedureName, _parameters.ToArray(), toReturn);
     return toReturn;
}
这两个方法,都指定DataAccessAdapterBase中的方法CallRetrievalStoredProcedure

public virtual bool CallRetrievalStoredProcedure(string storedProcedureToCall, DbParameter[] parameters, DataTable tableToFill)

    {
        using(DbCommand command = CreateStoredProcedureCallCommand(storedProcedureToCall, parameters))
        {
            using(DbDataAdapter adapter = CreateNewPhysicalDataAdapter())
            {
                adapter.SelectCommand = command;
                adapter.Fill(tableToFill);
            }
        }
        return true;
    }

也就调用泛型的DbDataAdapter ,DbCommand 来执行存储过程,它有一个overload方法,用来把结果放到DataSet

public virtual bool CallRetrievalStoredProcedure(string storedProcedureToCall, DbParameter[] parameters, DataSet dataSetToFill)

        {
            using(DbCommand command = CreateStoredProcedureCallCommand(storedProcedureToCall, parameters))
            {
                using(DbDataAdapter adapter = CreateNewPhysicalDataAdapter())
                {
                    adapter.SelectCommand = command;
                    adapter.Fill(dataSetToFill);
                }
            }
            return true;
        }

 

DataAccessAdapterBase的方法ProduceCorrectStoredProcedureName方法是根据存储过程的名字,来生成存储过程的调用方法
string IDataAccessCore.ProduceCorrectStoredProcedureName(string storedProcedureToCall)
{
            return CreateCorrectStoredProcedureName(storedProcedureToCall);
}

注意这个方法默认是public的,因为DataAccessAdapterBase为实现接口IDataAccessAdapter的.

继续进入DataAccessAdapterBase的CreateCorrectStoredProcedureName方法

protected virtual string CreateCorrectStoredProcedureName(string storedProcedureToCall)
{
            DynamicQueryEngineBase dqe = CreateDynamicQueryEngine();
            string procName = dqe.GetNewPerCallStoredProcedureName(storedProcedureToCall);
            procName = dqe.GetNewStoredProcedureName(procName);
            return procName;
}

这里又会进入DQE,用DQE的GetNewPerCallStoredProcedureName方法生成调用语句。

以SQL Server为例子,进入到DynamicQueryEngine的GetNewPerCallStoredProcedureName方法
public override string GetNewPerCallStoredProcedureName(string currentName)
        {
            Regex procNamePartFinder = _procMatchingMatcher;
            MatchCollection matchesFound = procNamePartFinder.Matches(currentName);

            if(matchesFound.Count <= 0)

            {
                // just the proc name, or some weird format we don't support, return the proc name
                return currentName;
            }

            // there's just 1 match:

            string catalogName = matchesFound[0].Groups["catalogName"].Value;
            string schemaName = matchesFound[0].Groups["schemaName"].Value;
            string procName = matchesFound[0].Groups["procName"].Value;
正则表达式procMatchingMatcher 的定义如下
private static readonly Regex _procMatchingMatcher = new Regex(@"((?<catalogName>\[[\w\. \$@#]+\]|\w+(?=\.)).)?(?<schemaName>\[[\w\. \$@#]+\]|\w+).(?<procName>\[[\w\. \$@#]+\])", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

在这个方法的开头,会应用正则表达式对存储过程的调用语法进入验证,格式满足像这样的AdventureWorks.dbo.uspGetEmployeeManagers

继续GetNewPerCallStoredProcedureName方法中的代码

string toReturn;

if(catalogName.Length <= 0)
{
// no catalog specified
toReturn = ((DbSpecificCreatorBase)this.Creator).GetNewPerCallSchemaName(schemaName) + "." + procName;
}
else
{
// catalog and schema specified
toReturn = ((DbSpecificCreatorBase)this.Creator).GetNewPerCallCatalogName(catalogName) + "." + ((DbSpecificCreatorBase)this.Creator).GetNewPerCallSchemaName(schemaName) + "." + procName;
}
生成的代码SqlServerSpecificCreator派生于DbSpecificCreatorBase,进入它的GetNewPerCallCatalogName
,是为了确定调用的catalogName和schemaName。catalogName是数据库名称,schemaName是存储过程的所有

比如AdventureWorks.Sales.uspGetEmployeeManagers和AdventureWorks.dbo.uspGetEmployeeManagers

在SQL Server中,它们是代表不同的存储过程。
 
如上图,有两个版本的uspGetEmployeeManagers,它们schemaName分别是dbo和Sales
最后回到RetrievalProcedures的方法

public static DataTable UspGetEmployeeManagers(System.Int32 employeeId, IDataAccessCore dataAccessProvider) 

        using(StoredProcedureCall call = CreateUspGetEmployeeManagersCall(dataAccessProvider, employeeId))
            {
                DataTable toReturn = call.FillDataTable();               
                return toReturn;
            } 
}

再来看生成的代码中三个方法的第三方法GetUspGetEmployeeManagersCallAsQuery

public static IRetrievalQuery GetUspGetEmployeeManagersCallAsQuery(System.Int32 employeeId)
        {
            using(DataAccessAdapter dataAccessProvider = new DataAccessAdapter())
            {
                return CreateUspGetEmployeeManagersCall(dataAccessProvider, employeeId).ToRetrievalQuery();
            }
        }

同上面的第二个方法,调用CreateUspGetEmployeeManagersCall,区别于第二个方法,这里调用它的ToRetrievalQuery方法,返回IRetrievalQuery查询

StoredProcedureCall的ToRetrievalQuery的代码如下
public IRetrievalQuery ToRetrievalQuery()
{
            DbCommand cmd = _creator.CreateCommand();
            cmd.CommandText = _dataAccessProvider.ProduceCorrectStoredProcedureName(_storedProcedureName);
            cmd.CommandType = CommandType.StoredProcedure;
            IRetrievalQuery toReturn = new RetrievalQuery(cmd);
            foreach(DbParameter parameter in _parameters)
            {
                toReturn.Parameters.Add(parameter);
            }
            return toReturn;
}
也是通过通用的DbCommand ,DbConnection来产生命令,发送到服务器中。

结论:SQL Server的DynamicQueryEngine会负责解析SQL Server类型的存储过程的调用方式,如开头所示,存储过程的执行仍然是通过泛型的DbCommand,DbDataAdapter,它已经内置到ORM Support类型库中。
 
推荐一个办法,分别用SQL Server和MySQL的数据库方言写出
SELECT * FROM SalesOrderHeader WHERE SalesOrderID=@ SalesOrderId
的存储过程的实现。这样可以更加清楚的看到哪些代码是DQE的工作,哪些是固定到ORM Support中的代码。

分析到这里,我想到一个LLBL Gen的设计思路,比如要支持MySQL和SQL Server,先把基础的类型写出来,放到ORM Supporto类型库中,比如DynamicQueryEngineBase,用于查询数据的基础类型,DbSpecificCreatorBase用于生成数据库方言的方法,然后将需要依据数据为类型不同而变化的部分放到generated code中,也就是database-specific中。再配合ORM设计工具,依据模板生成可以变化的方法。这三个相互配合,产生强大的易用开发效果。
如果你有一套ORM的理论,别忘了配合一个自动化的代码生成工具,真正做到快速开发。

转载地址:http://pflxo.baihongyu.com/

你可能感兴趣的文章
ASP.NET MVC使用动态产生meta
查看>>
WIN7无法访问共享打印机及文件的解决办法
查看>>
C语言基础(9)-字符串格式化输入和输出
查看>>
【转】使用import scope解决maven继承(单)问题
查看>>
.NET Core微服务之路:让我们对上一个Demo通讯进行修改,完成RPC通讯
查看>>
你 我 我们 技术 让世界变得更好一些
查看>>
统计建模与R软件-附R原程序
查看>>
安装MSSQL2008出现的问题记录
查看>>
iOS 中NSRunLoop的使用
查看>>
WCF安全:通过 扩展实现用户名密码认证
查看>>
模块化JavaScript设计模式(一)
查看>>
Android4.2.2NDK和源代码开发jni的异同
查看>>
oracle 删除已连接用户
查看>>
这家棋牌游戏厂商冲刺港交所:上半年营收近3亿 曾李青是投资人
查看>>
巴西圣保罗街头上演“唱响中国”新春快闪活动
查看>>
Apollo 3.0来了!百度自动驾驶硬件系统全解读
查看>>
你或许不知道Vue的这些小技巧
查看>>
Vue2.0进阶组件篇1 教你秒撸(短信倒计时组件)
查看>>
测试你的前端代码 - part2(单元测试)
查看>>
使用 Cocos Creator 打造自己的爆款小游戏《方块弹珠》!
查看>>