AdventureWorks的存储过程uspGetEmployeeManagers,调用方法如下
DECLARE @return_value intEXEC @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中的方法CallRetrievalStoredProcedurepublic 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方法,用来把结果放到DataSetpublic 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的理论,别忘了配合一个自动化的代码生成工具,真正做到快速开发。