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();
        }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM