2014/01/28

REST Server 取得 Remote IP ( Client IP ) 的方法

有關這個問題,解法如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
procedure TWebModule.DSServerConnect(
  DSConnectEventObject: TDSConnectEventObject);
var _Session: TDSSession;
begin
  try
    if Assigned(DSConnectEventObject.ChannelInfo) then
    begin
      _Session := TDSSessionManager.GetThreadSession;
      if Assigned(_Session) then
      begin
        if _Session.GetData('RemoteAddr') = '' then
          _Session.PutData('RemoteAddr', DSConnectEventObject.ChannelInfo.Info);
      end;
    end;
  except
  end;
end;

今天測試大概是可以解決:
1. Request.RemoteAddr 不定時會存取失敗
2. REST 方法呼叫時,DSConnectEventObject.ChannelInfo 和 TDSSessionManager.GetThreadSession 必會存取失敗的問題

可能有人會問,如果使用 REST 方法時又想記錄 IP 的時候該怎麼處理?
目前原則上就只能避開使用 TDSSessionManage 。
至於什麼時候會修正這個 Bug ?
 嗯.
.
.
.
……我想應該是在不久的將來吧!

See also:


DataSnap 的 2-Tier 設計方式

無意中看到這篇:DataSnap客戶端共享服務器端的數據庫連接

真是另類的設計方式!
有機會可以試試看。

2014/2/1 更新
這樣的方式就是把 TSQLConnection 連線傳送到 Client 。所以缺點就和 2-Tier 架構相同。

2017/06/25 更新

2014/01/26

決定 DataSnap Sevice 連結資料庫的方式

既然要把 Database 連接元件抽離,就免不了還要重新決定哪一種連線方法。

當然如果已經有喜好的連接元件,也是可以直接使用,並不是說 DataSnap Service 就必須採用特定元件。

Delphi / C++ Builder 提供了以下元件:
BDE 元件盤
BDE (Borland Database Engine)
經典中的經典,強大的 BDE Administrator 可以說是老 Delphi 開發者美好回憶。雖然最新版的 XE 已經把 BDE 排除在安裝包中,但對 BDE 有執念的開發者,還是會想方設法地把它找回來。

*ID: 29997, BDE Installer for RAD Studio, Delphi, C++Builder XE7

只是 BDE 在停止維護後, Unicode 的支援計畫也隨之停止,對於這讓人又愛又恨的元件,還是讓它還給歷史,擦擦眼淚,來看下一組元件。


ADO(ActiveX Data Objects) Express / dbGO
Borland 重新包裝 ADO 後,所做出的超完美元件組。在經歷的十餘年後,才有了可以和它相提併論的 FireDAC 。在這段期間,能走出 BDE 傷痛的開發者,絕大多數都選擇了 ADO ,它的穩定性及 SQL Server 完美搭配,再也沒有元件能出其右。

dbExpress
這個元件組很有戲,當時的開發市場情形簡述如下:

1. Borland 為了擺脫微軟的陰影。
2. 因應 BDE 停止開發後所帶來的衝擊。
3. Borland 高層決定推廣 DataSnap 技術。

於是 dbExpress 就在這個期待下誕生了,當時 dbExpress 的教學如雨後春筍般的不斷冒出。

只是, dbExpress 面臨了以下的問題:

1. 和 BDE、ADO 的開發方式有很大的矛盾。
2. dbExpress 的 Driver 完成度不足。

dbExpress 先天就是以搭配 DataSnap 為前提的元件,所以它的設定非常的簡單,而且和 BDE、ADO 相比,只有它們一半的功能,另一半則由 TDataSetProvider 及 TClientDataSet 來實現,這就和原本使用 BDE 和 ADO 的開發者的使用習慣完全不同,而所有關於 dbExpress 的書,對於 DataSnap 也沒有多所著墨,一律視為 dbExpress 框架。

導致讓開發者認為 dbExpress 是個很囉嗦的元件組。可是卻又不得不承認,在兩層架構下,dbExpress + DataSnap 真的很囉嗦。

