zoukankan      html  css  js  c++  java
  • 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的理论,别忘了配合一个自动化的代码生成工具,真正做到快速开发。

  • 相关阅读:
    从苦逼到牛逼,详解Linux运维工程师的打怪升级之路
    Linux命令学习
    Linux企业运维人员最常用150个命令汇总
    理解Docker
    理解Docker(8):Docker 存储之卷(Volume)
    理解Docker(7):Docker 存储
    理解Docker(6):若干企业生产环境中的容器网络方案
    理解Docker(5):Docker 网络
    理解Docker(4):Docker 容器使用 cgroups 限制资源使用
    理解Docker(3):Docker 使用 Linux namespace 隔离容器的运行环境
  • 原文地址:https://www.cnblogs.com/JamesLi2015/p/2153220.html
Copyright © 2011-2022 走看看