• ERP 高级查询(Advanced Query)设计与实现 SQL语句解析成LLBL Gen ORM代码


    ERP 高级查询(Advanced Query)设计与实现 SQL语句解析成LLBL Gen ORM代码

     

    对于开始接触基于ORM技术开发的ERP程序,在相当长的时间内还是会考虑SQL语句,而不是ORM查询。即便是在很熟悉ORM查询,也不如对SQL语句的了解程度。于是想做出一个查询工具,把SQL语句转化为C#代码,用于查询。

    这样的程序片段在很多地方都需用用到。

    比如SQL语句

    SELECT RECNUM ,CCY ,DESCRIPTION ,SUSPENDED ,DEFAULT_RATE   FROM [Currency] 

    查询当前的货币及其名称,默认汇率。打开高级查询功能,把这几个字段拖动到SQL语句窗口中,点击按钮Execute即可在结果窗格中看到生成的ORM语句片段。

    image

    生成的LLBL Gen Pro代码片段

    ICurrencyManager currencyManager = ClientProxyFactory.CreateProxyInstance<ICurrencyManager>();
    ExcludeIncludeFieldsList fieldlist = new ExcludeIncludeFieldsList(false);
    fieldlist.Add(CurrencyFields.RECNUM);
    fieldlist.Add(CurrencyFields.CCY);
    fieldlist.Add(CurrencyFields.DESCRIPTION);
    fieldlist.Add(CurrencyFields.SUSPENDED);
    fieldlist.Add(CurrencyFields.DEFAULT_RATE);
    CurrencyEntity currency = currencyManager.GetCurrency(this., null, fieldlist);

    再来分析一下,如何实现这个过程。

    1  树结点多选功能

    如上图中所示,我选中树节点Currency中的多个字段,然后把它拖动到SQL语句窗口中,自动生成SQL查询语句。这里要实现树节点多选功能。WinForms内置的树控件不支持此功能,需要另找控件。

    这里,我选用CodeProject上的控件MultiSelectTreeView,它的功能用法如下介绍所示

    Summary   description   for   MultiSelectTreeView.   
    The   MultiSelectTreeView   inherits   from   System.Windows.Forms.TreeView   to      allow   user   to   select   multiple   nodes.    The   underlying   comctl32   TreeView   doesn't   support   multiple   selection.  Hence   this   MultiSelectTreeView   listens   for   the   BeforeSelect   &&   AfterSelect   
     events   to   dynamically   change   the   BackColor   of   the   individual   treenodes   to   
    denote   selection.   It   then   adds   the   TreeNode   to   the   internal   arraylist   of   currently   selectedNodes   after   validation   checks.   
         
    The MultiSelectTreeView supports   
    1)   Select   +   Control   will   add   the   current   node   to   list   of   SelectedNodes   
    2)   Select   +   Shitft     will   add   the   current   node   and   all   the   nodes   between   the   two     
     (if   the   start   node   and   end   node   is   at   the   same   level)   
    3)   Control   +   A   when   the   MultiSelectTreeView   has   focus   will   select   all   Nodes.   

    2  鼠标拖动编程

    树结点中设置AllowDrag,加入事件响应方法ItemDrag

    private void treeTables_ItemDrag(object sender, ItemDragEventArgs e)
     {
                if (e.Button == MouseButtons.Left)
                {
                    DoDragDrop(e.Item, DragDropEffects.Copy);
                }   
     }

    要拖进的SQL TextEdtor,则对它加入事件响应方法

     private void txtSqlScript_DragDrop(object sender, DragEventArgs e)
     {      
                TreeNode draggedNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
                if (draggedNode.Tag == "Column")
                {
                    List<string> columns=new List<string>();
                    foreach(TreeNode node in treeTables.SelectedNodes)
                    {
                       columns.Add(node.Text.Substring(0, node.Text.IndexOf("(") ));
                    }
                    string tableName = draggedNode.Parent.Text;
                    string sql = string.Format("SELECT {0}  FROM [{1}] ", string.Join(",",columns), tableName);
                    txtSqlScript.InsertText(sql);
                }
                else if (draggedNode.Tag == "Table")
                {
                    txtSqlScript.InsertText(string.Format("[{0}]",draggedNode.Text));
                }
     }

     

    这几句话,根据节点代表的含义(字段,表名),来构造SQL查询语句。

     

    3 SQL语句解析

    Visual Studio Database Edition提供了SQL语句解析功能,引用这两个程序集,实现类似的代码

    image

    public static string Execute(QueryDataSource dataSource,string sql)
     {
                string csharpCode = string.Empty;
                IList<ParseError> Errors;
                var parser = new TSql100Parser(false);
                StringReader reader = new StringReader(sql);
                IScriptFragment result = parser.Parse(reader, out Errors);
                var Script = result as TSqlScript;
                foreach (var ts in Script.Batches)
                {
                    foreach (var st in ts.Statements)
                    {
                        IterateStatement(st,ref csharpCode,dataSource);
                    }
                }
                return csharpCode;
    }

    此方法根据传入的SQL语句,返回C#代码。LLBL Gen 是一项ORM技术,ORM首要解决的问题是C#代码如何转化为对数据库操作的SQL语句。在这里,我把这个过程反过来,根据SQL语句,得到C#代码。

    数据表Currency 对应的程序中的实体类型是CurrencyEntity,它的字段RECNUM对应于C#实体类型CurrencyEntity中的Recnum,这个映射关系存储于生成的C#代码中。

    /// <summary>Inits CurrencyEntity's mappings</summary>
    private void InitCurrencyEntityMappings()
    {
        this.AddElementMapping( "CurrencyEntity", "MIS", @"dbo", "Currency", 29 );
        this.AddElementFieldMapping( "CurrencyEntity", "AcctApForex", "ACCT_AP_FOREX", true, "NVarChar", 30, 0, 0, false, "", null, typeof(System.String), 0 );
        this.AddElementFieldMapping( "CurrencyEntity", "AcctArForex", "ACCT_AR_FOREX", true, "NVarChar", 30, 0, 0, false, "", null, typeof(System.String), 1 );
        this.AddElementFieldMapping( "CurrencyEntity", "ApInvoBal", "AP_INVO_BAL", true, "Decimal", 0, 2, 16, false, "", null, typeof(System.Decimal), 2 );
        this.AddElementFieldMapping( "CurrencyEntity", "ApLinvoBal", "AP_LINVO_BAL", true, "Decimal", 0, 2, 16, false, "", null, typeof(System.Decimal), 3 );
        this.AddElementFieldMapping( "CurrencyEntity", "ApLnetBal", "AP_LNET_BAL", true, "Decimal", 0, 2, 16, false, "", null, typeof(System.Decimal), 4 );
        this.AddElementFieldMapping( "CurrencyEntity", "ApLopenBal", "AP_LOPEN_BAL", true, "Decimal", 0, 2, 16, false, "", null, typeof(System.Decimal), 5 );
        this.AddElementFieldMapping( "CurrencyEntity", "ApNetBal", "AP_NET_BAL", true, "Decimal", 0, 2, 16, false, "", null, typeof(System.Decimal), 6 );
    }

    运行时,只需要调用此方法即可得到它们的映射关系,以实现将SQL语句的object(table,column)转化为C#程序对应的object(Entity,property)。

     

    4  Debug功能的实现

    为了实现即使运行效果,将上面的生成的SQL语句,编译成C#程序集,再执行程序集,得到返回的结果。

    CodeDomProvider codeProvider = null;
    if (Language == LanguageType.CSharp)
              codeProvider = new CSharpCodeProvider();
    else if (Language == LanguageType.VB)
              codeProvider = new VBCodeProvider();
    
     //create the language specific code compiler
    ICodeCompiler compiler = codeProvider.CreateCompiler();
    
     //add compiler parameters
    CompilerParameters compilerParams = new CompilerParameters();
    compilerParams.CompilerOptions = "/target:library"; // you can add /optimize
    compilerParams.GenerateExecutable = false;
    compilerParams.GenerateInMemory = true;
    compilerParams.IncludeDebugInformation = false;
    
     // add some basic references
    compilerParams.ReferencedAssemblies.Add("mscorlib.dll");
    compilerParams.ReferencedAssemblies.Add("System.dll");
    compilerParams.ReferencedAssemblies.Add("System.Data.dll");
    compilerParams.ReferencedAssemblies.Add("System.Drawing.dll");
    compilerParams.ReferencedAssemblies.Add("System.Xml.dll");
    compilerParams.ReferencedAssemblies.Add("System.Windows.Forms.dll");
     

    如果团队中允许多语言并行开发,此功能也可以实现将生成的C#代码,转化为VB代码。

    在这里,需要构造一个Main方法,把生成的代码嵌入到这个Main方法中去,返回一个对象,把这个对象反射到Debug的网格窗口中。反射调用的入口方法如下所示

    private object  CallEntry(Assembly assembly, string entryPoint)
     {
                object result = null;
                try
                {
                    //Use reflection to call the static Main function
                    Module[] mods = assembly.GetModules(false);
                    Type[] types = mods[0].GetTypes();
                    foreach (Type type in types)
                    {
                        MethodInfo mi = type.GetMethod(entryPoint,
                                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
                        if (mi != null)
                        {
                            if (mi.GetParameters().Length == 1)
                            {
                                if (mi.GetParameters()[0].ParameterType.IsArray)
                                {
                                    string[] par = new string[1]; // if Main has string [] arguments
                                    result=mi.Invoke(null, par);
                                }
                            }
                            else
                            {
                               result= mi.Invoke(null, null);
                            }
                        }
                    }
                    LogErrMsgs("Engine could not find the public static " + entryPoint);
                }
                catch (Exception ex)
                {
                    LogErrMsgs("Error:  An exception occurred", ex);
                }
                return result;
    }

     

     

    最后一步,绑定结果到网格中

    void BindEntity2Grid(IEntity2 entity)
    {
                grid.RowHeadersVisible = false;
                grid.AutoGenerateColumns = false;
                grid.Columns.Clear();
    
                Type type = entity.GetType();
                PropertyInfo[] propertyInfos = type.GetProperties();  
                for (int i = 0; i < propertyInfos.Length; i++)
                {
                    PropertyInfo property = propertyInfos[i];
                    if (excludeColumns.Contains(property.Name))
                        continue;
    
                    DataGridViewTextBoxColumn column = new DataGridViewTextBoxColumn();
                    column.HeaderText = property.Name;
                    column.DataPropertyName = property.Name;
                    column.Name = property.Name;
                    grid.Columns.Add(column);
                }
                for (int i = 0; i < propertyInfos.Length; i++)
                {
                    PropertyInfo property = propertyInfos[i];
                    if (excludeColumns.Contains(property.Name))
                        continue;
    
                    grid.Rows[0].Cells[property.Name].Value = ReflectionHelper.GetPropertyValue(entity, property.Name);
                }
     }
     

    这里的目的是读取对象的属性,生成Grid的列,并填充值。

    这里使用的SQL语法高亮控件来自于CodeProject的FastColoredTextBox。 读取一个数据库中所有的表及表的字段用到如下的SQL语句,供您参考。

    SELECT name  FROM sysobjects  WHERE xtype='U'  
     
    SELECT syscolumns.NAME AS [ColumnName], systypes.NAME AS [ColumnType], syscolumns.length AS  [ColumnLength],syscolumns.isnullable AS [Nullable]  FROM syscolumns   
    left join systypes on syscolumns.xusertype=systypes.xusertype 
    WHERE id=(select id from sysobjects where name='Currency‘

     

    此功能有个明显的Bug,没有把功能编码显示在地址栏中,因为它继承于FormBase,需要改成FunctionFormBase可达到目的,界面可能需要重新排版一下,改变继承的基类会让窗体设计器重新加载基类控件,子类的控件会丢失。

    [FunctionCode("SAUMAQ")]
    public partial class AdvancedQuery : FormBase
        {
            private string _fileName;
    
            public AdvancedQuery()
            {
                InitializeComponent();
            }
    }
  • 相关阅读:
    深入解析ATL 笔记1 添加一个simple object时做了什么
    分析MFC中CDialog的子类对象如何知道自己是model还是modeless的
    分析boost::signal之识别是否Trackable的派生类对象
    《转》汇编标志位
    <转> strcpy当初没有考虑到的地方
    <转>OD常用断点列表
    依赖倒转原则
    <转> xor eax, ebp” being used
    <转>NEG+SBB指令组合的用处
    函数Int3断点检测
  • 原文地址:https://www.cnblogs.com/mingyongcheng/p/3123382.html
Copyright © 2020-2023  润新知