然而 dbExpress 設計的方式真能順利把兩層架構無痛升級三層架構嗎?

另一個讓人垢病的就是 dbExpress Driver,由於 Borland 採用的是三方廠商共同支撐這部份的技術支援路線,所以自家開發的 dbExpress Driver 只相容於 InterBase ,其它 dbExpress Driver 寫得是問題很多;大部份的開發者,包含當時為 dbExpress 寫書的作者,都經過 dbExpress Bugs 的洗禮。

從 dbExpress 1.0 一直到 4.0 後,在 RAD Studio XE7 的說明文件中,正式宣告 dbExpress 停止開發,也算是終止了開發者為 dbExpress Bugs 糾結的惡夢。

RAD Studio XE7 的說明文件中,宣告 dbExpress 停止在行動裝置的開發,將 dbExpress 納入 DataSnap 的標準配備,並以 FireDAC 導正為 2-Tier 資料庫設計的最佳方案。

目前使用上,我認為 dbExpress 是設定最無腦的元件組,但如果有餘力,買個 dbExpress Driver 就更完美了。

dbExpress ODBC driver 也是追求穩定的另一選擇。

FireDAC
Embarcadero 於 2013 年買下 AnyDAC 後,結合 FireMonkey framework ,正式更名為 FireDAC,它並不是只能用在 FireMonkey application,VCL application 也同樣能享受這高效能的資料連結元件。

FireDAC 和 dbExpress 一樣,也是透過不同的 Driver 來達到對不同種類的資料庫支援,但設計方式屬於道地的兩層架構,這對原本使用 BDE 和 ADO 的開發者來說衝擊較小,所以一堆出便大受開發者的青睞。

如果是兩層開發,選擇就相對簡單:

  • 使用微軟方庫或只能使用 ODBC 的資料庫,就用 ADO。
  • EMBT 支援的資料庫或對 TDataSet 的操作一致性就用 FireDAC。

有買 DBX Driver 的,就繼續沿用或是透過 FireDAC 加載,這可以說是將 DBX 做 2-Tier 化的好方法。

但要使用 DataSnap,必須再重新思考這四大元件的選擇。

--- 未完待續 ---

2014/01/22

Session funtion programming in PostgreSQL's PL/pgSQL

In SQL Server, we can to used:

DECLARE @Variable Type
SELECT * FROM TableName WHERE KeyField = @Variable

like something...

But, PostgreSQL not support SESSION VARIABLE.

However, In PostgreSQL 9.x, it add the statement in PL/pgSQL.

1. DO statement


Example code:
DO $$
DECLARE myvar integer;
BEGIN
    SELECT 5 INTO myvar;

    DROP TABLE IF EXISTS tmp_table;
    CREATE TABLE tmp_table AS
    SELECT * FROM yourtable WHERE   id = myvar;
END $$;

SELECT * FROM tmp_table;


2. CREATE OR REPLACE FUNCTION


Example code:
CREATE OR REPLACE FUNCTION somefuncname() RETURNS int LANGUAGE plpgsql AS $$
DECLARE
  one int;
  two int;
BEGIN
  one := 1;
  two := 2;
  RETURN one + two;
END
$$;
SELECT somefuncname();



These solution can solve need execute PL/pgSQL in session function.

The Sequelae(後遺症) is remain temp tempFunction / tempTable in PostgreSQL database, when the executed PL/pgSQL command.

3. Session Variables


See also:
How to declare local variables in postgresql?
PostgreSQL Documentation: SQL-DO
How to declare a variable in a PostgreSQL query

2014/01/20

葡萄牙人寫的 Master / Detail for ClientDataSet 教學

雖然完全看不懂葡萄牙文,但英文的程式仍具有很大的幫助。

在 DataSetProvider.Options 設定 段落可以說是全篇的精華啊!

PASSO-A-PASSO COMO CRIAR UM RELACIONAMENTO MASTER/DETAIL USANDO CLIENTDATASET


