LLBL Gen 3.x 源代码追踪与解析 存储过程的执行

AdventureWorks的存储过程uspGetEmployeeManagers,调用方法如下
DECLARE    @return_value int

EXEC    @return_value = [dbo].[uspGetEmployeeManagers]
        @EmployeeID = 1
执行的结果所示

image

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

[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启动模板编辑器
image
点击Edit selected来编辑当前的模板,进入SD_RetrievalProceduresTemplate模板,很像ASP.NET的语法
image
对于存储过程,它会生成三个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中,它们是代表不同的存储过程。
 image
如上图,有两个版本的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的理论,别忘了配合一个自动化的代码生成工具,真正做到快速开发。

posted @ 2011-08-25 14:03  信息化建设  阅读(1663)  评论(0编辑  收藏  举报