主要設定為 True 的 Options 項目有:(說明參閱)
poCascadeDeletes: Tells the server to delete detail records automatically when master table records are deleted. To use this option, the provider must represent the master of a master/detail relationship and the database must support cascaded deletes as part of its referential integrity settings.

poCascadeUpdates: Tells the server to automatically update detail records when master table key values are changed. To use this option, the provider must represent the master of a master/detail relationship and the database must support cascaded updates as part of its referential integrity settings. 

poAutoRefresh: Refreshes the client dataset with current record values whenever it applies updates.

poPropogateChanges: Changes made in a BeforeUpdateRecord or AfterUpdateRecord event handler are sent back to the client and merged into the client dataset.

poRetainServerOrder: Signals that the client should not attempt to re-establish the default order by sorting the records in the data packet. This prevents the client from changing the record order because, for example, it is unable to duplicate the locale on the server.



2014/01/17

DBCommon.pas 雖然有 BUG ,但裡頭值得研究

此單元可以分析SQL語句,提供對於SQL 語句的TOKEN方式的解析。提供TExprParser,分析了類。
SQL Parser in Delphi for SQL Server

很開心的,我遇到這個問題……
DBCommon.GetIndexForOrderBy Get ERROR
List Index Out of Bounds (-1)

簡單的說就是:
如果 Order 後面接了一些內容 (EX: LIMIT),而該 SQL 語句也沒有回傳值(RecordCount = 0)時,就會發生錯誤。

仿間的解決方法 Can I recompile the .PAS files used by the Delphi IDE?

2014/01/20 更新
在這篇文章中,有個出乎意料的解法:
dbExpress + InterBase and ORDER BY
Datasnap.Provider.TProviderOptions 

  DataSetProvider.Options.poRetainServerOrder := True;

這樣就能夠解決 GetIndexForOrderBy 在特殊情況下會出錯的問題。

只是為什麼要這樣設定?
EMBT 沒說清楚,論壇上也沒講明白。

2014/01/21 更新
針對 poRetainServerOrder 設定項, Devart 論壇的回覆是這樣的:
This behavior is due to the fact that, when setting the poRetainServerOrder option to True, sorting on the client side doesn't occur, the sorting returned by the server is used.

那麼,這個錯誤真的算是 "Bug" 嗎?
按照 2004 年的 Borland 論壇,某位 Delphi 狂熱者是這麼說的:
Therefore there is no bug, it is as designed, and there are two documented
ways of dealing with the situation.


是 Bug 還是 Feature?就交給在座前的各位看倌評判囉!

2015/07/02 更新
poRetainServerOrder 的影響 :
在導出 MyBase XML,裡面的 PARAMS 段如下:
 

DEFAULT_ORDER 的值由左到右標明 Field 為排序的順位
這個 Index 用到的機會很小,幾乎也沒有人在用
因為!只有搭配【DBX】時才會產生,如果沒有這「Feature」,還真不知道會有這個問題。

2014/01/14

Trim 全形空白 Delphi & C#

C#:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System;

namespace SampleApplication
{
    static class Program
    {
        ///<summary>
        /// Trim (char)12288. Power by EdenW.
        ///</summary>
        public static string TrimEx(string S)
        {
          int I = 0;
          int L = S.Length - 1;
          while ((I <= L) && ((S[I]==' ') || (S[I]==' ')))
            I++;
            
          if (I > L) return "";
          else
          {
            while ((S[L]==' ') || (S[L]<=' '))
              L--;
            
            return S.Substring(I, L - I + 1);
          }
        }    
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            //Console.WriteLine("Hello world!");
            Console.WriteLine(TrimEx(" Hello world! "));
        }
    }
}


Delphi
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
///<summary>
/// Trim (char)12288. Power by EdenW.
///</summary>
function TrimEx(const S: string): string;
var
  I, L: Integer;
begin
  L := Length(S);
  I := 1;
  while (I <= L) and ((S[I]=' ') or (S[I]<=' ')) do Inc(I);
  if I > L then Result := '' else
  begin
    while ((S[L]=' ') or (S[L]<=' ')) do Dec(L);
    Result := Copy(S, I, L - I + 1);
  end;
end;